From 78911c1a579c762b63e392b70b24fe36ba78a38e Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 9 Apr 2024 12:24:14 +0400 Subject: [PATCH 01/70] Skip EL part of graffiti when EL info not available (#8175) * Skip EL part of graffiti when EL info not available (previously was NA0000) * Change EL tracker to push either UNKNOWN (when failed) or valuable version --- .../coordinator/GraffitiBuilder.java | 16 +- .../coordinator/GraffitiBuilderTest.java | 154 +++++++++++++++++- .../ExecutionClientVersionProvider.java | 20 ++- .../ExecutionClientVersionProviderTest.java | 45 ++++- 4 files changed, 219 insertions(+), 16 deletions(-) diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilder.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilder.java index eb346831356..611803cf3a3 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilder.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilder.java @@ -128,8 +128,10 @@ protected Bytes32 joinNonEmpty(final String delimiter, final String... parts) { @VisibleForTesting protected String formatClientsInfo(final int length) { + final boolean isElInfoAvailable = !ClientVersion.UNKNOWN.equals(executionClientVersion.get()); final String safeConsensusCode = extractClientCodeSafely(consensusClientVersion); - final String safeExecutionCode = extractClientCodeSafely(executionClientVersion.get()); + final String safeExecutionCode = + isElInfoAvailable ? extractClientCodeSafely(executionClientVersion.get()) : ""; // LH1be52536BU0f91a674 if (length >= 20) { return String.format( @@ -137,7 +139,7 @@ protected String formatClientsInfo(final int length) { safeConsensusCode, consensusClientVersion.commit().toUnprefixedHexString(), safeExecutionCode, - executionClientVersion.get().commit().toUnprefixedHexString()); + isElInfoAvailable ? executionClientVersion.get().commit().toUnprefixedHexString() : ""); } // LH1be5BU0f91 if (length >= 12) { @@ -146,7 +148,9 @@ protected String formatClientsInfo(final int length) { safeConsensusCode, consensusClientVersion.commit().toUnprefixedHexString().substring(0, 4), safeExecutionCode, - executionClientVersion.get().commit().toUnprefixedHexString().substring(0, 4)); + isElInfoAvailable + ? executionClientVersion.get().commit().toUnprefixedHexString().substring(0, 4) + : ""); } // LH1bBU0f if (length >= 8) { @@ -155,11 +159,13 @@ protected String formatClientsInfo(final int length) { safeConsensusCode, consensusClientVersion.commit().toUnprefixedHexString().substring(0, 2), safeExecutionCode, - executionClientVersion.get().commit().toUnprefixedHexString().substring(0, 2)); + isElInfoAvailable + ? executionClientVersion.get().commit().toUnprefixedHexString().substring(0, 2) + : ""); } // LHBU if (length >= 4) { - return String.format("%s%s", safeConsensusCode, safeExecutionCode); + return String.format("%s%s", safeConsensusCode, isElInfoAvailable ? safeExecutionCode : ""); } return ""; diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilderTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilderTest.java index ea990b5249e..734da134a1c 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilderTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/GraffitiBuilderTest.java @@ -101,6 +101,17 @@ protected int calculateGraffitiLength(Optional graffiti) { assertThat(graffitiBuilder.buildGraffiti(Optional.of(graffiti))).isEqualTo(graffiti); } + @Test + public void buildGraffiti_shouldPreferCallInput() { + final Bytes32 defaultGraffiti = Bytes32Parser.toBytes32(asciiGraffiti32); + final Bytes32 userGraffiti = Bytes32Parser.toBytes32(ASCII_GRAFFITI_20); + final Bytes32 expectedGraffiti = Bytes32Parser.toBytes32(ASCII_GRAFFITI_20 + " TK"); + this.graffitiBuilder = new GraffitiBuilder(CLIENT_CODES, Optional.of(defaultGraffiti)); + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + assertThat(graffitiBuilder.buildGraffiti(Optional.of(userGraffiti))) + .isEqualTo(expectedGraffiti); + } + @ParameterizedTest(name = "format={0}, userGraffiti={1}") @MethodSource("getBuildGraffitiFixtures") public void buildGraffiti_shouldProvideCorrectOutput( @@ -122,6 +133,27 @@ public void buildGraffiti_shouldProvideCorrectOutput( .isEqualTo(expectedGraffitiBytes); } + @ParameterizedTest(name = "format={0}, userGraffiti={1}") + @MethodSource("getBuildGraffitiFixturesElInfoNa") + public void buildGraffiti_shouldProvideCorrectOutput_whenElInfoNa( + final ClientGraffitiAppendFormat clientGraffitiAppendFormat, + final Optional maybeUserGraffiti, + final String expectedGraffiti) { + this.graffitiBuilder = new GraffitiBuilder(clientGraffitiAppendFormat, userGraffiti); + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + final Bytes32 expectedGraffitiBytes = Bytes32Parser.toBytes32(expectedGraffiti); + assertThat( + new String( + Arrays.copyOfRange( + expectedGraffitiBytes.toArray(), + 0, + 32 - expectedGraffitiBytes.numberOfTrailingZeroBytes()), + StandardCharsets.UTF_8)) + .isEqualTo(expectedGraffiti); + assertThat(graffitiBuilder.buildGraffiti(maybeUserGraffiti.map(Bytes32Parser::toBytes32))) + .isEqualTo(expectedGraffitiBytes); + } + @Test public void extractGraffiti_shouldReturnEmptyString() { assertThat(graffitiBuilder.extractGraffiti(Optional.empty(), 0)).isEqualTo(""); @@ -290,7 +322,7 @@ public void formatClientInfo_shouldRenderClientNames() { } @Test - public void formatClientInfo_shouldSkipClientsInfoWhenNotEnoughSpace() { + public void formatClientInfo_shouldSkipClientsInfo_whenNotEnoughSpace() { graffitiBuilder.onExecutionClientVersion(BESU_CLIENT_VERSION); // Empty @@ -305,6 +337,84 @@ public void formatClientInfo_shouldSkipClientsInfoWhenNotEnoughSpace() { .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isEqualTo(0)); } + @Test + public void formatClientInfo_shouldRenderClClientNameAndFullCommit_whenElInfoNotAvailable() { + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + + // 20: LH1be52536BU0f91a674 + assertThat(graffitiBuilder.formatClientsInfo(30)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString()) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(20)); + assertThat(graffitiBuilder.formatClientsInfo(20)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString()) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(20)); + } + + @Test + public void formatClientInfo_shouldRenderClClientNameAndHalfCommit_whenElInfoNotAvailable() { + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + + // 12: LH1be5BU0f91 + assertThat(graffitiBuilder.formatClientsInfo(19)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString().substring(0, 4)) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(12)); + assertThat(graffitiBuilder.formatClientsInfo(12)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString().substring(0, 4)) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(12)); + } + + @Test + public void formatClientInfo_shouldRenderClClientNameAnd1stCommitByte_whenElInfoNotAvailable() { + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + + // 8: LH1bBU0f + assertThat(graffitiBuilder.formatClientsInfo(11)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString().substring(0, 2)) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(8)); + assertThat(graffitiBuilder.formatClientsInfo(8)) + .isEqualTo( + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString().substring(0, 2)) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(8)); + } + + @Test + public void formatClientInfo_shouldRenderClClientName_whenElInfoNotAvailable() { + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + + // 4: LHBU + assertThat(graffitiBuilder.formatClientsInfo(7)) + .isEqualTo(TEKU_CLIENT_VERSION.code()) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(4)); + assertThat(graffitiBuilder.formatClientsInfo(4)) + .isEqualTo(TEKU_CLIENT_VERSION.code()) + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isLessThan(4)); + } + + @Test + public void formatClientInfo_shouldSkipClientsInfo_whenNotEnoughSpaceAndElInfoNotAvailable() { + graffitiBuilder.onExecutionClientVersion(ClientVersion.UNKNOWN); + + // Empty + assertThat(graffitiBuilder.formatClientsInfo(3)) + .isEqualTo("") + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isEqualTo(0)); + assertThat(graffitiBuilder.formatClientsInfo(0)) + .isEqualTo("") + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isEqualTo(0)); + assertThat(graffitiBuilder.formatClientsInfo(-1)) + .isEqualTo("") + .satisfies(s -> assertThat(s.getBytes(StandardCharsets.UTF_8).length).isEqualTo(0)); + } + @ParameterizedTest(name = "code={0}") @MethodSource("getClientCodes") public void formatClientInfo_shouldHandleBadCodeOnClientNamesAndFullCommit( @@ -452,4 +562,46 @@ private static Stream getBuildGraffitiFixtures() { Arguments.of(DISABLED, Optional.of(UTF_8_GRAFFITI_4), UTF_8_GRAFFITI_4), Arguments.of(DISABLED, Optional.of(ASCII_GRAFFITI_20), ASCII_GRAFFITI_20)); } + + private static Stream getBuildGraffitiFixturesElInfoNa() { + return Stream.of( + Arguments.of( + AUTO, + Optional.empty(), + TEKU_CLIENT_VERSION.code() + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString()), + Arguments.of( + AUTO, + Optional.of("small"), + "small " + + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString()), + Arguments.of( + AUTO, + Optional.of(UTF_8_GRAFFITI_4), + UTF_8_GRAFFITI_4 + + " " + + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString()), + Arguments.of( + AUTO, + Optional.of(ASCII_GRAFFITI_20), + ASCII_GRAFFITI_20 + + " " + + TEKU_CLIENT_VERSION.code() + + TEKU_CLIENT_VERSION.commit().toUnprefixedHexString().substring(0, 2)), + Arguments.of(CLIENT_CODES, Optional.empty(), TEKU_CLIENT_VERSION.code()), + Arguments.of(CLIENT_CODES, Optional.of("small"), "small " + TEKU_CLIENT_VERSION.code()), + Arguments.of( + CLIENT_CODES, + Optional.of(UTF_8_GRAFFITI_4), + UTF_8_GRAFFITI_4 + " " + TEKU_CLIENT_VERSION.code()), + Arguments.of( + CLIENT_CODES, + Optional.of(ASCII_GRAFFITI_20), + ASCII_GRAFFITI_20 + " " + TEKU_CLIENT_VERSION.code()), + Arguments.of(DISABLED, Optional.empty(), ""), + Arguments.of(DISABLED, Optional.of("small"), "small"), + Arguments.of(DISABLED, Optional.of(UTF_8_GRAFFITI_4), UTF_8_GRAFFITI_4), + Arguments.of(DISABLED, Optional.of(ASCII_GRAFFITI_20), ASCII_GRAFFITI_20)); + } } diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProvider.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProvider.java index a0645df48cc..27d268ea630 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProvider.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProvider.java @@ -70,16 +70,26 @@ private void updateClientInfo() { final ClientVersion executionClientVersion = clientVersions.get(0); updateVersionIfNeeded(executionClientVersion); }) - .finish(ex -> LOG.debug("Exception while calling engine_getClientVersion", ex)); + .finish( + ex -> { + LOG.debug("Exception while calling engine_getClientVersion", ex); + updateVersionIfNeeded(ClientVersion.UNKNOWN); + }); } private synchronized void updateVersionIfNeeded(final ClientVersion executionClientVersion) { if (executionClientVersion.equals(this.executionClientVersion.get())) { return; } - EVENT_LOG.logExecutionClientVersion( - executionClientVersion.name(), executionClientVersion.version()); - this.executionClientVersion.set(executionClientVersion); - executionClientVersionChannel.onExecutionClientVersion(executionClientVersion); + if (!executionClientVersion.equals(ClientVersion.UNKNOWN)) { + EVENT_LOG.logExecutionClientVersion( + executionClientVersion.name(), executionClientVersion.version()); + } + // push UNKNOWN forward only when it's set for the first time + if (!executionClientVersion.equals(ClientVersion.UNKNOWN) + || this.executionClientVersion.get() == null) { + this.executionClientVersion.set(executionClientVersion); + executionClientVersionChannel.onExecutionClientVersion(executionClientVersion); + } } } diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProviderTest.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProviderTest.java index 0e09e90f4c5..19cc20c2dc8 100644 --- a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProviderTest.java +++ b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/ExecutionClientVersionProviderTest.java @@ -16,6 +16,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,28 +44,29 @@ public void setUp() { } @Test - public void doesNotPublishExecutionClientVersionIfFailed() { + public void pushUnknownExecutionClientVersionInChannel_whenFailed() { when(executionLayerChannel.engineGetClientVersion(any())) .thenReturn(SafeFuture.failedFuture(new IllegalStateException("oopsy"))); new ExecutionClientVersionProvider( executionLayerChannel, publishChannel, ClientVersion.UNKNOWN); - verify(publishChannel, never()).onExecutionClientVersion(any()); + verify(publishChannel).onExecutionClientVersion(ClientVersion.UNKNOWN); } @Test - public void doesNotTryToUpdateExecutionClientVersionIfElHasNotBeenUnavailable() { + public void doesNotTryToUpdateExecutionClientVersion_whenElHasNotBeenUnavailable() { final ExecutionClientVersionProvider executionClientVersionProvider = new ExecutionClientVersionProvider( executionLayerChannel, publishChannel, ClientVersion.UNKNOWN); executionClientVersionProvider.onAvailabilityUpdated(true); // EL called only one time - verify(executionLayerChannel, times(1)).engineGetClientVersion(any()); + verify(executionLayerChannel).engineGetClientVersion(any()); + verify(publishChannel).onExecutionClientVersion(executionClientVersion); } @Test - public void updatesExecutionClientVersionWhenElIsAvailableAfterBeingUnavailable() { + public void updatesExecutionClientVersion_whenElIsAvailableAfterBeingUnavailable() { final ExecutionClientVersionProvider executionClientVersionProvider = new ExecutionClientVersionProvider( executionLayerChannel, publishChannel, ClientVersion.UNKNOWN); @@ -93,4 +95,37 @@ public void updatesExecutionClientVersionWhenElIsAvailableAfterBeingUnavailable( // ignoring the same verify(publishChannel, times(2)).onExecutionClientVersion(any()); } + + @Test + public void doesNotPushUnknownVersionInChannel_whenELIsDownInTheMiddle() { + final ExecutionClientVersionProvider executionClientVersionProvider = + new ExecutionClientVersionProvider( + executionLayerChannel, publishChannel, ClientVersion.UNKNOWN); + + // Good start + verify(publishChannel).onExecutionClientVersion(executionClientVersion); + reset(publishChannel); + + // EL is broken + when(executionLayerChannel.engineGetClientVersion(any())) + .thenReturn(SafeFuture.failedFuture(new IllegalStateException("oopsy"))); + + executionClientVersionProvider.onAvailabilityUpdated(false); + executionClientVersionProvider.onAvailabilityUpdated(true); + // EL called two times + verify(executionLayerChannel, times(2)).engineGetClientVersion(any()); + // UNKNOWN version is not pushed in the channel + verify(publishChannel, never()).onExecutionClientVersion(any()); + + // EL is back + when(executionLayerChannel.engineGetClientVersion(any())) + .thenReturn(SafeFuture.completedFuture(List.of(executionClientVersion))); + + executionClientVersionProvider.onAvailabilityUpdated(false); + executionClientVersionProvider.onAvailabilityUpdated(true); + // EL called 3 times + verify(executionLayerChannel, times(3)).engineGetClientVersion(any()); + // Version is the same, not pushed in the channel + verify(publishChannel, never()).onExecutionClientVersion(any()); + } } From 3172d2f8d07ded4cb1dfa364074d30feacfa7d2d Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 9 Apr 2024 16:47:42 +0400 Subject: [PATCH 02/70] Adopt new c-kzg 'das' branch --- gradle/versions.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 0606defd8d8..9b374fb9b5e 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -34,7 +34,7 @@ dependencyManagement { dependency 'io.libp2p:jvm-libp2p:1.1.0-RELEASE' dependency 'tech.pegasys:jblst:0.3.11' - dependency 'tech.pegasys:jc-kzg-4844:1.0.0' + dependency 'tech.pegasys:jc-kzg-4844:das-test' dependency 'org.hdrhistogram:HdrHistogram:2.1.12' From 4603466c16a082954b68484b708f7de627583db5 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 9 Apr 2024 17:05:05 +0400 Subject: [PATCH 03/70] Disable negative test which segfaults --- .../kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index 8f4895eff1e..eaa5aedb70c 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -35,6 +35,7 @@ import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -251,6 +252,7 @@ public void incorrectTrustedSetupFilesShouldThrow(final String filename) { assertThat(cause.getMessage()).contains("Failed to parse trusted setup file"); } + @Disabled("das kzg version crashes") @Test public void monomialTrustedSetupFilesShouldThrow() { final KZGException kzgException = From bf17838466d033c92c6c6f0107a7e694cdd91beb Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 9 Apr 2024 18:39:48 +0400 Subject: [PATCH 04/70] Add sanity CKZG4844JNI test --- .../test/java/tech/pegasys/teku/kzg/CKZG4844Test.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index eaa5aedb70c..81fa96e3fad 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -22,6 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.google.common.collect.Streams; +import ethereum.ckzg4844.CKZG4844JNI; import ethereum.ckzg4844.CKZGException; import ethereum.ckzg4844.CKZGException.CKZGError; import java.math.BigInteger; @@ -130,6 +131,14 @@ public void testVerifyingEmptyBatch() { assertThat(CKZG.verifyBlobKzgProofBatch(List.of(), List.of(), List.of())).isTrue(); } + @Test + public void testExtendingBlob() { + Bytes blob = getSampleBlob(); + Bytes extBlob = Bytes.wrap(CKZG4844JNI.computeCells(blob.toArrayUnsafe())); + assertThat(extBlob.size()).isEqualTo(blob.size() * 2); + assertThat(extBlob.slice(0, blob.size())).isEqualTo(blob); + } + @Test public void testComputingAndVerifyingSingleProof() { final Bytes blob = getSampleBlob(); From 6a5b007401c971f4c661bee1bf931a4b33163ed4 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Wed, 10 Apr 2024 12:09:12 +0200 Subject: [PATCH 05/70] Block publishing performance (#8181) --- .../ValidatorApiHandlerIntegrationTest.java | 5 +- .../validator/coordinator/BlockFactory.java | 7 +- .../coordinator/BlockFactoryDeneb.java | 9 ++- .../coordinator/BlockFactoryPhase0.java | 11 ++- .../BlockOperationSelectorFactory.java | 26 ++++--- .../MilestoneBasedBlockFactory.java | 16 +++- .../coordinator/ValidatorApiHandler.java | 33 ++++---- .../publisher/AbstractBlockPublisher.java | 30 +++++--- .../coordinator/publisher/BlockPublisher.java | 5 +- .../publisher/BlockPublisherDeneb.java | 13 +++- .../publisher/BlockPublisherPhase0.java | 13 +++- .../MilestoneBasedBlockPublisher.java | 6 +- .../coordinator/AbstractBlockFactoryTest.java | 12 ++- .../coordinator/BlockFactoryDenebTest.java | 3 +- .../BlockOperationSelectorFactoryTest.java | 31 ++++++-- .../coordinator/ValidatorApiHandlerTest.java | 11 +-- .../publisher/AbstractBlockPublisherTest.java | 70 ++++++++++++----- ...cutionLayerBlockProductionManagerImpl.java | 10 ++- ...onLayerBlockProductionManagerImplTest.java | 30 ++++++-- ...uctionAndPublishingPerformanceFactory.java | 57 ++++++++++++++ .../trackers/BlockProductionPerformance.java | 7 -- .../BlockProductionPerformanceFactory.java | 38 ---------- .../BlockProductionPerformanceImpl.java | 20 ++--- .../trackers/BlockPublishingPerformance.java | 52 +++++++++++++ .../BlockPublishingPerformanceImpl.java | 76 +++++++++++++++++++ .../java/tech/pegasys/teku/spec/Spec.java | 8 +- .../ExecutionLayerBlockProductionManager.java | 11 ++- .../infrastructure/logging/EventLogger.java | 9 +++ .../infrastructure/metrics/MetricsConfig.java | 47 ++++++++---- .../beaconchain/BeaconChainController.java | 15 ++-- .../teku/cli/options/MetricsOptions.java | 26 +++++-- 31 files changed, 517 insertions(+), 190 deletions(-) create mode 100644 ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionAndPublishingPerformanceFactory.java delete mode 100644 ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceFactory.java create mode 100644 ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformance.java create mode 100644 ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformanceImpl.java diff --git a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java index 7bdce7d7fd6..7ed4010551c 100644 --- a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java +++ b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java @@ -29,7 +29,7 @@ import tech.pegasys.teku.beacon.sync.events.SyncState; import tech.pegasys.teku.beacon.sync.events.SyncStateProvider; import tech.pegasys.teku.beacon.sync.events.SyncStateTracker; -import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory; +import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionAndPublishingPerformanceFactory; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem; import tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricUtils; @@ -121,7 +121,8 @@ public class ValidatorApiHandlerIntegrationTest { syncCommitteeMessagePool, syncCommitteeContributionPool, syncCommitteeSubscriptionManager, - new BlockProductionPerformanceFactory(new SystemTimeProvider(), true, 0)); + new BlockProductionAndPublishingPerformanceFactory( + new SystemTimeProvider(), __ -> UInt64.ZERO, true, 0, 0)); @BeforeEach public void setup() { diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java index d5d88c9174d..766ce6051b9 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java @@ -18,6 +18,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; @@ -37,7 +38,9 @@ SafeFuture createUnsignedBlock( Optional requestedBuilderBoostFactor, BlockProductionPerformance blockProductionPerformance); - SafeFuture unblindSignedBlockIfBlinded(SignedBeaconBlock maybeBlindedBlock); + SafeFuture unblindSignedBlockIfBlinded( + SignedBeaconBlock maybeBlindedBlock, BlockPublishingPerformance blockPublishingPerformance); - List createBlobSidecars(SignedBlockContainer blockContainer); + List createBlobSidecars( + SignedBlockContainer blockContainer, BlockPublishingPerformance blockPublishingPerformance); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDeneb.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDeneb.java index c2fef608644..ee35b270828 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDeneb.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDeneb.java @@ -18,6 +18,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; @@ -70,8 +71,12 @@ public SafeFuture createUnsignedBlock( } @Override - public List createBlobSidecars(final SignedBlockContainer blockContainer) { - return operationSelector.createBlobSidecarsSelector().apply(blockContainer); + public List createBlobSidecars( + final SignedBlockContainer blockContainer, + final BlockPublishingPerformance blockPublishingPerformance) { + return operationSelector + .createBlobSidecarsSelector(blockPublishingPerformance) + .apply(blockContainer); } private BlockContents createBlockContents( diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java index db8875e0af9..cc05e2885fa 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java @@ -22,6 +22,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; @@ -94,16 +95,20 @@ private BlockContainerAndMetaData beaconBlockAndStateToBlockContainerAndMetaData @Override public SafeFuture unblindSignedBlockIfBlinded( - final SignedBeaconBlock maybeBlindedBlock) { + final SignedBeaconBlock maybeBlindedBlock, + final BlockPublishingPerformance blockPublishingPerformance) { if (maybeBlindedBlock.isBlinded()) { return spec.unblindSignedBeaconBlock( - maybeBlindedBlock.getSignedBlock(), operationSelector.createBlockUnblinderSelector()); + maybeBlindedBlock.getSignedBlock(), + operationSelector.createBlockUnblinderSelector(blockPublishingPerformance)); } return SafeFuture.completedFuture(maybeBlindedBlock); } @Override - public List createBlobSidecars(final SignedBlockContainer blockContainer) { + public List createBlobSidecars( + final SignedBlockContainer blockContainer, + final BlockPublishingPerformance blockPublishingPerformance) { return Collections.emptyList(); } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java index 610069b46c1..5e7e8bce826 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactory.java @@ -25,6 +25,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -409,7 +410,8 @@ private SafeFuture builderSetKzgCommitments( .thenAccept(bodyBuilder::blobKzgCommitments); } - public Consumer createBlockUnblinderSelector() { + public Consumer createBlockUnblinderSelector( + final BlockPublishingPerformance blockPublishingPerformance) { return bodyUnblinder -> { final SignedBeaconBlock signedBlindedBlock = bodyUnblinder.getSignedBlindedBeaconBlock(); @@ -432,7 +434,7 @@ public Consumer createBlockUnblinderSelector() { bodyUnblinder.setExecutionPayloadSupplier( () -> executionLayerBlockProductionManager - .getUnblindedPayload(signedBlindedBlock) + .getUnblindedPayload(signedBlindedBlock, blockPublishingPerformance) .thenApply(BuilderPayload::getExecutionPayload)); } }; @@ -442,7 +444,8 @@ public Function> createBlobsBundleSelector( return block -> getCachedExecutionBlobsBundle(block.getSlot()); } - public Function> createBlobSidecarsSelector() { + public Function> createBlobSidecarsSelector( + final BlockPublishingPerformance blockPublishingPerformance) { return blockContainer -> { final UInt64 slot = blockContainer.getSlot(); final SignedBeaconBlock block = blockContainer.getSignedBlock(); @@ -479,12 +482,17 @@ public Function> createBlobSidecarsSelec proofs = blockContainer.getKzgProofs().orElseThrow(); } - return IntStream.range(0, blobs.size()) - .mapToObj( - index -> - miscHelpersDeneb.constructBlobSidecar( - block, UInt64.valueOf(index), blobs.get(index), proofs.get(index))) - .toList(); + final List blobSidecars = + IntStream.range(0, blobs.size()) + .mapToObj( + index -> + miscHelpersDeneb.constructBlobSidecar( + block, UInt64.valueOf(index), blobs.get(index), proofs.get(index))) + .toList(); + + blockPublishingPerformance.blobSidecarsPrepared(); + + return blobSidecars; }; } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java index 7ba58d5bd5d..637e34bcf5c 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java @@ -22,6 +22,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; @@ -84,15 +85,22 @@ public SafeFuture createUnsignedBlock( @Override public SafeFuture unblindSignedBlockIfBlinded( - final SignedBeaconBlock maybeBlindedBlock) { + final SignedBeaconBlock maybeBlindedBlock, + final BlockPublishingPerformance blockPublishingPerformance) { final SpecMilestone milestone = getMilestone(maybeBlindedBlock.getSlot()); - return registeredFactories.get(milestone).unblindSignedBlockIfBlinded(maybeBlindedBlock); + return registeredFactories + .get(milestone) + .unblindSignedBlockIfBlinded(maybeBlindedBlock, blockPublishingPerformance); } @Override - public List createBlobSidecars(final SignedBlockContainer blockContainer) { + public List createBlobSidecars( + final SignedBlockContainer blockContainer, + BlockPublishingPerformance blockPublishingPerformance) { final SpecMilestone milestone = getMilestone(blockContainer.getSlot()); - return registeredFactories.get(milestone).createBlobSidecars(blockContainer); + return registeredFactories + .get(milestone) + .createBlobSidecars(blockContainer, blockPublishingPerformance); } private SpecMilestone getMilestone(final UInt64 slot) { diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index 2f8b20bc6fe..f8d3f183d86 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -20,7 +20,6 @@ import static tech.pegasys.teku.infrastructure.metrics.Validator.DutyType.ATTESTATION_PRODUCTION; import static tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricUtils.startTimer; import static tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricsSteps.CREATE; -import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_SLOT; import com.google.common.annotations.VisibleForTesting; @@ -54,8 +53,9 @@ import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuty; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeSelectionProof; +import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionAndPublishingPerformanceFactory; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; -import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -116,7 +116,8 @@ public class ValidatorApiHandler implements ValidatorApiChannel { */ private static final int DUTY_EPOCH_TOLERANCE = 1; - private final BlockProductionPerformanceFactory blockProductionPerformanceFactory; + private final BlockProductionAndPublishingPerformanceFactory + blockProductionAndPublishingPerformanceFactory; private final ChainDataProvider chainDataProvider; private final NodeDataProvider nodeDataProvider; private final CombinedChainDataClient combinedChainDataClient; @@ -160,8 +161,10 @@ public ValidatorApiHandler( final SyncCommitteeMessagePool syncCommitteeMessagePool, final SyncCommitteeContributionPool syncCommitteeContributionPool, final SyncCommitteeSubscriptionManager syncCommitteeSubscriptionManager, - final BlockProductionPerformanceFactory blockProductionPerformanceFactory) { - this.blockProductionPerformanceFactory = blockProductionPerformanceFactory; + final BlockProductionAndPublishingPerformanceFactory + blockProductionAndPublishingPerformanceFactory) { + this.blockProductionAndPublishingPerformanceFactory = + blockProductionAndPublishingPerformanceFactory; this.chainDataProvider = chainDataProvider; this.nodeDataProvider = nodeDataProvider; this.combinedChainDataClient = combinedChainDataClient; @@ -323,21 +326,14 @@ public SafeFuture> createUnsignedBlock( return NodeSyncingException.failedFuture(); } final BlockProductionPerformance blockProductionPerformance = - blockProductionPerformanceFactory.create(slot); + blockProductionAndPublishingPerformanceFactory.createForProduction(slot); return forkChoiceTrigger .prepareForBlockProduction(slot, blockProductionPerformance) .thenCompose( __ -> combinedChainDataClient.getStateForBlockProduction( slot, forkChoiceTrigger.isForkChoiceOverrideLateBlockEnabled())) - .thenPeek( - maybeState -> { - maybeState.ifPresent( - state -> - blockProductionPerformance.slotTime( - () -> secondsToMillis(spec.computeTimeAtSlot(state, slot)))); - blockProductionPerformance.getStateAtSlot(); - }) + .thenPeek(__ -> blockProductionPerformance.getStateAtSlot()) .thenCompose( blockSlotState -> createBlock( @@ -630,13 +626,18 @@ private SafeFuture processAggregateAndProof( public SafeFuture sendSignedBlock( final SignedBlockContainer maybeBlindedBlockContainer, final BroadcastValidationLevel broadcastValidationLevel) { + final BlockPublishingPerformance blockPublishingPerformance = + blockProductionAndPublishingPerformanceFactory.createForPublishing( + maybeBlindedBlockContainer.getSlot()); return blockPublisher - .sendSignedBlock(maybeBlindedBlockContainer, broadcastValidationLevel) + .sendSignedBlock( + maybeBlindedBlockContainer, broadcastValidationLevel, blockPublishingPerformance) .exceptionally( ex -> { final String reason = getRootCauseMessage(ex); return SendSignedBlockResult.rejected(reason); - }); + }) + .alwaysRun(blockPublishingPerformance::complete); } @Override diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisher.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisher.java index f0be29bc0b5..ee70f300bb4 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisher.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisher.java @@ -19,6 +19,7 @@ import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -55,18 +56,19 @@ public AbstractBlockPublisher( @Override public SafeFuture sendSignedBlock( final SignedBlockContainer blockContainer, - final BroadcastValidationLevel broadcastValidationLevel) { + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { return blockFactory - .unblindSignedBlockIfBlinded(blockContainer.getSignedBlock()) + .unblindSignedBlockIfBlinded(blockContainer.getSignedBlock(), blockPublishingPerformance) .thenPeek(performanceTracker::saveProducedBlock) .thenCompose( signedBlock -> { // creating blob sidecars after unblinding the block to ensure in the blinded flow we // will have the cached builder payload final List blobSidecars = - blockFactory.createBlobSidecars(blockContainer); + blockFactory.createBlobSidecars(blockContainer, blockPublishingPerformance); return gossipAndImportUnblindedSignedBlockAndBlobSidecars( - signedBlock, blobSidecars, broadcastValidationLevel); + signedBlock, blobSidecars, broadcastValidationLevel, blockPublishingPerformance); }) .thenCompose(result -> calculateResult(blockContainer, result)); } @@ -75,13 +77,15 @@ public SafeFuture sendSignedBlock( gossipAndImportUnblindedSignedBlockAndBlobSidecars( final SignedBeaconBlock block, final List blobSidecars, - final BroadcastValidationLevel broadcastValidationLevel) { + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { if (broadcastValidationLevel == BroadcastValidationLevel.NOT_REQUIRED) { // when broadcast validation is disabled, we can publish the block (and blob sidecars) // immediately and then import - publishBlockAndBlobSidecars(block, blobSidecars); - return importBlockAndBlobSidecars(block, blobSidecars, broadcastValidationLevel); + publishBlockAndBlobSidecars(block, blobSidecars, blockPublishingPerformance); + return importBlockAndBlobSidecars( + block, blobSidecars, broadcastValidationLevel, blockPublishingPerformance); } // when broadcast validation is enabled, we need to wait for the validation to complete before @@ -89,14 +93,15 @@ public SafeFuture sendSignedBlock( final SafeFuture blockImportAndBroadcastValidationResults = - importBlockAndBlobSidecars(block, blobSidecars, broadcastValidationLevel); + importBlockAndBlobSidecars( + block, blobSidecars, broadcastValidationLevel, blockPublishingPerformance); blockImportAndBroadcastValidationResults .thenCompose(BlockImportAndBroadcastValidationResults::broadcastValidationResult) .thenAccept( broadcastValidationResult -> { if (broadcastValidationResult == BroadcastValidationResult.SUCCESS) { - publishBlockAndBlobSidecars(block, blobSidecars); + publishBlockAndBlobSidecars(block, blobSidecars, blockPublishingPerformance); LOG.debug("Block (and blob sidecars) publishing initiated"); } else { LOG.warn( @@ -118,10 +123,13 @@ public SafeFuture sendSignedBlock( abstract SafeFuture importBlockAndBlobSidecars( SignedBeaconBlock block, List blobSidecars, - BroadcastValidationLevel broadcastValidationLevel); + BroadcastValidationLevel broadcastValidationLevel, + BlockPublishingPerformance blockPublishingPerformance); abstract void publishBlockAndBlobSidecars( - SignedBeaconBlock block, List blobSidecars); + SignedBeaconBlock block, + List blobSidecars, + BlockPublishingPerformance blockPublishingPerformance); private SafeFuture calculateResult( final SignedBlockContainer maybeBlindedBlockContainer, diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisher.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisher.java index 33efc17b03b..0a861dd8fc8 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisher.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisher.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.validator.coordinator.publisher; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; @@ -21,5 +22,7 @@ /** Used to publish blocks (unblinded and blinded) and blob sidecars */ public interface BlockPublisher { SafeFuture sendSignedBlock( - SignedBlockContainer blockContainer, BroadcastValidationLevel broadcastValidationLevel); + SignedBlockContainer blockContainer, + BroadcastValidationLevel broadcastValidationLevel, + BlockPublishingPerformance blockPublishingPerformance); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherDeneb.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherDeneb.java index d0038341f7b..2eee9a561d9 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherDeneb.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherDeneb.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.validator.coordinator.publisher; import java.util.List; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; @@ -51,16 +52,22 @@ public BlockPublisherDeneb( SafeFuture importBlockAndBlobSidecars( final SignedBeaconBlock block, final List blobSidecars, - final BroadcastValidationLevel broadcastValidationLevel) { + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { // provide blobs for the block before importing it blockBlobSidecarsTrackersPool.onCompletedBlockAndBlobSidecars(block, blobSidecars); - return blockImportChannel.importBlock(block, broadcastValidationLevel); + return blockImportChannel + .importBlock(block, broadcastValidationLevel) + .thenPeek(__ -> blockPublishingPerformance.blockImportCompleted()); } @Override void publishBlockAndBlobSidecars( - final SignedBeaconBlock block, final List blobSidecars) { + final SignedBeaconBlock block, + final List blobSidecars, + BlockPublishingPerformance blockPublishingPerformance) { blockGossipChannel.publishBlock(block); blobSidecarGossipChannel.publishBlobSidecars(blobSidecars); + blockPublishingPerformance.blockAndBlobSidecarsPublishingInitiated(); } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherPhase0.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherPhase0.java index 36dbb881309..21ca2b370b1 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherPhase0.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherPhase0.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.validator.coordinator.publisher; import java.util.List; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; @@ -42,13 +43,19 @@ public BlockPublisherPhase0( SafeFuture importBlockAndBlobSidecars( final SignedBeaconBlock block, final List blobSidecars, - final BroadcastValidationLevel broadcastValidationLevel) { - return blockImportChannel.importBlock(block, broadcastValidationLevel); + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { + return blockImportChannel + .importBlock(block, broadcastValidationLevel) + .thenPeek(__ -> blockPublishingPerformance.blockImportCompleted()); } @Override void publishBlockAndBlobSidecars( - final SignedBeaconBlock block, final List blobSidecars) { + final SignedBeaconBlock block, + final List blobSidecars, + final BlockPublishingPerformance blockPublishingPerformance) { blockGossipChannel.publishBlock(block); + blockPublishingPerformance.blockPublishingInitiated(); } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java index 1d6137d7b6d..2dabbf3ebac 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java @@ -17,6 +17,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Supplier; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; @@ -79,10 +80,11 @@ public MilestoneBasedBlockPublisher( @Override public SafeFuture sendSignedBlock( final SignedBlockContainer blockContainer, - final BroadcastValidationLevel broadcastValidationLevel) { + final BroadcastValidationLevel broadcastValidationLevel, + BlockPublishingPerformance blockPublishingPerformance) { final SpecMilestone blockMilestone = spec.atSlot(blockContainer.getSlot()).getMilestone(); return registeredPublishers .get(blockMilestone) - .sendSignedBlock(blockContainer, broadcastValidationLevel); + .sendSignedBlock(blockContainer, broadcastValidationLevel, blockPublishingPerformance); } } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/AbstractBlockFactoryTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/AbstractBlockFactoryTest.java index c4c72093395..bdcd0f95592 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/AbstractBlockFactoryTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/AbstractBlockFactoryTest.java @@ -36,6 +36,7 @@ import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.bls.BLSSignatureVerifier; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -289,11 +290,13 @@ protected SignedBeaconBlock assertBlockUnblinded( final BlockFactory blockFactory = createBlockFactory(spec); // no need to prepare blobs bundle when only testing block unblinding - when(executionLayer.getUnblindedPayload(blindedBlock)) + when(executionLayer.getUnblindedPayload(blindedBlock, BlockPublishingPerformance.NOOP)) .thenReturn(SafeFuture.completedFuture(executionPayload)); final SignedBeaconBlock unblindedBlock = - blockFactory.unblindSignedBlockIfBlinded(blindedBlock).join(); + blockFactory + .unblindSignedBlockIfBlinded(blindedBlock, BlockPublishingPerformance.NOOP) + .join(); assertThat(unblindedBlock).isNotNull(); assertThat(unblindedBlock.hashTreeRoot()).isEqualTo(blindedBlock.hashTreeRoot()); @@ -302,7 +305,7 @@ protected SignedBeaconBlock assertBlockUnblinded( .isEqualTo(Optional.empty()); if (blindedBlock.isBlinded()) { - verify(executionLayer).getUnblindedPayload(blindedBlock); + verify(executionLayer).getUnblindedPayload(blindedBlock, BlockPublishingPerformance.NOOP); assertThat(unblindedBlock.getMessage().getBody().getOptionalExecutionPayload()) .hasValue(executionPayload); } else { @@ -364,7 +367,8 @@ protected BlockAndBlobSidecars createBlockAndBlobSidecars( when(executionLayer.getCachedUnblindedPayload(signedBlockContainer.getSlot())) .thenReturn(builderPayload); - final List blobSidecars = blockFactory.createBlobSidecars(signedBlockContainer); + final List blobSidecars = + blockFactory.createBlobSidecars(signedBlockContainer, BlockPublishingPerformance.NOOP); return new BlockAndBlobSidecars(signedBlockContainer, blobSidecars); } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDenebTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDenebTest.java index 13d834c83d9..67103b9e60e 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDenebTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockFactoryDenebTest.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.stream.IntStream; import org.junit.jupiter.api.Test; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.ssz.SszCollection; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.spec.Spec; @@ -97,7 +98,7 @@ void unblindSignedBlock_shouldUnblindBeaconBlock() { final SignedBeaconBlock unblindedBlock = assertBlockUnblinded(blindedBlock, spec); - verify(executionLayer).getUnblindedPayload(unblindedBlock); + verify(executionLayer).getUnblindedPayload(unblindedBlock, BlockPublishingPerformance.NOOP); assertThat(unblindedBlock.isBlinded()).isFalse(); assertThat(unblindedBlock).isEqualTo(expectedUnblindedBlock); diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java index 7efce12065c..8dddab4934d 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/BlockOperationSelectorFactoryTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem; import tech.pegasys.teku.infrastructure.ssz.SszData; @@ -657,10 +658,10 @@ void shouldUnblindSignedBlindedBeaconBlock() { final CapturingBeaconBlockUnblinder blockUnblinder = new CapturingBeaconBlockUnblinder(spec.getGenesisSchemaDefinitions(), blindedSignedBlock); - when(executionLayer.getUnblindedPayload(blindedSignedBlock)) + when(executionLayer.getUnblindedPayload(blindedSignedBlock, BlockPublishingPerformance.NOOP)) .thenReturn(SafeFuture.completedFuture(randomExecutionPayload)); - factory.createBlockUnblinderSelector().accept(blockUnblinder); + factory.createBlockUnblinderSelector(BlockPublishingPerformance.NOOP).accept(blockUnblinder); assertThat(blockUnblinder.executionPayload).isCompletedWithValue(randomExecutionPayload); } @@ -789,7 +790,9 @@ void shouldCreateBlobSidecarsForBlockContents() { MiscHelpersDeneb.required(spec.atSlot(signedBlockContents.getSlot()).miscHelpers()); final List blobSidecars = - factory.createBlobSidecarsSelector().apply(signedBlockContents); + factory + .createBlobSidecarsSelector(BlockPublishingPerformance.NOOP) + .apply(signedBlockContents); final SszList expectedBlobs = signedBlockContents.getBlobs().orElseThrow(); final SszList expectedProofs = signedBlockContents.getKzgProofs().orElseThrow(); @@ -833,7 +836,11 @@ void shouldFailCreatingBlobSidecarsIfBuilderBlobsBundleCommitmentsRootIsNotConsi dataStructureUtil.randomExecutionPayload(), Optional.of(blobsBundle)); - assertThatThrownBy(() -> factory.createBlobSidecarsSelector().apply(signedBlindedBeaconBlock)) + assertThatThrownBy( + () -> + factory + .createBlobSidecarsSelector(BlockPublishingPerformance.NOOP) + .apply(signedBlindedBeaconBlock)) .isInstanceOf(IllegalStateException.class) .hasMessage( "Commitments in the builder BlobsBundle don't match the commitments in the block"); @@ -854,7 +861,11 @@ void shouldFailCreatingBlobSidecarsIfBuilderBlobsBundleProofsIsNotConsistent() { dataStructureUtil.randomExecutionPayload(), Optional.of(blobsBundle)); - assertThatThrownBy(() -> factory.createBlobSidecarsSelector().apply(signedBlindedBeaconBlock)) + assertThatThrownBy( + () -> + factory + .createBlobSidecarsSelector(BlockPublishingPerformance.NOOP) + .apply(signedBlindedBeaconBlock)) .isInstanceOf(IllegalStateException.class) .hasMessage( "The number of blobs in BlobsBundle doesn't match the number of commitments in the block"); @@ -875,7 +886,11 @@ void shouldFailCreatingBlobSidecarsIfBuilderBlobsBundleBlobsIsNotConsistent() { dataStructureUtil.randomExecutionPayload(), Optional.of(blobsBundle)); - assertThatThrownBy(() -> factory.createBlobSidecarsSelector().apply(signedBlindedBeaconBlock)) + assertThatThrownBy( + () -> + factory + .createBlobSidecarsSelector(BlockPublishingPerformance.NOOP) + .apply(signedBlindedBeaconBlock)) .isInstanceOf(IllegalStateException.class) .hasMessage( "The number of proofs in BlobsBundle doesn't match the number of commitments in the block"); @@ -899,7 +914,9 @@ void shouldCreateBlobSidecarsForBlindedBlock() { Optional.of(blobsBundle)); final List blobSidecars = - factory.createBlobSidecarsSelector().apply(signedBlindedBeaconBlock); + factory + .createBlobSidecarsSelector(BlockPublishingPerformance.NOOP) + .apply(signedBlindedBeaconBlock); final SszList expectedBlobs = blobsBundle.getBlobs(); final SszList expectedProofs = blobsBundle.getProofs(); diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index aa6587d5e15..40eb7c723fa 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -66,8 +66,8 @@ import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuties; import tech.pegasys.teku.ethereum.json.types.validator.ProposerDuty; import tech.pegasys.teku.ethereum.json.types.validator.SyncCommitteeDuties; +import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionAndPublishingPerformanceFactory; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; -import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem; import tech.pegasys.teku.infrastructure.metrics.Validator.ValidatorDutyMetricUtils; @@ -172,8 +172,9 @@ class ValidatorApiHandlerTest { private final ArgumentCaptor> blobSidecarsCaptor2 = ArgumentCaptor.forClass(List.class); - private final BlockProductionPerformanceFactory blockProductionPerformanceFactory = - new BlockProductionPerformanceFactory(StubTimeProvider.withTimeInMillis(0), false, 0); + private final BlockProductionAndPublishingPerformanceFactory blockProductionPerformanceFactory = + new BlockProductionAndPublishingPerformanceFactory( + StubTimeProvider.withTimeInMillis(0), __ -> ZERO, false, 0, 0); private Spec spec; private UInt64 epochStartSlot; @@ -220,7 +221,7 @@ public void setUp() { when(chainDataClient.isOptimisticBlock(any())).thenReturn(false); doAnswer(invocation -> SafeFuture.completedFuture(invocation.getArgument(0))) .when(blockFactory) - .unblindSignedBlockIfBlinded(any()); + .unblindSignedBlockIfBlinded(any(), any()); when(proposersDataManager.updateValidatorRegistrations(any(), any())) .thenReturn(SafeFuture.COMPLETE); } @@ -1337,7 +1338,7 @@ private void setupDeneb() { .toList(); }) .when(blockFactory) - .createBlobSidecars(any()); + .createBlobSidecars(any(), any()); } private SafeFuture prepareBlockImportResult( diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java index 9ffde68cde2..7fd4c543dad 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java @@ -23,6 +23,7 @@ import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; @@ -59,9 +60,10 @@ public class AbstractBlockPublisherTest { @BeforeEach public void setUp() { - when(blockFactory.unblindSignedBlockIfBlinded(signedBlock)) + when(blockFactory.unblindSignedBlockIfBlinded(signedBlock, BlockPublishingPerformance.NOOP)) .thenReturn(SafeFuture.completedFuture(signedBlock)); - when(blockFactory.createBlobSidecars(signedBlockContents)).thenReturn(blobSidecars); + when(blockFactory.createBlobSidecars(signedBlockContents, BlockPublishingPerformance.NOOP)) + .thenReturn(blobSidecars); } @Test @@ -69,7 +71,10 @@ public void setUp() { sendSignedBlock_shouldPublishImmediatelyAndImportWhenBroadcastValidationIsNotRequired() { when(blockPublisher.importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.NOT_REQUIRED)) + signedBlock, + blobSidecars, + BroadcastValidationLevel.NOT_REQUIRED, + BlockPublishingPerformance.NOOP)) .thenReturn( SafeFuture.completedFuture( new BlockImportAndBroadcastValidationResults( @@ -77,20 +82,29 @@ public void setUp() { assertThatSafeFuture( blockPublisher.sendSignedBlock( - signedBlockContents, BroadcastValidationLevel.NOT_REQUIRED)) + signedBlockContents, + BroadcastValidationLevel.NOT_REQUIRED, + BlockPublishingPerformance.NOOP)) .isCompletedWithValue(SendSignedBlockResult.success(signedBlockContents.getRoot())); - verify(blockPublisher).publishBlockAndBlobSidecars(signedBlock, blobSidecars); + verify(blockPublisher) + .publishBlockAndBlobSidecars(signedBlock, blobSidecars, BlockPublishingPerformance.NOOP); verify(blockPublisher) .importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.NOT_REQUIRED); + signedBlock, + blobSidecars, + BroadcastValidationLevel.NOT_REQUIRED, + BlockPublishingPerformance.NOOP); } @Test public void sendSignedBlock_shouldWaitToPublishWhenBroadcastValidationIsSpecified() { final SafeFuture validationResult = new SafeFuture<>(); when(blockPublisher.importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION)) + signedBlock, + blobSidecars, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP)) .thenReturn( SafeFuture.completedFuture( new BlockImportAndBroadcastValidationResults( @@ -99,19 +113,26 @@ public void sendSignedBlock_shouldWaitToPublishWhenBroadcastValidationIsSpecifie final SafeFuture sendSignedBlockResult = blockPublisher.sendSignedBlock( - signedBlockContents, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION); + signedBlockContents, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP); assertThatSafeFuture(sendSignedBlockResult).isNotCompleted(); verify(blockPublisher) .importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION); + signedBlock, + blobSidecars, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP); - verify(blockPublisher, never()).publishBlockAndBlobSidecars(signedBlock, blobSidecars); + verify(blockPublisher, never()) + .publishBlockAndBlobSidecars(signedBlock, blobSidecars, BlockPublishingPerformance.NOOP); validationResult.complete(BroadcastValidationResult.SUCCESS); - verify(blockPublisher).publishBlockAndBlobSidecars(signedBlock, blobSidecars); + verify(blockPublisher) + .publishBlockAndBlobSidecars(signedBlock, blobSidecars, BlockPublishingPerformance.NOOP); assertThatSafeFuture(sendSignedBlockResult) .isCompletedWithValue(SendSignedBlockResult.success(signedBlockContents.getRoot())); } @@ -120,7 +141,10 @@ public void sendSignedBlock_shouldWaitToPublishWhenBroadcastValidationIsSpecifie public void sendSignedBlock_shouldNotPublishWhenBroadcastValidationFails() { final SafeFuture validationResult = new SafeFuture<>(); when(blockPublisher.importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION)) + signedBlock, + blobSidecars, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP)) .thenReturn( SafeFuture.completedFuture( new BlockImportAndBroadcastValidationResults( @@ -129,19 +153,26 @@ public void sendSignedBlock_shouldNotPublishWhenBroadcastValidationFails() { final SafeFuture sendSignedBlockResult = blockPublisher.sendSignedBlock( - signedBlockContents, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION); + signedBlockContents, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP); assertThatSafeFuture(sendSignedBlockResult).isNotCompleted(); verify(blockPublisher) .importBlockAndBlobSidecars( - signedBlock, blobSidecars, BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION); + signedBlock, + blobSidecars, + BroadcastValidationLevel.CONSENSUS_AND_EQUIVOCATION, + BlockPublishingPerformance.NOOP); - verify(blockPublisher, never()).publishBlockAndBlobSidecars(signedBlock, blobSidecars); + verify(blockPublisher, never()) + .publishBlockAndBlobSidecars(signedBlock, blobSidecars, BlockPublishingPerformance.NOOP); validationResult.complete(BroadcastValidationResult.CONSENSUS_FAILURE); - verify(blockPublisher, never()).publishBlockAndBlobSidecars(signedBlock, blobSidecars); + verify(blockPublisher, never()) + .publishBlockAndBlobSidecars(signedBlock, blobSidecars, BlockPublishingPerformance.NOOP); assertThatSafeFuture(sendSignedBlockResult) .isCompletedWithValue( SendSignedBlockResult.rejected("FAILED_BROADCAST_VALIDATION: CONSENSUS_FAILURE")); @@ -160,12 +191,15 @@ public BlockPublisherTest( SafeFuture importBlockAndBlobSidecars( final SignedBeaconBlock block, final List blobSidecars, - final BroadcastValidationLevel broadcastValidationLevel) { + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { return null; } @Override void publishBlockAndBlobSidecars( - final SignedBeaconBlock block, final List blobSidecars) {} + final SignedBeaconBlock block, + final List blobSidecars, + final BlockPublishingPerformance blockPublishingPerformance) {} } } diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java index d388e3d86c4..6f047f38d3f 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImpl.java @@ -19,6 +19,7 @@ import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.ethereum.events.SlotEventsChannel; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -136,11 +137,16 @@ public ExecutionPayloadResult initiateBlockAndBlobsProduction( } @Override - public SafeFuture getUnblindedPayload(final SignedBeaconBlock signedBeaconBlock) { + public SafeFuture getUnblindedPayload( + final SignedBeaconBlock signedBeaconBlock, + final BlockPublishingPerformance blockPublishingPerformance) { return executionLayerChannel .builderGetPayload(signedBeaconBlock, this::getCachedPayloadResult) .thenPeek( - builderPayload -> builderResultCache.put(signedBeaconBlock.getSlot(), builderPayload)); + builderPayload -> { + builderResultCache.put(signedBeaconBlock.getSlot(), builderPayload); + blockPublishingPerformance.builderGetPayload(); + }); } @Override diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImplTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImplTest.java index c2d8928bf74..3a7159eccc2 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImplTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ExecutionLayerBlockProductionManagerImplTest.java @@ -34,6 +34,7 @@ import tech.pegasys.teku.ethereum.executionclient.schema.Response; import tech.pegasys.teku.ethereum.executionlayer.ExecutionLayerManagerImpl.Source; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.logging.EventLogger; import tech.pegasys.teku.infrastructure.metrics.StubMetricsSystem; @@ -142,13 +143,17 @@ public void preDeneb_builderOffline() throws Exception { final SafeFuture unblindedPayload = blockProductionManager.getUnblindedPayload( - dataStructureUtil.randomSignedBlindedBeaconBlock(slot)); + dataStructureUtil.randomSignedBlindedBeaconBlock(slot), + BlockPublishingPerformance.NOOP); assertThat(unblindedPayload.get()).isEqualTo(localPayload); // wrong slot, we will hit builder client by this call final SignedBeaconBlock signedBlindedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(slot.plus(1)); - assertThatThrownBy(() -> blockProductionManager.getUnblindedPayload(signedBlindedBeaconBlock)); + assertThatThrownBy( + () -> + blockProductionManager.getUnblindedPayload( + signedBlindedBeaconBlock, BlockPublishingPerformance.NOOP)); verify(builderClient).getPayload(signedBlindedBeaconBlock); } @@ -195,7 +200,9 @@ public void preDeneb_builderOnline() throws Exception { final ExecutionPayload payload = prepareBuilderGetPayloadResponse(signedBlindedBeaconBlock); // we expect result from the builder - assertThat(blockProductionManager.getUnblindedPayload(signedBlindedBeaconBlock)) + assertThat( + blockProductionManager.getUnblindedPayload( + signedBlindedBeaconBlock, BlockPublishingPerformance.NOOP)) .isCompletedWithValue(payload); // we expect both builder and local engine have been called @@ -241,7 +248,10 @@ public void preDeneb_noBuilder() throws Exception { // we will hit builder client by this call final SignedBeaconBlock signedBlindedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(slot); - assertThatThrownBy(() -> blockProductionManager.getUnblindedPayload(signedBlindedBeaconBlock)); + assertThatThrownBy( + () -> + blockProductionManager.getUnblindedPayload( + signedBlindedBeaconBlock, BlockPublishingPerformance.NOOP)); verify(builderClient).getPayload(signedBlindedBeaconBlock); } @@ -304,7 +314,8 @@ public void postDeneb_builderOffline() throws Exception { final SafeFuture unblindedPayload = blockProductionManager.getUnblindedPayload( - dataStructureUtil.randomSignedBlindedBeaconBlock(slot)); + dataStructureUtil.randomSignedBlindedBeaconBlock(slot), + BlockPublishingPerformance.NOOP); assertThat(unblindedPayload.get()).isEqualTo(localPayload); verifyNoMoreInteractions(builderClient); @@ -356,7 +367,9 @@ public void postDeneb_builderOnline() throws Exception { prepareBuilderGetPayloadResponseWithBlobs(signedBlindedBeaconBlock); // we expect result from the builder - assertThat(blockProductionManager.getUnblindedPayload(signedBlindedBeaconBlock)) + assertThat( + blockProductionManager.getUnblindedPayload( + signedBlindedBeaconBlock, BlockPublishingPerformance.NOOP)) .isCompletedWithValue(payloadAndBlobsBundle); // we expect both builder and local engine have been called @@ -407,7 +420,10 @@ public void postDeneb_noBuilder() throws Exception { // we will hit builder client by this call final SignedBeaconBlock signedBlindedBeaconBlock = dataStructureUtil.randomSignedBlindedBeaconBlock(slot); - assertThatThrownBy(() -> blockProductionManager.getUnblindedPayload(signedBlindedBeaconBlock)); + assertThatThrownBy( + () -> + blockProductionManager.getUnblindedPayload( + signedBlindedBeaconBlock, BlockPublishingPerformance.NOOP)); verify(builderClient).getPayload(signedBlindedBeaconBlock); } diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionAndPublishingPerformanceFactory.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionAndPublishingPerformanceFactory.java new file mode 100644 index 00000000000..7d196542cad --- /dev/null +++ b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionAndPublishingPerformanceFactory.java @@ -0,0 +1,57 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.ethereum.performance.trackers; + +import java.util.function.Function; +import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class BlockProductionAndPublishingPerformanceFactory { + private final TimeProvider timeProvider; + private final boolean enabled; + private final int lateProductionEventThreshold; + private final int latePublishingEventThreshold; + private final Function slotTimeCalculator; + + public BlockProductionAndPublishingPerformanceFactory( + final TimeProvider timeProvider, + final Function slotTimeCalculator, + final boolean enabled, + final int lateProductionEventThreshold, + final int latePublishingEventThreshold) { + this.timeProvider = timeProvider; + this.slotTimeCalculator = slotTimeCalculator; + this.enabled = enabled; + this.lateProductionEventThreshold = lateProductionEventThreshold; + this.latePublishingEventThreshold = latePublishingEventThreshold; + } + + public BlockProductionPerformance createForProduction(final UInt64 slot) { + if (enabled) { + return new BlockProductionPerformanceImpl( + timeProvider, slot, slotTimeCalculator.apply(slot), lateProductionEventThreshold); + } else { + return BlockProductionPerformance.NOOP; + } + } + + public BlockPublishingPerformance createForPublishing(final UInt64 slot) { + if (enabled) { + return new BlockPublishingPerformanceImpl( + timeProvider, slot, slotTimeCalculator.apply(slot), latePublishingEventThreshold); + } else { + return BlockPublishingPerformance.NOOP; + } + } +} diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformance.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformance.java index ed01ee21774..b0d2bcb5e70 100644 --- a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformance.java +++ b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformance.java @@ -13,9 +13,6 @@ package tech.pegasys.teku.ethereum.performance.trackers; -import java.util.function.Supplier; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - /** * This is high level flow, some steps are executed only if builder flow take place * @@ -62,8 +59,6 @@ public interface BlockProductionPerformance { BlockProductionPerformance NOOP = new BlockProductionPerformance() { - @Override - public void slotTime(final Supplier slotTimeSupplier) {} @Override public void complete() {} @@ -102,8 +97,6 @@ public void stateTransition() {} public void stateHashing() {} }; - void slotTime(Supplier slotTimeSupplier); - void complete(); void prepareOnTick(); diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceFactory.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceFactory.java deleted file mode 100644 index 5a0b408f033..00000000000 --- a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceFactory.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2023 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.performance.trackers; - -import tech.pegasys.teku.infrastructure.time.TimeProvider; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - -public class BlockProductionPerformanceFactory { - private final TimeProvider timeProvider; - private final boolean enabled; - private final int lateEventThreshold; - - public BlockProductionPerformanceFactory( - final TimeProvider timeProvider, final boolean enabled, final int lateEventThreshold) { - this.timeProvider = timeProvider; - this.enabled = enabled; - this.lateEventThreshold = lateEventThreshold; - } - - public BlockProductionPerformance create(final UInt64 slot) { - if (enabled) { - return new BlockProductionPerformanceImpl(timeProvider, slot, lateEventThreshold); - } else { - return BlockProductionPerformance.NOOP; - } - } -} diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceImpl.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceImpl.java index bd6da142a4f..185d762998e 100644 --- a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceImpl.java +++ b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockProductionPerformanceImpl.java @@ -13,7 +13,6 @@ package tech.pegasys.teku.ethereum.performance.trackers; -import java.util.function.Supplier; import tech.pegasys.teku.infrastructure.logging.EventLogger; import tech.pegasys.teku.infrastructure.time.PerformanceTracker; import tech.pegasys.teku.infrastructure.time.TimeProvider; @@ -22,28 +21,23 @@ public class BlockProductionPerformanceImpl implements BlockProductionPerformance { private final PerformanceTracker performanceTracker; private final UInt64 slot; - private UInt64 slotTime = UInt64.ZERO; + private final UInt64 slotTime; private final int lateThreshold; - public BlockProductionPerformanceImpl( - final TimeProvider timeProvider, final UInt64 slot, final int lateThreshold) { + BlockProductionPerformanceImpl( + final TimeProvider timeProvider, + final UInt64 slot, + final UInt64 slotTime, + final int lateThreshold) { this.performanceTracker = new PerformanceTracker(timeProvider); this.lateThreshold = lateThreshold; this.slot = slot; + this.slotTime = slotTime; performanceTracker.addEvent("start"); } - @Override - public void slotTime(final Supplier slotTimeSupplier) { - this.slotTime = slotTimeSupplier.get(); - } - @Override public void complete() { - if (slotTime.isZero()) { - // we haven't managed to calculate slot time, something wrong happened - return; - } final UInt64 completionTime = performanceTracker.addEvent(COMPLETE_LABEL); final boolean isLateEvent = completionTime.minusMinZero(slotTime).isGreaterThan(lateThreshold); performanceTracker.report( diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformance.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformance.java new file mode 100644 index 00000000000..fe9e468ec08 --- /dev/null +++ b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformance.java @@ -0,0 +1,52 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.ethereum.performance.trackers; + +public interface BlockPublishingPerformance { + String COMPLETE_LABEL = "complete"; + + BlockPublishingPerformance NOOP = + new BlockPublishingPerformance() { + + @Override + public void complete() {} + + @Override + public void builderGetPayload() {} + + @Override + public void blobSidecarsPrepared() {} + + @Override + public void blockAndBlobSidecarsPublishingInitiated() {} + + @Override + public void blockPublishingInitiated() {} + + @Override + public void blockImportCompleted() {} + }; + + void blockAndBlobSidecarsPublishingInitiated(); + + void blockPublishingInitiated(); + + void builderGetPayload(); + + void blobSidecarsPrepared(); + + void blockImportCompleted(); + + void complete(); +} diff --git a/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformanceImpl.java b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformanceImpl.java new file mode 100644 index 00000000000..83367e434ab --- /dev/null +++ b/ethereum/performance-trackers/src/main/java/tech/pegasys/teku/ethereum/performance/trackers/BlockPublishingPerformanceImpl.java @@ -0,0 +1,76 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.ethereum.performance.trackers; + +import tech.pegasys.teku.infrastructure.logging.EventLogger; +import tech.pegasys.teku.infrastructure.time.PerformanceTracker; +import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class BlockPublishingPerformanceImpl implements BlockPublishingPerformance { + private final PerformanceTracker performanceTracker; + private final UInt64 slot; + private final UInt64 slotTime; + private final int lateThreshold; + + BlockPublishingPerformanceImpl( + final TimeProvider timeProvider, + final UInt64 slot, + final UInt64 slotTime, + final int lateThreshold) { + this.performanceTracker = new PerformanceTracker(timeProvider); + this.lateThreshold = lateThreshold; + this.slot = slot; + this.slotTime = slotTime; + performanceTracker.addEvent("start"); + } + + @Override + public void complete() { + final UInt64 completionTime = performanceTracker.addEvent(COMPLETE_LABEL); + final boolean isLateEvent = completionTime.minusMinZero(slotTime).isGreaterThan(lateThreshold); + performanceTracker.report( + slotTime, + isLateEvent, + (event, stepDuration) -> {}, + totalDuration -> {}, + (totalDuration, timings) -> + EventLogger.EVENT_LOG.slowBlockPublishingEvent(slot, totalDuration, timings)); + } + + @Override + public void builderGetPayload() { + performanceTracker.addEvent("builder_get_payload"); + } + + @Override + public void blobSidecarsPrepared() { + performanceTracker.addEvent("blob_sidecars_prepared"); + } + + @Override + public void blockAndBlobSidecarsPublishingInitiated() { + performanceTracker.addEvent("block_and_blob_sidecars_publishing_initiated"); + } + + @Override + public void blockPublishingInitiated() { + performanceTracker.addEvent("block_publishing_initiated"); + } + + @Override + public void blockImportCompleted() { + performanceTracker.addEvent("block_import_completed"); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 695442c4cd5..ee2add97ad5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -428,8 +428,12 @@ public UInt64 computeEpochAtSlot(final UInt64 slot) { return atSlot(slot).miscHelpers().computeEpochAtSlot(slot); } - public UInt64 computeTimeAtSlot(BeaconState state, UInt64 slot) { - return atSlot(slot).miscHelpers().computeTimeAtSlot(state.getGenesisTime(), slot); + public UInt64 computeTimeAtSlot(final BeaconState state, final UInt64 slot) { + return computeTimeAtSlot(state.getGenesisTime(), slot); + } + + public UInt64 computeTimeAtSlot(final UInt64 genesisTime, final UInt64 slot) { + return atSlot(slot).miscHelpers().computeTimeAtSlot(genesisTime, slot); } public Bytes computeSigningRoot(BeaconBlock block, Bytes32 domain) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerBlockProductionManager.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerBlockProductionManager.java index 4b5ce61f8f1..7f52c0dd619 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerBlockProductionManager.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerBlockProductionManager.java @@ -15,6 +15,7 @@ import java.util.Optional; import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformance; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -59,7 +60,8 @@ public ExecutionPayloadResult initiateBlockAndBlobsProduction( @Override public SafeFuture getUnblindedPayload( - final SignedBeaconBlock signedBeaconBlock) { + final SignedBeaconBlock signedBeaconBlock, + final BlockPublishingPerformance blockPublishingPerformance) { return SafeFuture.completedFuture(null); } @@ -112,11 +114,12 @@ ExecutionPayloadResult initiateBlockAndBlobsProduction( */ Optional getCachedPayloadResult(UInt64 slot); - SafeFuture getUnblindedPayload(SignedBeaconBlock signedBeaconBlock); + SafeFuture getUnblindedPayload( + SignedBeaconBlock signedBeaconBlock, BlockPublishingPerformance blockPublishingPerformance); /** - * Requires {@link #getUnblindedPayload(SignedBeaconBlock)} to have been called first in order for - * a value to be present + * Requires {@link #getUnblindedPayload(SignedBeaconBlock, BlockPublishingPerformance)} to have + * been called first in order for a value to be present */ Optional getCachedUnblindedPayload(UInt64 slot); } diff --git a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java index c3ec074464e..7935fb12f74 100644 --- a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java +++ b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/EventLogger.java @@ -303,6 +303,15 @@ public void slowBlockProductionEvent( warn(slowBlockProductionLog, Color.YELLOW); } + public void slowBlockPublishingEvent( + final UInt64 slot, final UInt64 totalProcessingDuration, final String timings) { + final String slowBlockPublishingLog = + String.format( + "Slow Block Publishing *** Slot: %s %s total: %sms", + slot, timings, totalProcessingDuration); + warn(slowBlockPublishingLog, Color.YELLOW); + } + public void executionLayerStubEnabled() { error( "Execution Layer Stub has been enabled! This is UNSAFE! You WILL fail to produce blocks and may follow an invalid chain.", diff --git a/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/MetricsConfig.java b/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/MetricsConfig.java index b98dd1847fc..83d921f596f 100644 --- a/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/MetricsConfig.java +++ b/infrastructure/metrics/src/main/java/tech/pegasys/teku/infrastructure/metrics/MetricsConfig.java @@ -40,8 +40,9 @@ public class MetricsConfig { public static final int DEFAULT_METRICS_PUBLICATION_INTERVAL = 60; public static final boolean DEFAULT_BLOCK_PERFORMANCE_ENABLED = true; public static final boolean DEFAULT_TICK_PERFORMANCE_ENABLED = false; - public static final boolean DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_ENABLED = true; + public static final boolean DEFAULT_BLOCK_PRODUCTION_AND_PUBLISHING_PERFORMANCE_ENABLED = true; public static final int DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_WARNING_THRESHOLD = 300; + public static final int DEFAULT_BLOCK_PUBLISHING_PERFORMANCE_WARNING_THRESHOLD = 1000; public static final boolean DEFAULT_BLOB_SIDECARS_STORAGE_COUNTERS_ENABLED = false; private final boolean metricsEnabled; @@ -55,8 +56,9 @@ public class MetricsConfig { private final boolean blockPerformanceEnabled; private final boolean tickPerformanceEnabled; private final boolean blobSidecarsStorageCountersEnabled; - private final boolean blockProductionPerformanceEnabled; + private final boolean blockProductionAndPublishingPerformanceEnabled; private final int blockProductionPerformanceWarningThreshold; + private final int blockPublishingPerformanceWarningThreshold; private MetricsConfig( final boolean metricsEnabled, @@ -70,8 +72,9 @@ private MetricsConfig( final boolean blockPerformanceEnabled, final boolean tickPerformanceEnabled, final boolean blobSidecarsStorageCountersEnabled, - final boolean blockProductionPerformanceEnabled, - final int blockProductionPerformanceWarningThreshold) { + final boolean blockProductionAndPublishingPerformanceEnabled, + final int blockProductionPerformanceWarningThreshold, + final int blockPublishingPerformanceWarningThreshold) { this.metricsEnabled = metricsEnabled; this.metricsPort = metricsPort; this.metricsInterface = metricsInterface; @@ -83,8 +86,10 @@ private MetricsConfig( this.blockPerformanceEnabled = blockPerformanceEnabled; this.tickPerformanceEnabled = tickPerformanceEnabled; this.blobSidecarsStorageCountersEnabled = blobSidecarsStorageCountersEnabled; - this.blockProductionPerformanceEnabled = blockProductionPerformanceEnabled; + this.blockProductionAndPublishingPerformanceEnabled = + blockProductionAndPublishingPerformanceEnabled; this.blockProductionPerformanceWarningThreshold = blockProductionPerformanceWarningThreshold; + this.blockPublishingPerformanceWarningThreshold = blockPublishingPerformanceWarningThreshold; } public static MetricsConfigBuilder builder() { @@ -127,14 +132,18 @@ public boolean isBlockPerformanceEnabled() { return blockPerformanceEnabled; } - public boolean isBlockProductionPerformanceEnabled() { - return blockProductionPerformanceEnabled; + public boolean isBlockProductionAndPublishingPerformanceEnabled() { + return blockProductionAndPublishingPerformanceEnabled; } public int getBlockProductionPerformanceWarningThreshold() { return blockProductionPerformanceWarningThreshold; } + public int getBlockPublishingPerformanceWarningThreshold() { + return blockPublishingPerformanceWarningThreshold; + } + public boolean isTickPerformanceEnabled() { return tickPerformanceEnabled; } @@ -154,10 +163,12 @@ public static final class MetricsConfigBuilder { private int metricsPublishInterval = DEFAULT_METRICS_PUBLICATION_INTERVAL; private int idleTimeoutSeconds = DEFAULT_IDLE_TIMEOUT_SECONDS; private boolean blockPerformanceEnabled = DEFAULT_BLOCK_PERFORMANCE_ENABLED; - private boolean blockProductionPerformanceEnabled = - DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_ENABLED; + private boolean blockProductionAndPublishingPerformanceEnabled = + DEFAULT_BLOCK_PRODUCTION_AND_PUBLISHING_PERFORMANCE_ENABLED; private int blockProductionPerformanceWarningThreshold = DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_WARNING_THRESHOLD; + private int blockPublishingPerformanceWarningThreshold = + DEFAULT_BLOCK_PUBLISHING_PERFORMANCE_WARNING_THRESHOLD; private boolean tickPerformanceEnabled = DEFAULT_TICK_PERFORMANCE_ENABLED; private boolean blobSidecarsStorageCountersEnabled = DEFAULT_BLOB_SIDECARS_STORAGE_COUNTERS_ENABLED; @@ -221,9 +232,10 @@ public MetricsConfigBuilder blockPerformanceEnabled(final boolean blockPerforman return this; } - public MetricsConfigBuilder blockProductionPerformanceEnabled( - final boolean blockProductionPerformanceEnabled) { - this.blockProductionPerformanceEnabled = blockProductionPerformanceEnabled; + public MetricsConfigBuilder blockProductionAndPublishingPerformanceEnabled( + final boolean blockProductionAndPublishingPerformanceEnabled) { + this.blockProductionAndPublishingPerformanceEnabled = + blockProductionAndPublishingPerformanceEnabled; return this; } @@ -233,6 +245,12 @@ public MetricsConfigBuilder blockProductionPerformanceWarningThreshold( return this; } + public MetricsConfigBuilder blockPublishingPerformanceWarningThreshold( + final int blockPublishingPerformanceWarningThreshold) { + this.blockPublishingPerformanceWarningThreshold = blockPublishingPerformanceWarningThreshold; + return this; + } + public MetricsConfigBuilder tickPerformanceEnabled(final boolean tickPerformanceEnabled) { this.tickPerformanceEnabled = tickPerformanceEnabled; return this; @@ -257,8 +275,9 @@ public MetricsConfig build() { blockPerformanceEnabled, tickPerformanceEnabled, blobSidecarsStorageCountersEnabled, - blockProductionPerformanceEnabled, - blockProductionPerformanceWarningThreshold); + blockProductionAndPublishingPerformanceEnabled, + blockProductionPerformanceWarningThreshold, + blockPublishingPerformanceWarningThreshold); } } } diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index b40a16060f3..243b0e28801 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -18,6 +18,7 @@ import static tech.pegasys.teku.infrastructure.logging.StatusLogger.STATUS_LOG; import static tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory.BEACON; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.millisToSeconds; +import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ZERO; import static tech.pegasys.teku.spec.config.SpecConfig.GENESIS_SLOT; import static tech.pegasys.teku.statetransition.attestation.AggregatingAttestationPool.DEFAULT_MAXIMUM_ATTESTATION_COUNT; @@ -55,7 +56,7 @@ import tech.pegasys.teku.ethereum.execution.types.Eth1Address; import tech.pegasys.teku.ethereum.executionclient.ExecutionClientVersionChannel; import tech.pegasys.teku.ethereum.executionclient.ExecutionClientVersionProvider; -import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionPerformanceFactory; +import tech.pegasys.teku.ethereum.performance.trackers.BlockProductionAndPublishingPerformanceFactory; import tech.pegasys.teku.ethereum.pow.api.Eth1EventsChannel; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.async.AsyncRunnerFactory; @@ -924,11 +925,15 @@ public void initValidatorApiHandler() { } else { blobSidecarGossipChannel = BlobSidecarGossipChannel.NOOP; } - final BlockProductionPerformanceFactory blockProductionPerformanceFactory = - new BlockProductionPerformanceFactory( + + final BlockProductionAndPublishingPerformanceFactory blockProductionPerformanceFactory = + new BlockProductionAndPublishingPerformanceFactory( timeProvider, - beaconConfig.getMetricsConfig().isBlockProductionPerformanceEnabled(), - beaconConfig.getMetricsConfig().getBlockProductionPerformanceWarningThreshold()); + (slot) -> + secondsToMillis(spec.computeTimeAtSlot(recentChainData.getGenesisTime(), slot)), + beaconConfig.getMetricsConfig().isBlockProductionAndPublishingPerformanceEnabled(), + beaconConfig.getMetricsConfig().getBlockProductionPerformanceWarningThreshold(), + beaconConfig.getMetricsConfig().getBlockPublishingPerformanceWarningThreshold()); final ValidatorApiHandler validatorApiHandler = new ValidatorApiHandler( diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/MetricsOptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/MetricsOptions.java index a9954f16286..16c3fbafcf7 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/MetricsOptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/MetricsOptions.java @@ -113,11 +113,12 @@ public class MetricsOptions { hidden = true, showDefaultValue = Visibility.ALWAYS, paramLabel = "", - description = "Whether block production timing metrics are tracked and reported", + description = + "Whether block production and publishing timing metrics are tracked and reported", fallbackValue = "true", arity = "0..1") - private boolean blockProductionPerformanceEnabled = - MetricsConfig.DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_ENABLED; + private boolean blockProductionAndPublishingPerformanceEnabled = + MetricsConfig.DEFAULT_BLOCK_PRODUCTION_AND_PUBLISHING_PERFORMANCE_ENABLED; @Option( names = {"--Xmetrics-block-production-timing-tracking-warning-threshold"}, @@ -131,6 +132,18 @@ public class MetricsOptions { private int blockProductionPerformanceWarningThreshold = MetricsConfig.DEFAULT_BLOCK_PRODUCTION_PERFORMANCE_WARNING_THRESHOLD; + @Option( + names = {"--Xmetrics-block-publishing-timing-tracking-warning-threshold"}, + hidden = true, + showDefaultValue = Visibility.ALWAYS, + paramLabel = "", + description = + "The time (in ms) at which block publishing is to be considered 'slow'. If set to 100, block publishing taking at least 100ms would raise a warning.", + fallbackValue = "true", + arity = "0..1") + private int blockPublishingPerformanceWarningThreshold = + MetricsConfig.DEFAULT_BLOCK_PUBLISHING_PERFORMANCE_WARNING_THRESHOLD; + @Option( names = {"--Xmetrics-blob-sidecars-storage-enabled"}, hidden = true, @@ -156,9 +169,12 @@ public void configure(TekuConfiguration.Builder builder) { .blockPerformanceEnabled(blockPerformanceEnabled) .tickPerformanceEnabled(tickPerformanceEnabled) .blobSidecarsStorageCountersEnabled(blobSidecarsStorageCountersEnabled) - .blockProductionPerformanceEnabled(blockProductionPerformanceEnabled) + .blockProductionAndPublishingPerformanceEnabled( + blockProductionAndPublishingPerformanceEnabled) .blockProductionPerformanceWarningThreshold( - blockProductionPerformanceWarningThreshold)); + blockProductionPerformanceWarningThreshold) + .blockPublishingPerformanceWarningThreshold( + blockPublishingPerformanceWarningThreshold)); } private URL parseMetricsEndpointUrl() { From 2c2249ab922d7912b945e0dd81cbed36fb1129a3 Mon Sep 17 00:00:00 2001 From: Enrico Del Fante Date: Wed, 10 Apr 2024 12:54:10 +0200 Subject: [PATCH 06/70] Cleanup on block performance (#8184) --- .../java/tech/pegasys/teku/spec/Spec.java | 51 +++++++++---------- .../beaconchain/BeaconChainController.java | 3 +- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index ee2add97ad5..2638f8b21f7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -292,10 +292,10 @@ public int getSyncCommitteeSize(final UInt64 slot) { // Genesis public BeaconState initializeBeaconStateFromEth1( - Bytes32 eth1BlockHash, - UInt64 eth1Timestamp, - List deposits, - Optional payloadHeader) { + final Bytes32 eth1BlockHash, + final UInt64 eth1Timestamp, + final List deposits, + final Optional payloadHeader) { final GenesisGenerator genesisGenerator = createGenesisGenerator(); genesisGenerator.updateCandidateState(eth1BlockHash, eth1Timestamp, deposits); payloadHeader.ifPresent(genesisGenerator::updateExecutionPayloadHeader); @@ -415,7 +415,7 @@ public UInt64 getPreviousEpoch(final BeaconState state) { return atState(state).beaconStateAccessors().getPreviousEpoch(state); } - public Bytes32 getSeed(BeaconState state, UInt64 epoch, Bytes4 domainType) + public Bytes32 getSeed(final BeaconState state, final UInt64 epoch, final Bytes4 domainType) throws IllegalArgumentException { return atState(state).beaconStateAccessors().getSeed(state, epoch, domainType); } @@ -429,38 +429,35 @@ public UInt64 computeEpochAtSlot(final UInt64 slot) { } public UInt64 computeTimeAtSlot(final BeaconState state, final UInt64 slot) { - return computeTimeAtSlot(state.getGenesisTime(), slot); + return atSlot(slot).miscHelpers().computeTimeAtSlot(state.getGenesisTime(), slot); } - public UInt64 computeTimeAtSlot(final UInt64 genesisTime, final UInt64 slot) { - return atSlot(slot).miscHelpers().computeTimeAtSlot(genesisTime, slot); - } - - public Bytes computeSigningRoot(BeaconBlock block, Bytes32 domain) { + public Bytes computeSigningRoot(final BeaconBlock block, final Bytes32 domain) { return atBlock(block).miscHelpers().computeSigningRoot(block, domain); } - public Bytes computeSigningRoot(BeaconBlockHeader blockHeader, Bytes32 domain) { + public Bytes computeSigningRoot(final BeaconBlockHeader blockHeader, final Bytes32 domain) { return atSlot(blockHeader.getSlot()).miscHelpers().computeSigningRoot(blockHeader, domain); } - public Bytes computeSigningRoot(AggregateAndProof proof, Bytes32 domain) { + public Bytes computeSigningRoot(final AggregateAndProof proof, final Bytes32 domain) { return atSlot(proof.getAggregate().getData().getSlot()) .miscHelpers() .computeSigningRoot(proof, domain); } - public Bytes computeSigningRoot(UInt64 slot, Bytes32 domain) { + public Bytes computeSigningRoot(final UInt64 slot, final Bytes32 domain) { return atSlot(slot).miscHelpers().computeSigningRoot(slot, domain); } - public Bytes computeBuilderApplicationSigningRoot(UInt64 slot, Merkleizable object) { + public Bytes computeBuilderApplicationSigningRoot(final UInt64 slot, final Merkleizable object) { final MiscHelpers miscHelpers = atSlot(slot).miscHelpers(); return miscHelpers.computeSigningRoot( object, miscHelpers.computeDomain(Domain.APPLICATION_BUILDER)); } - public Bytes4 computeForkDigest(Bytes4 currentVersion, Bytes32 genesisValidatorsRoot) { + public Bytes4 computeForkDigest( + final Bytes4 currentVersion, final Bytes32 genesisValidatorsRoot) { return atForkVersion(currentVersion) .miscHelpers() .computeForkDigest(currentVersion, genesisValidatorsRoot); @@ -564,7 +561,7 @@ public UInt64 getCurrentSlotForMillis(UInt64 currentTimeMillis, UInt64 genesisTi .getCurrentSlotForMillis(currentTimeMillis, genesisTimeMillis); } - public UInt64 getCurrentSlot(ReadOnlyStore store) { + public UInt64 getCurrentSlot(final ReadOnlyStore store) { return atTime(store.getGenesisTime(), store.getTimeSeconds()) .getForkChoiceUtil() .getCurrentSlot(store); @@ -574,36 +571,38 @@ public UInt64 getCurrentEpoch(final ReadOnlyStore store) { return computeEpochAtSlot(getCurrentSlot(store)); } - public UInt64 getSlotStartTime(UInt64 slotNumber, UInt64 genesisTime) { + public UInt64 getSlotStartTime(final UInt64 slotNumber, final UInt64 genesisTime) { return atSlot(slotNumber).getForkChoiceUtil().getSlotStartTime(slotNumber, genesisTime); } - public UInt64 getSlotStartTimeMillis(UInt64 slotNumber, UInt64 genesisTimeMillis) { + public UInt64 getSlotStartTimeMillis(final UInt64 slotNumber, final UInt64 genesisTimeMillis) { return atSlot(slotNumber) .getForkChoiceUtil() .getSlotStartTimeMillis(slotNumber, genesisTimeMillis); } public Optional getAncestor( - ReadOnlyForkChoiceStrategy forkChoiceStrategy, Bytes32 root, UInt64 slot) { + final ReadOnlyForkChoiceStrategy forkChoiceStrategy, final Bytes32 root, final UInt64 slot) { return forGetAncestor(forkChoiceStrategy, root, slot) .getForkChoiceUtil() .getAncestor(forkChoiceStrategy, root, slot); } public NavigableMap getAncestors( - ReadOnlyForkChoiceStrategy forkChoiceStrategy, - Bytes32 root, - UInt64 startSlot, - UInt64 step, - UInt64 count) { + final ReadOnlyForkChoiceStrategy forkChoiceStrategy, + final Bytes32 root, + final UInt64 startSlot, + final UInt64 step, + final UInt64 count) { return forGetAncestor(forkChoiceStrategy, root, startSlot) .getForkChoiceUtil() .getAncestors(forkChoiceStrategy, root, startSlot, step, count); } public NavigableMap getAncestorsOnFork( - ReadOnlyForkChoiceStrategy forkChoiceStrategy, Bytes32 root, UInt64 startSlot) { + final ReadOnlyForkChoiceStrategy forkChoiceStrategy, + final Bytes32 root, + final UInt64 startSlot) { return forGetAncestor(forkChoiceStrategy, root, startSlot) .getForkChoiceUtil() .getAncestorsOnFork(forkChoiceStrategy, root, startSlot); diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 243b0e28801..3ae8b310fb9 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -929,8 +929,7 @@ public void initValidatorApiHandler() { final BlockProductionAndPublishingPerformanceFactory blockProductionPerformanceFactory = new BlockProductionAndPublishingPerformanceFactory( timeProvider, - (slot) -> - secondsToMillis(spec.computeTimeAtSlot(recentChainData.getGenesisTime(), slot)), + (slot) -> secondsToMillis(recentChainData.computeTimeAtSlot(slot)), beaconConfig.getMetricsConfig().isBlockProductionAndPublishingPerformanceEnabled(), beaconConfig.getMetricsConfig().getBlockProductionPerformanceWarningThreshold(), beaconConfig.getMetricsConfig().getBlockPublishingPerformanceWarningThreshold()); From 008ab850b03bf1e4f37968d1c2f573cd03805891 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Wed, 10 Apr 2024 17:45:42 +0400 Subject: [PATCH 07/70] Implement new KZG methods --- .../java/tech/pegasys/teku/kzg/CKZG4844.java | 47 ++++++++++++++- .../tech/pegasys/teku/kzg/CKZG4844Utils.java | 16 ++++- .../main/java/tech/pegasys/teku/kzg/Cell.java | 21 +++++++ .../tech/pegasys/teku/kzg/CellAndProof.java | 7 +++ .../java/tech/pegasys/teku/kzg/CellID.java | 14 +++++ .../tech/pegasys/teku/kzg/CellWithID.java | 11 ++++ .../main/java/tech/pegasys/teku/kzg/KZG.java | 54 ++++++++++++++++- .../tech/pegasys/teku/kzg/KZGException.java | 4 ++ .../java/tech/pegasys/teku/kzg/KZGProof.java | 17 +++++- .../tech/pegasys/teku/kzg/CKZG4844Test.java | 58 +++++++++++++++---- 10 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index 02152135f7d..db073d4f391 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -14,12 +14,18 @@ package tech.pegasys.teku.kzg; import ethereum.ckzg4844.CKZG4844JNI; + import java.util.List; import java.util.Optional; +import java.util.stream.IntStream; + +import ethereum.ckzg4844.CellsAndProofs; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; + /** * Wrapper around jc-kzg-4844 * @@ -49,7 +55,9 @@ private CKZG4844() { } } - /** Only one trusted setup at a time can be loaded. */ + /** + * Only one trusted setup at a time can be loaded. + */ @Override public synchronized void loadTrustedSetup(final String trustedSetupFile) throws KZGException { if (loadedTrustedSetupFile.isPresent() @@ -145,4 +153,41 @@ public KZGProof computeBlobKzgProof(final Bytes blob, final KZGCommitment kzgCom "Failed to compute KZG proof for blob with commitment " + kzgCommitment, ex); } } + + @Override + public List computeCells(Bytes blob) { + byte[] cellBytes = CKZG4844JNI.computeCells(blob.toArrayUnsafe()); + return Cell.splitBytes(Bytes.wrap(cellBytes)); + } + + @Override + public List computeCellsAndProofs(Bytes blob) { + CellsAndProofs cellsAndProofs = CKZG4844JNI.computeCellsAndProofs(blob.toArrayUnsafe()); + List cells = Cell.splitBytes(Bytes.wrap(cellsAndProofs.getCells())); + List proofs = KZGProof.splitBytes(Bytes.wrap(cellsAndProofs.getProofs())); + if (cells.size() != proofs.size()) throw new KZGException("Cells and proofs size differ"); + return IntStream.range(0, cells.size()) + .mapToObj(i -> new CellAndProof(cells.get(i), proofs.get(i))) + .toList(); + } + + @Override + public boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof) { + return CKZG4844JNI.verifyCellProof( + commitment.toArrayUnsafe(), + cellWithID.id().id().longValue(), + cellWithID.cell().bytes().toArrayUnsafe(), + proof.toArrayUnsafe()); + } + + @Override + public List recoverCells(List cells) { + long[] cellIds = cells.stream().mapToLong(c -> c.id().id().longValue()).toArray(); + byte[] cellBytes = CKZG4844Utils.flattenBytes( + cells.stream().map(c -> c.cell().bytes()).toList(), + cells.size() * BYTES_PER_CELL + ); + byte[] recovered = CKZG4844JNI.recoverCells(cellIds, cellBytes); + return Cell.splitBytes(Bytes.wrap(recovered)); + } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java index 92a32240b04..2b5da271f5d 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java @@ -24,10 +24,14 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; +import java.util.stream.IntStream; + import org.apache.tuweni.bytes.Bytes; import tech.pegasys.teku.infrastructure.http.UrlSanitizer; import tech.pegasys.teku.infrastructure.io.resource.ResourceLoader; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; + class CKZG4844Utils { private static final int MAX_BYTES_TO_FLATTEN = 100_663_296; // ~100.66 MB or 768 blobs @@ -56,6 +60,16 @@ public static byte[] flattenG2Points(final List g2Points) { return flattenBytes(g2Points, CKZG4844JNI.BYTES_PER_G2 * g2Points.size()); } + static List bytesChunked(Bytes bytes, int chunkSize) { + if (bytes.size() % chunkSize != 0) { + throw new IllegalArgumentException("Invalid bytes size: " + bytes.size()); + } + return IntStream.range(0, bytes.size() / chunkSize) + .map(i -> i * chunkSize) + .mapToObj(startIdx -> bytes.slice(startIdx, chunkSize)) + .toList(); + } + public static TrustedSetup parseTrustedSetupFile(final String trustedSetupFile) throws IOException { final String sanitizedTrustedSetup = UrlSanitizer.sanitizePotentialUrl(trustedSetupFile); @@ -90,7 +104,7 @@ public static TrustedSetup parseTrustedSetupFile(final String trustedSetupFile) } } - private static byte[] flattenBytes(final List toFlatten, final int expectedSize) { + static byte[] flattenBytes(final List toFlatten, final int expectedSize) { return flattenBytes(toFlatten, Bytes::toArrayUnsafe, expectedSize); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java new file mode 100644 index 00000000000..f5a2949436c --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java @@ -0,0 +1,21 @@ +package tech.pegasys.teku.kzg; + +import ethereum.ckzg4844.CKZG4844JNI; +import org.apache.tuweni.bytes.Bytes; + +import java.util.List; +import java.util.stream.IntStream; + +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; + +public record Cell(Bytes bytes) { + + static Cell ZERO = new Cell(Bytes.wrap(new byte[BYTES_PER_CELL])); + + static List splitBytes(Bytes bytes) { + return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_CELL) + .stream() + .map(Cell::new) + .toList(); + } +} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java new file mode 100644 index 00000000000..518aa4c842d --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java @@ -0,0 +1,7 @@ +package tech.pegasys.teku.kzg; + +public record CellAndProof( + Cell cell, + KZGProof proof +) { +} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java new file mode 100644 index 00000000000..5b06a886df6 --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java @@ -0,0 +1,14 @@ +package tech.pegasys.teku.kzg; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public record CellID(UInt64 id) { + + static CellID fromCellColumnIndex(int idx) { + return new CellID(UInt64.valueOf(idx)); + } + + int getColumnIndex() { + return id.intValue(); + } +} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java new file mode 100644 index 00000000000..2c29ad14ec9 --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java @@ -0,0 +1,11 @@ +package tech.pegasys.teku.kzg; + +public record CellWithID( + Cell cell, + CellID id +) { + + static CellWithID fromCellAndColumn(Cell cell, int index) { + return new CellWithID(cell, CellID.fromCellColumnIndex(index)); + } +} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index f1b791b4f7c..43f0fc67127 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -14,9 +14,13 @@ package tech.pegasys.teku.kzg; import java.util.List; +import java.util.stream.Stream; + import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes48; +import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; + /** * This interface specifies all the KZG functions needed for the Deneb specification and is the * entry-point for all KZG operations in Teku. @@ -31,10 +35,12 @@ static KZG getInstance() { new KZG() { @Override - public void loadTrustedSetup(final String trustedSetupFile) throws KZGException {} + public void loadTrustedSetup(final String trustedSetupFile) throws KZGException { + } @Override - public void freeTrustedSetup() throws KZGException {} + public void freeTrustedSetup() throws KZGException { + } @Override public boolean verifyBlobKzgProof( @@ -62,6 +68,38 @@ public KZGProof computeBlobKzgProof(final Bytes blob, final KZGCommitment kzgCom throws KZGException { return KZGProof.fromBytesCompressed(Bytes48.ZERO); } + + @Override + public List computeCells(Bytes blob) { + List blobCells = Cell.splitBytes(blob); + return Stream.concat( + blobCells.stream(), + Stream.generate(() -> Cell.ZERO).limit(blobCells.size()) + ).toList(); + } + + @Override + public List computeCellsAndProofs(Bytes blob) { + return computeCells(blob) + .stream() + .map(cell -> new CellAndProof(cell, KZGProof.fromBytesCompressed(Bytes48.ZERO))) + .toList(); + } + + @Override + public boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof) { + return true; + } + + @Override + public List recoverCells(List cells) { + if (cells.size() < CELLS_PER_BLOB) + throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); + return cells.stream() + .map(CellWithID::cell) + .limit(CELLS_PER_BLOB) + .toList(); + } }; void loadTrustedSetup(String trustedSetupFile) throws KZGException; @@ -78,4 +116,16 @@ boolean verifyBlobKzgProofBatch( KZGCommitment blobToKzgCommitment(Bytes blob) throws KZGException; KZGProof computeBlobKzgProof(Bytes blob, KZGCommitment kzgCommitment) throws KZGException; + + // EIP-7594 methods + + List computeCells(Bytes blob); + + List computeCellsAndProofs(Bytes blob); + + boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof); + + // TODO veryCellProofBatch() + + List recoverCells(List cells); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGException.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGException.java index 39a376932a9..1cfe1f1a8c8 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGException.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGException.java @@ -18,4 +18,8 @@ public class KZGException extends RuntimeException { public KZGException(final String message, final Throwable cause) { super(message, cause); } + + public KZGException(final String message) { + super(message); + } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java index 88844853ffe..5f70255f5c4 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java @@ -14,8 +14,12 @@ package tech.pegasys.teku.kzg; import static com.google.common.base.Preconditions.checkArgument; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_PROOF; import ethereum.ckzg4844.CKZG4844JNI; + +import java.util.List; import java.util.Objects; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes48; @@ -29,12 +33,12 @@ public static KZGProof fromHexString(final String hexString) { public static KZGProof fromSSZBytes(final Bytes bytes) { checkArgument( - bytes.size() == CKZG4844JNI.BYTES_PER_PROOF, - "Expected " + CKZG4844JNI.BYTES_PER_PROOF + " bytes but received %s.", + bytes.size() == BYTES_PER_PROOF, + "Expected " + BYTES_PER_PROOF + " bytes but received %s.", bytes.size()); return SSZ.decode( bytes, - reader -> new KZGProof(Bytes48.wrap(reader.readFixedBytes(CKZG4844JNI.BYTES_PER_PROOF)))); + reader -> new KZGProof(Bytes48.wrap(reader.readFixedBytes(BYTES_PER_PROOF)))); } public static KZGProof fromBytesCompressed(final Bytes48 bytes) throws IllegalArgumentException { @@ -45,6 +49,13 @@ public static KZGProof fromArray(final byte[] bytes) { return fromBytesCompressed(Bytes48.wrap(bytes)); } + static List splitBytes(Bytes bytes) { + return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_PROOF) + .stream() + .map(b -> new KZGProof(Bytes48.wrap(b))) + .toList(); + } + private final Bytes48 bytesCompressed; public KZGProof(final Bytes48 bytesCompressed) { diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index 81fa96e3fad..d61c0b1ce1d 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -15,6 +15,7 @@ import static ethereum.ckzg4844.CKZG4844JNI.BLS_MODULUS; import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_BLOB; +import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; import static ethereum.ckzg4844.CKZG4844JNI.FIELD_ELEMENTS_PER_BLOB; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -25,6 +26,7 @@ import ethereum.ckzg4844.CKZG4844JNI; import ethereum.ckzg4844.CKZGException; import ethereum.ckzg4844.CKZGException.CKZGError; + import java.math.BigInteger; import java.nio.ByteOrder; import java.util.List; @@ -32,6 +34,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; + +import kotlin.ranges.IntRange; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.AfterAll; @@ -115,7 +119,7 @@ public void testComputingAndVerifyingBatchProofs() { assertThat(CKZG.verifyBlobKzgProofBatch(blobs, kzgCommitments, kzgProofs)).isTrue(); assertThat( - CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) + CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) .isFalse(); assertThat(CKZG.verifyBlobKzgProofBatch(blobs, getSampleCommitments(numberOfBlobs), kzgProofs)) .isFalse(); @@ -171,7 +175,7 @@ public void testComputingAndVerifyingBatchSingleProof() { assertThat(CKZG.verifyBlobKzgProofBatch(blobs, kzgCommitments, kzgProofs)).isTrue(); assertThat( - CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) + CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) .isFalse(); assertThat(CKZG.verifyBlobKzgProofBatch(blobs, getSampleCommitments(numberOfBlobs), kzgProofs)) .isFalse(); @@ -220,9 +224,9 @@ public void testVerifyingBatchProofsThrowsIfSizesDoesntMatch() { @ParameterizedTest(name = "blob={0}") @ValueSource( strings = { - "0x0d2024ece3e004271319699b8b00cc010628b6bc0be5457f031fb1db0afd3ff8", - "0x", - "0x925668a49d06f4" + "0x0d2024ece3e004271319699b8b00cc010628b6bc0be5457f031fb1db0afd3ff8", + "0x", + "0x925668a49d06f4" }) public void testComputingProofWithIncorrectLengthBlobDoesNotCauseSegfault(final String blobHex) { final Bytes blob = Bytes.fromHexString(blobHex); @@ -248,15 +252,15 @@ public void testComputingProofWithIncorrectLengthBlobDoesNotCauseSegfault(final @ParameterizedTest(name = "trusted_setup={0}") @ValueSource( strings = { - "broken/trusted_setup_g1_length.txt", - "broken/trusted_setup_g2_length.txt", - "broken/trusted_setup_g2_bytesize.txt" + "broken/trusted_setup_g1_length.txt", + "broken/trusted_setup_g2_length.txt", + "broken/trusted_setup_g2_bytesize.txt" }) public void incorrectTrustedSetupFilesShouldThrow(final String filename) { final Throwable cause = assertThrows( - KZGException.class, - () -> CKZG.loadTrustedSetup(TrustedSetupLoader.getTrustedSetupFile(filename))) + KZGException.class, + () -> CKZG.loadTrustedSetup(TrustedSetupLoader.getTrustedSetupFile(filename))) .getCause(); assertThat(cause.getMessage()).contains("Failed to parse trusted setup file"); } @@ -282,10 +286,44 @@ public void testInvalidLengthG2PointInNewTrustedSetup() { .hasMessage("Expected G2 point to be 96 bytes"); } + static int CELLS_PER_EXT_BLOB = CELLS_PER_BLOB; + static int CELLS_PER_ORIG_BLOB = CELLS_PER_EXT_BLOB / 2; + + @Test + public void testComputeRecoverCells() { + Bytes blob = getSampleBlob(); + List cells = CKZG.computeCells(blob); + assertThat(cells).hasSize(CELLS_PER_EXT_BLOB); + + List cellsToRecover = IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) + .mapToObj(i -> new CellWithID(cells.get(i), CellID.fromCellColumnIndex(i))) + .toList(); + + List recoveredCells = CKZG.recoverCells(cellsToRecover); + assertThat(recoveredCells).isEqualTo(cells); + } + private List getSampleBlobs(final int count) { return IntStream.range(0, count).mapToObj(__ -> getSampleBlob()).collect(Collectors.toList()); } + @Test + public void testComputeAndVerifyCellProof() { + Bytes blob = getSampleBlob(); + List cellAndProofs = CKZG.computeCellsAndProofs(blob); + KZGCommitment kzgCommitment = CKZG.blobToKzgCommitment(blob); + + for (int i = 0; i < cellAndProofs.size(); i++) { + assertThat( + CKZG.verifyCellProof(kzgCommitment, CellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), cellAndProofs.get(i).proof()) + ).isTrue(); + var invalidProof = cellAndProofs.get((i + 1) % cellAndProofs.size()).proof(); + assertThat( + CKZG.verifyCellProof(kzgCommitment, CellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), invalidProof) + ).isFalse(); + } + } + private Bytes getSampleBlob() { return IntStream.range(0, FIELD_ELEMENTS_PER_BLOB) .mapToObj(__ -> randomBLSFieldElement()) From 91b4e3d121cedfe2df059dc58b8f46f1d011a8eb Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Thu, 11 Apr 2024 08:02:59 +1000 Subject: [PATCH 08/70] Electra spec config and builder (#8183) * Electra spec config and builder There were fields missing for EIP-7251, and also some of the attestation fields. I added the entire diff from consensus-specs while I was going, it seemed easiest... Signed-off-by: Paul Harris --- .../teku/spec/config/SpecConfigElectra.java | 26 ++++ .../spec/config/SpecConfigElectraImpl.java | 106 ++++++++++++++- .../spec/config/builder/ElectraBuilder.java | 124 +++++++++++++++++- .../teku/spec/config/configs/mainnet.yaml | 6 +- .../teku/spec/config/configs/minimal.yaml | 6 +- .../spec/config/presets/mainnet/electra.yaml | 30 ++++- .../spec/config/presets/minimal/electra.yaml | 32 ++++- .../spec/config/presets/swift/electra.yaml | 35 ++++- .../spec/config/SpecConfigElectraTest.java | 15 ++- 9 files changed, 369 insertions(+), 11 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java index 73fd3e8c364..88f93843e2a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java @@ -31,6 +31,32 @@ static SpecConfigElectra required(final SpecConfig specConfig) { + specConfig.getClass().getSimpleName())); } + UInt64 getMinActivationBalance(); + + UInt64 getMaxEffectiveBalanceElectra(); + + UInt64 getPendingBalanceDepositsLimit(); + + UInt64 getPendingPartialWithdrawalsLimit(); + + UInt64 getPendingConsolidationsLimit(); + + int getWhistleblowerRewardQuotientElectra(); + + int getMinSlashingPenaltyQuotientElectra(); + + int getMaxAttesterSlashingsElectra(); + + int getMaxAttestationsElectra(); + + int getMaxConsolidations(); + + int getMaxPartialWithdrawalsPerPayload(); + + UInt64 getMinPerEpochChurnLimitElectra(); + + UInt64 getMaxPerEpochActivationExitChurnLimit(); + Bytes4 getElectraForkVersion(); UInt64 getElectraForkEpoch(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 3d0ba197b03..5bb79a1249f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -22,21 +22,60 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final Bytes4 electraForkVersion; private final UInt64 electraForkEpoch; + private final UInt64 minPerEpochChurnLimitElectra; + private final UInt64 maxPerEpochActivationExitChurnLimit; private final int maxDepositReceiptsPerPayload; private final int maxExecutionLayerExits; + private final UInt64 minActivationBalance; + private final UInt64 maxEffectiveBalanceElectra; + private final UInt64 pendingBalanceDepositsLimit; + private final UInt64 pendingPartialWithdrawalsLimit; + private final UInt64 pendingConsolidationsLimit; + private final int whistleblowerRewardQuotientElectra; + private final int minSlashingPenaltyQuotientElectra; + private final int maxPartialWithdrawalsPerPayload; + private final int maxAttesterSlashingsElectra; + private final int maxAttestationsElectra; + private final int maxConsolidations; public SpecConfigElectraImpl( final SpecConfigDeneb specConfig, final Bytes4 electraForkVersion, final UInt64 electraForkEpoch, final int maxDepositReceiptsPerPayload, - final int maxExecutionLayerExits) { + final int maxExecutionLayerExits, + final UInt64 minPerEpochChurnLimitElectra, + final UInt64 maxPerEpochActivationExitChurnLimit, + final UInt64 minActivationBalance, + final UInt64 maxEffectiveBalanceElectra, + final UInt64 pendingBalanceDepositsLimit, + final UInt64 pendingPartialWithdrawalsLimit, + final UInt64 pendingConsolidationsLimit, + final int whistleblowerRewardQuotientElectra, + final int minSlashingPenaltyQuotientElectra, + final int maxPartialWithdrawalsPerPayload, + final int maxAttesterSlashingsElectra, + final int maxAttestationsElectra, + final int maxConsolidations) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; this.maxDepositReceiptsPerPayload = maxDepositReceiptsPerPayload; this.maxExecutionLayerExits = maxExecutionLayerExits; + this.minPerEpochChurnLimitElectra = minPerEpochChurnLimitElectra; + this.maxPerEpochActivationExitChurnLimit = maxPerEpochActivationExitChurnLimit; + this.minActivationBalance = minActivationBalance; + this.maxEffectiveBalanceElectra = maxEffectiveBalanceElectra; + this.pendingBalanceDepositsLimit = pendingBalanceDepositsLimit; + this.pendingPartialWithdrawalsLimit = pendingPartialWithdrawalsLimit; + this.pendingConsolidationsLimit = pendingConsolidationsLimit; + this.whistleblowerRewardQuotientElectra = whistleblowerRewardQuotientElectra; + this.minSlashingPenaltyQuotientElectra = minSlashingPenaltyQuotientElectra; + this.maxPartialWithdrawalsPerPayload = maxPartialWithdrawalsPerPayload; + this.maxAttesterSlashingsElectra = maxAttesterSlashingsElectra; + this.maxAttestationsElectra = maxAttestationsElectra; + this.maxConsolidations = maxConsolidations; } @Override @@ -59,6 +98,71 @@ public int getMaxExecutionLayerExits() { return maxExecutionLayerExits; } + @Override + public UInt64 getMinActivationBalance() { + return minActivationBalance; + } + + @Override + public UInt64 getMaxEffectiveBalanceElectra() { + return maxEffectiveBalanceElectra; + } + + @Override + public UInt64 getPendingBalanceDepositsLimit() { + return pendingBalanceDepositsLimit; + } + + @Override + public UInt64 getPendingPartialWithdrawalsLimit() { + return pendingPartialWithdrawalsLimit; + } + + @Override + public UInt64 getPendingConsolidationsLimit() { + return pendingConsolidationsLimit; + } + + @Override + public int getWhistleblowerRewardQuotientElectra() { + return whistleblowerRewardQuotientElectra; + } + + @Override + public int getMinSlashingPenaltyQuotientElectra() { + return minSlashingPenaltyQuotientElectra; + } + + @Override + public int getMaxAttesterSlashingsElectra() { + return maxAttesterSlashingsElectra; + } + + @Override + public int getMaxAttestationsElectra() { + return maxAttestationsElectra; + } + + @Override + public int getMaxConsolidations() { + return maxConsolidations; + } + + @Override + public int getMaxPartialWithdrawalsPerPayload() { + return maxPartialWithdrawalsPerPayload; + } + + @Override + public UInt64 getMinPerEpochChurnLimitElectra() { + return minPerEpochChurnLimitElectra; + } + + @Override + public UInt64 getMaxPerEpochActivationExitChurnLimit() { + return maxPerEpochActivationExitChurnLimit; + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index f1ab10bf21b..dc9924dd9c7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -30,8 +30,21 @@ public class ElectraBuilder implements ForkConfigBuilder getValidationMap() { constants.put("electraForkEpoch", electraForkEpoch); constants.put("electraForkVersion", electraForkVersion); constants.put("maxDepositReceiptsPerPayload", maxDepositReceiptsPerPayload); + constants.put("minPerEpochChurnLimitElectra", minPerEpochChurnLimitElectra); + constants.put("maxExecutionLayerExits", maxExecutionLayerExits); + constants.put("minActivationBalance", minActivationBalance); + constants.put("maxEffectiveBalanceElectra", maxEffectiveBalanceElectra); + constants.put("pendingBalanceDepositsLimit", pendingBalanceDepositsLimit); + constants.put("pendingPartialWithdrawalsLimit", pendingPartialWithdrawalsLimit); + constants.put("pendingConsolidationsLimit", pendingConsolidationsLimit); + constants.put("whistleblowerRewardQuotientElectra", whistleblowerRewardQuotientElectra); + constants.put("minSlashingPenaltyQuotientElectra", minSlashingPenaltyQuotientElectra); + constants.put("maxPartialWithdrawalsPerPayload", maxPartialWithdrawalsPerPayload); + constants.put("maxAttesterSlashingsElectra", maxAttesterSlashingsElectra); + constants.put("maxAttestationsElectra", maxAttestationsElectra); + constants.put("maxConsolidations", maxConsolidations); return constants; } diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index a706330f635..1def514bbf2 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -141,4 +141,8 @@ MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` -BLOB_SIDECAR_SUBNET_COUNT: 6 \ No newline at end of file +BLOB_SIDECAR_SUBNET_COUNT: 6 + +# [New in Electra:EIP7251] +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index 87b928277bb..d913bb3b9cb 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -141,4 +141,8 @@ MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` -BLOB_SIDECAR_SUBNET_COUNT: 6 \ No newline at end of file +BLOB_SIDECAR_SUBNET_COUNT: 6 + +# [New in Electra:EIP7251] +MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml index b920664fff2..adac3d4f85b 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml @@ -1,8 +1,36 @@ # Mainnet preset - Electra +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 +PENDING_CONSOLIDATIONS_LIMIT: 262144 + +# Reward and penalty quotients +# --------------------------------------------------------------- +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2 * 3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +MAX_CONSOLIDATIONS: 1 + # Execution # --------------------------------------------------------------- # 2**13 (= 8192) receipts MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192 # 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 \ No newline at end of file +MAX_EXECUTION_LAYER_EXITS: 16 +# 2**3 (= 8) partial withdrawals +MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml index ff5bd201834..f36e5a80041 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml @@ -1,8 +1,38 @@ # Minimal preset - Electra +# Gwei values +# --------------------------------------------------------------- +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 +# [customized] smaller queue +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 +# [customized] smaller queue +PENDING_CONSOLIDATIONS_LIMIT: 64 + +# Reward and penalty quotients +# --------------------------------------------------------------- +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2 * 3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +MAX_CONSOLIDATIONS: 1 + # Execution # --------------------------------------------------------------- # [customized] MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 # 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 \ No newline at end of file +MAX_EXECUTION_LAYER_EXITS: 16 +# [customized] 2**1 (= 2) +MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml index d8a8b77dd38..f36e5a80041 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml @@ -1,11 +1,38 @@ # Minimal preset - Electra -# Max operations per block +# Gwei values # --------------------------------------------------------------- -# 2**4 (= 16) -MAX_EXECUTION_LAYER_EXITS: 16 +# 2**5 * 10**9 (= 32,000,000,000) Gwei +MIN_ACTIVATION_BALANCE: 32000000000 +# 2**11 * 10**9 (= 2,048,000,000,000) Gwei +MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 + +# State list lengths +# --------------------------------------------------------------- +PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 +# [customized] smaller queue +PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 +# [customized] smaller queue +PENDING_CONSOLIDATIONS_LIMIT: 64 + +# Reward and penalty quotients +# --------------------------------------------------------------- +MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 +WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 + +# # Max operations per block +# --------------------------------------------------------------- +# `uint64(2**0)` (= 1) +MAX_ATTESTER_SLASHINGS_ELECTRA: 1 +# `uint64(2 * 3)` (= 8) +MAX_ATTESTATIONS_ELECTRA: 8 +MAX_CONSOLIDATIONS: 1 # Execution # --------------------------------------------------------------- # [customized] -MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 \ No newline at end of file +MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 +# 2**4 (= 16) exits +MAX_EXECUTION_LAYER_EXITS: 16 +# [customized] 2**1 (= 2) +MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 \ No newline at end of file diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index fce8ab80718..e0b79f22f73 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -83,6 +83,19 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomBytes4(), dataStructureUtil.randomUInt64(999_999), dataStructureUtil.randomPositiveInt(16), - dataStructureUtil.randomPositiveInt(16)) {}; + dataStructureUtil.randomPositiveInt(16), + dataStructureUtil.randomUInt64(128000000000L), + dataStructureUtil.randomUInt64(256000000000L), + dataStructureUtil.randomUInt64(32000000000L), + dataStructureUtil.randomUInt64(2048000000000L), + dataStructureUtil.randomUInt64(134217728L), + dataStructureUtil.randomUInt64(134217728L), + dataStructureUtil.randomUInt64(262144L), + dataStructureUtil.randomPositiveInt(4096), + dataStructureUtil.randomPositiveInt(4096), + dataStructureUtil.randomPositiveInt(8), + dataStructureUtil.randomPositiveInt(8), + dataStructureUtil.randomPositiveInt(8), + dataStructureUtil.randomPositiveInt(8)) {}; } } From 514539b434111167917bf6cf36a1dd0123af1220 Mon Sep 17 00:00:00 2001 From: Paul Harris Date: Thu, 11 Apr 2024 14:24:41 +1000 Subject: [PATCH 09/70] Added a timeout to state regeneration (#8097) Added a development flag to allow us to set state regeneration timeout, with a 120 second timeout by default, and a tiny bit of sanity around the flag not being less than 1. Signed-off-by: Paul Harris --- .../services/chainstorage/StorageService.java | 6 +- storage/build.gradle | 2 + .../teku/storage/server/ChainStorage.java | 8 +- .../storage/server/StorageConfiguration.java | 23 +++- .../server/state/FinalizedStateCache.java | 74 +++++------- .../server/state/StateCacheLoader.java | 110 ++++++++++++++++++ .../server/MultiThreadedStoreTest.java | 1 + .../server/state/FinalizedStateCacheTest.java | 2 +- .../server/state/StateCacheLoaderTest.java | 62 ++++++++++ .../FileBackedStorageSystemBuilder.java | 11 +- .../InMemoryStorageSystemBuilder.java | 11 +- .../storage/storageSystem/StorageSystem.java | 6 +- .../cli/options/BeaconNodeDataOptions.java | 12 ++ 13 files changed, 274 insertions(+), 54 deletions(-) create mode 100644 storage/src/main/java/tech/pegasys/teku/storage/server/state/StateCacheLoader.java create mode 100644 storage/src/test/java/tech/pegasys/teku/storage/server/state/StateCacheLoaderTest.java diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index a8f007ee21e..f2215bdbbb5 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -130,7 +130,11 @@ protected SafeFuture doStart() { } final EventChannels eventChannels = serviceConfig.getEventChannels(); chainStorage = - ChainStorage.create(database, config.getSpec(), config.getDataStorageMode()); + ChainStorage.create( + database, + config.getSpec(), + config.getDataStorageMode(), + config.getStateRebuildTimeoutSeconds()); final DepositStorage depositStorage = DepositStorage.create( eventChannels.getPublisher(Eth1EventsChannel.class), diff --git a/storage/build.gradle b/storage/build.gradle index e311b2f1c72..fd19d858348 100644 --- a/storage/build.gradle +++ b/storage/build.gradle @@ -38,6 +38,7 @@ dependencies { testImplementation testFixtures(project(':infrastructure:metrics')) testImplementation project(':ethereum:networks') testImplementation testFixtures(project(':ethereum:spec')) + testImplementation testFixtures(project(':infrastructure:logging')) testImplementation testFixtures(project(':infrastructure:async')) testImplementation testFixtures(project(':infrastructure:time')) testImplementation testFixtures(project(':storage')) @@ -67,6 +68,7 @@ dependencies { testFixturesImplementation 'org.hyperledger.besu.internal:metrics-core' testFixturesImplementation 'org.hyperledger.besu:plugin-api' + jmhImplementation testFixtures(project(':storage')) jmhImplementation testFixtures(project(':ethereum:spec')) } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java index 6a514f521af..6a7c8b17d2c 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java @@ -70,11 +70,15 @@ private ChainStorage( } public static ChainStorage create( - final Database database, final Spec spec, final StateStorageMode dataStorageMode) { + final Database database, + final Spec spec, + final StateStorageMode dataStorageMode, + int stateRebuildTimeoutSeconds) { final int finalizedStateCacheSize = spec.getSlotsPerEpoch(SpecConfig.GENESIS_EPOCH) * 3; return new ChainStorage( database, - new FinalizedStateCache(spec, database, finalizedStateCacheSize, true), + new FinalizedStateCache( + spec, database, finalizedStateCacheSize, true, stateRebuildTimeoutSeconds), dataStorageMode); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/StorageConfiguration.java b/storage/src/main/java/tech/pegasys/teku/storage/server/StorageConfiguration.java index abe6c656e94..c7034371dfe 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/StorageConfiguration.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/StorageConfiguration.java @@ -35,7 +35,7 @@ public class StorageConfiguration { public static final boolean DEFAULT_STORE_NON_CANONICAL_BLOCKS_ENABLED = false; - + public static final int DEFAULT_STATE_REBUILD_TIMEOUT_SECONDS = 120; public static final long DEFAULT_STORAGE_FREQUENCY = 2048L; public static final int DEFAULT_MAX_KNOWN_NODE_CACHE_SIZE = 100_000; public static final Duration DEFAULT_BLOCK_PRUNING_INTERVAL = Duration.ofMinutes(15); @@ -59,6 +59,8 @@ public class StorageConfiguration { private final Duration blobsPruningInterval; private final int blobsPruningLimit; + private final int stateRebuildTimeoutSeconds; + private StorageConfiguration( final Eth1Address eth1DepositContract, final StateStorageMode dataStorageMode, @@ -70,6 +72,7 @@ private StorageConfiguration( final int blockPruningLimit, final Duration blobsPruningInterval, final int blobsPruningLimit, + int stateRebuildTimeoutSeconds, final Spec spec) { this.eth1DepositContract = eth1DepositContract; this.dataStorageMode = dataStorageMode; @@ -81,6 +84,7 @@ private StorageConfiguration( this.blockPruningLimit = blockPruningLimit; this.blobsPruningInterval = blobsPruningInterval; this.blobsPruningLimit = blobsPruningLimit; + this.stateRebuildTimeoutSeconds = stateRebuildTimeoutSeconds; this.spec = spec; } @@ -96,6 +100,10 @@ public StateStorageMode getDataStorageMode() { return dataStorageMode; } + public int getStateRebuildTimeoutSeconds() { + return stateRebuildTimeoutSeconds; + } + public long getDataStorageFrequency() { return dataStorageFrequency; } @@ -146,6 +154,7 @@ public static final class Builder { private int blockPruningLimit = DEFAULT_BLOCK_PRUNING_LIMIT; private Duration blobsPruningInterval = DEFAULT_BLOBS_PRUNING_INTERVAL; private int blobsPruningLimit = DEFAULT_BLOBS_PRUNING_LIMIT; + private int stateRebuildTimeoutSeconds = DEFAULT_STATE_REBUILD_TIMEOUT_SECONDS; private Builder() {} @@ -251,6 +260,7 @@ public StorageConfiguration build() { blockPruningLimit, blobsPruningInterval, blobsPruningLimit, + stateRebuildTimeoutSeconds, spec); } @@ -285,6 +295,17 @@ private Optional getStorageModeFromPersistedDatabase( throw new UncheckedIOException("Failed to read storage mode from file", ex); } } + + public Builder stateRebuildTimeoutSeconds(int stateRebuildTimeoutSeconds) { + if (stateRebuildTimeoutSeconds < 10 || stateRebuildTimeoutSeconds > 300) { + LOG.warn( + "State rebuild timeout is set outside of sensible defaults of 10 -> 300, {} was defined. Cannot be below 1, will allow the value to exceed 300.", + stateRebuildTimeoutSeconds); + } + this.stateRebuildTimeoutSeconds = Math.max(stateRebuildTimeoutSeconds, 1); + LOG.debug("stateRebuildTimeoutSeconds = {}", stateRebuildTimeoutSeconds); + return this; + } } static StateStorageMode determineStorageDefault( diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/state/FinalizedStateCache.java b/storage/src/main/java/tech/pegasys/teku/storage/server/state/FinalizedStateCache.java index 6e2c583eb83..fc3a9e6e794 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/state/FinalizedStateCache.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/state/FinalizedStateCache.java @@ -13,11 +13,8 @@ package tech.pegasys.teku.storage.server.state; -import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; - import com.google.common.base.Throwables; import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalCause; import com.google.common.cache.RemovalNotification; @@ -25,15 +22,15 @@ import java.util.NavigableSet; import java.util.Optional; import java.util.concurrent.ConcurrentSkipListSet; -import java.util.stream.Stream; -import tech.pegasys.teku.dataproviders.generators.StreamingStateRegenerator; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.storage.server.Database; public class FinalizedStateCache { + + private static final long MAX_REGENERATE_LOTS = 10_000L; + /** * Note this is a best effort basis to track what states are cached. Slots are added here slightly * before the stateCache is actually updated and removed slightly after they are evicted from the @@ -42,16 +39,29 @@ public class FinalizedStateCache { private final NavigableSet availableSlots = new ConcurrentSkipListSet<>(); private final LoadingCache stateCache; - private final Spec spec; - private final Database database; public FinalizedStateCache( final Spec spec, final Database database, final int maximumCacheSize, - final boolean useSoftReferences) { - this.spec = spec; - this.database = database; + final boolean useSoftReferences, + final int stateRebuildTimeoutSeconds) { + this( + spec, + database, + maximumCacheSize, + useSoftReferences, + stateRebuildTimeoutSeconds, + MAX_REGENERATE_LOTS); + } + + FinalizedStateCache( + final Spec spec, + final Database database, + final int maximumCacheSize, + final boolean useSoftReferences, + int stateRebuildTimeoutSeconds, + final long maxRegenerateSlots) { final CacheBuilder cacheBuilder = CacheBuilder.newBuilder() .maximumSize(maximumCacheSize) @@ -59,7 +69,10 @@ public FinalizedStateCache( if (useSoftReferences) { cacheBuilder.softValues(); } - this.stateCache = cacheBuilder.build(new StateCacheLoader()); + this.stateCache = + cacheBuilder.build( + new StateCacheLoader( + spec, database, stateRebuildTimeoutSeconds, maxRegenerateSlots, this)); } private void onRemovedFromCache( @@ -80,46 +93,17 @@ public Optional getFinalizedState(final UInt64 slot) { } } - private Optional getLatestStateFromCache(final UInt64 slot) { + Optional getLatestStateFromCache(final UInt64 slot) { return Optional.ofNullable(availableSlots.floor(slot)).map(stateCache::getIfPresent); } - private class StateCacheLoader extends CacheLoader { - - @Override - public BeaconState load(final UInt64 key) { - return regenerateState(key).orElseThrow(StateUnavailableException::new); - } - - private Optional regenerateState(final UInt64 slot) { - return database - .getLatestAvailableFinalizedState(slot) - .map(state -> regenerateState(slot, state)); - } - - private BeaconState regenerateState(final UInt64 slot, final BeaconState stateFromDisk) { - final Optional latestStateFromCache = getLatestStateFromCache(slot); - final BeaconState preState = - latestStateFromCache - .filter( - stateFromCache -> - stateFromCache.getSlot().compareTo(stateFromDisk.getSlot()) >= 0) - .orElse(stateFromDisk); - if (preState.getSlot().equals(slot)) { - return preState; - } - try (final Stream blocks = - database.streamFinalizedBlocks(preState.getSlot().plus(ONE), slot)) { - final BeaconState state = StreamingStateRegenerator.regenerate(spec, preState, blocks); - availableSlots.add(state.getSlot()); - return state; - } - } + NavigableSet getAvailableSlots() { + return availableSlots; } /** * Cache doesn't allow returning null but we may not be able to regenerate a state so throw this * exception and catch it in {@link #getFinalizedState(UInt64)} */ - private static class StateUnavailableException extends RuntimeException {} + static class StateUnavailableException extends RuntimeException {} } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/state/StateCacheLoader.java b/storage/src/main/java/tech/pegasys/teku/storage/server/state/StateCacheLoader.java new file mode 100644 index 00000000000..b959656f77f --- /dev/null +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/state/StateCacheLoader.java @@ -0,0 +1,110 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.storage.server.state; + +import static tech.pegasys.teku.infrastructure.unsigned.UInt64.ONE; + +import com.google.common.cache.CacheLoader; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.dataproviders.generators.StreamingStateRegenerator; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.storage.server.Database; + +class StateCacheLoader extends CacheLoader { + private static final Logger LOG = LogManager.getLogger(); + private final int stateRebuildTimeoutSeconds; + private final Database database; + private final long maxRegenerateSlots; + private final FinalizedStateCache finalizedStateCache; + private final Spec spec; + + StateCacheLoader( + final Spec spec, + final Database database, + final int stateRebuildTimeoutSeconds, + final long maxRegenerateSlots, + final FinalizedStateCache finalizedStateCache) { + this.database = database; + this.stateRebuildTimeoutSeconds = stateRebuildTimeoutSeconds; + this.maxRegenerateSlots = maxRegenerateSlots; + this.finalizedStateCache = finalizedStateCache; + this.spec = spec; + } + + @Override + public BeaconState load(final UInt64 key) { + return regenerateState(key).orElseThrow(FinalizedStateCache.StateUnavailableException::new); + } + + private Optional regenerateState(final UInt64 slot) { + final Optional maybeState = database.getLatestAvailableFinalizedState(slot); + if (maybeState.isEmpty()) { + return Optional.empty(); + } + final BeaconState state = maybeState.get(); + try { + return Optional.of( + regenerateStateWithinReasonableTime(slot, state) + .get(stateRebuildTimeoutSeconds, TimeUnit.SECONDS)); + } catch (ExecutionException | InterruptedException e) { + LOG.warn("Failed to regenerate state for slot {}", slot, e); + return Optional.empty(); + } catch (TimeoutException e) { + LOG.error( + "Timed out trying to regenerate state at slot {} starting from slot {} within {} seconds", + slot, + state.getSlot(), + stateRebuildTimeoutSeconds); + return Optional.empty(); + } + } + + private SafeFuture regenerateStateWithinReasonableTime( + final UInt64 slot, final BeaconState stateFromDisk) { + final Optional latestStateFromCache = + finalizedStateCache.getLatestStateFromCache(slot); + final BeaconState preState = + latestStateFromCache + .filter( + stateFromCache -> stateFromCache.getSlot().compareTo(stateFromDisk.getSlot()) >= 0) + .orElse(stateFromDisk); + if (preState.getSlot().equals(slot)) { + return SafeFuture.completedFuture(preState); + } + final long regenerateSlotCount = slot.minusMinZero(stateFromDisk.getSlot()).longValue(); + LOG.trace("Slots to regenerate state from: {}", regenerateSlotCount); + if (regenerateSlotCount > maxRegenerateSlots) { + LOG.error( + "Refusing to regenerate a state that is {} slots from what we have stored", + regenerateSlotCount); + return SafeFuture.failedFuture(new FinalizedStateCache.StateUnavailableException()); + } + try (final Stream blocks = + database.streamFinalizedBlocks(preState.getSlot().plus(ONE), slot)) { + final BeaconState state = StreamingStateRegenerator.regenerate(spec, preState, blocks); + finalizedStateCache.getAvailableSlots().add(state.getSlot()); + return SafeFuture.completedFuture(state); + } + } +} diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/MultiThreadedStoreTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/MultiThreadedStoreTest.java index 6c67aad45b3..a26bb36b9c5 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/server/MultiThreadedStoreTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/MultiThreadedStoreTest.java @@ -117,6 +117,7 @@ private StorageSystem createStorageSystem( .dataDir(tempDir.toPath()) .version(DatabaseVersion.LEVELDB2) .storageMode(storageMode) + .stateRebuildTimeoutSeconds(12) .stateStorageFrequency(1L) .storeConfig(storeConfig) .storeNonCanonicalBlocks(storeNonCanonicalBlocks) diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/state/FinalizedStateCacheTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/state/FinalizedStateCacheTest.java index 81565ccc3dd..a7e3c487e18 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/server/state/FinalizedStateCacheTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/state/FinalizedStateCacheTest.java @@ -44,7 +44,7 @@ class FinalizedStateCacheTest { private final Database database = mock(Database.class); // We don't use soft references in unit tests to avoid intermittency private final FinalizedStateCache cache = - new FinalizedStateCache(spec, database, MAXIMUM_CACHE_SIZE, false); + new FinalizedStateCache(spec, database, MAXIMUM_CACHE_SIZE, false, 120); @BeforeEach public void setUp() { diff --git a/storage/src/test/java/tech/pegasys/teku/storage/server/state/StateCacheLoaderTest.java b/storage/src/test/java/tech/pegasys/teku/storage/server/state/StateCacheLoaderTest.java new file mode 100644 index 00000000000..a32c593cc98 --- /dev/null +++ b/storage/src/test/java/tech/pegasys/teku/storage/server/state/StateCacheLoaderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.storage.server.state; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.LoadingCache; +import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.junit.jupiter.api.Test; +import tech.pegasys.infrastructure.logging.LogCaptor; +import tech.pegasys.teku.beacon.pow.TimeBasedEth1HeadTracker; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.util.DataStructureUtil; +import tech.pegasys.teku.storage.server.Database; + +public class StateCacheLoaderTest { + private static final Logger LOG = LogManager.getLogger(); + private final Spec spec = TestSpecFactory.createMinimalPhase0(); + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); + private final Database database = mock(Database.class); + private final FinalizedStateCache finalizedStateCache = mock(FinalizedStateCache.class); + private LoadingCache cache; + + @Test + void shouldRejectRegenerateIfTooFarAway() { + final BeaconState availableState = dataStructureUtil.randomBeaconState(UInt64.ZERO); + when(database.getLatestAvailableFinalizedState(any())).thenReturn(Optional.of(availableState)); + + final CacheBuilder cacheBuilder = + CacheBuilder.newBuilder() + .maximumSize(2) + .removalListener((k) -> LOG.info(String.format("removed %s", k.getKey()))); + this.cache = + cacheBuilder.build(new StateCacheLoader(spec, database, 1, 2, finalizedStateCache)); + try (LogCaptor logCaptor = LogCaptor.forClass(TimeBasedEth1HeadTracker.class)) { + assertThatThrownBy(() -> cache.get(UInt64.valueOf(4))) + .hasCauseInstanceOf(FinalizedStateCache.StateUnavailableException.class); + logCaptor.assertErrorLog( + "Refusing to regenerate a state that is 4 slots from what we have stored"); + } + } +} diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/FileBackedStorageSystemBuilder.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/FileBackedStorageSystemBuilder.java index 0b2a34db26a..ab1998e39af 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/FileBackedStorageSystemBuilder.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/FileBackedStorageSystemBuilder.java @@ -45,6 +45,8 @@ public class FileBackedStorageSystemBuilder { private long stateStorageFrequency = 1L; private boolean storeNonCanonicalBlocks = false; + private int stateRebuildTimeoutSeconds = 120; + private FileBackedStorageSystemBuilder() {} public static FileBackedStorageSystemBuilder create() { @@ -62,7 +64,8 @@ public StorageSystem build() { storeConfig, new SystemTimeProvider(), spec, - ChainBuilder.create(spec)); + ChainBuilder.create(spec), + stateRebuildTimeoutSeconds); } private Database buildDatabase() { @@ -138,6 +141,12 @@ public FileBackedStorageSystemBuilder storageMode(final StateStorageMode storage return this; } + public FileBackedStorageSystemBuilder stateRebuildTimeoutSeconds( + final int stateRebuildTimeoutSeconds) { + this.stateRebuildTimeoutSeconds = stateRebuildTimeoutSeconds; + return this; + } + public FileBackedStorageSystemBuilder stateStorageFrequency(final long stateStorageFrequency) { this.stateStorageFrequency = stateStorageFrequency; return this; diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/InMemoryStorageSystemBuilder.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/InMemoryStorageSystemBuilder.java index 6df97379790..cbfd952c1a1 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/InMemoryStorageSystemBuilder.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/InMemoryStorageSystemBuilder.java @@ -42,6 +42,8 @@ public class InMemoryStorageSystemBuilder { private long stateStorageFrequency = 1L; private boolean storeNonCanonicalBlocks = false; + private int stateRebuildTimeoutSeconds = 120; + private Spec spec = TestSpecFactory.createMinimalPhase0(); // Internal variables @@ -101,7 +103,8 @@ public StorageSystem build() { storeConfig, new SystemTimeProvider(), spec, - ChainBuilder.create(spec, validatorKeys)); + ChainBuilder.create(spec, validatorKeys), + stateRebuildTimeoutSeconds); } public InMemoryStorageSystemBuilder specProvider(final Spec spec) { @@ -148,6 +151,12 @@ public InMemoryStorageSystemBuilder stateStorageFrequency(final long stateStorag return this; } + public InMemoryStorageSystemBuilder stateRebuildTimeoutSeconds( + final int stateRebuildTimeoutSeconds) { + this.stateRebuildTimeoutSeconds = stateRebuildTimeoutSeconds; + return this; + } + public InMemoryStorageSystemBuilder storeConfig(final StoreConfig storeConfig) { checkNotNull(storeConfig); this.storeConfig = storeConfig; diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java index 5ce0166eaf6..c71959bfb9a 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java @@ -91,11 +91,13 @@ static StorageSystem create( final StoreConfig storeConfig, final TimeProvider timeProvider, final Spec spec, - final ChainBuilder chainBuilder) { + final ChainBuilder chainBuilder, + final int stateRebuildTimeoutSeconds) { final StubMetricsSystem metricsSystem = new StubMetricsSystem(); // Create and start storage server - final ChainStorage chainStorageServer = ChainStorage.create(database, spec, storageMode); + final ChainStorage chainStorageServer = + ChainStorage.create(database, spec, storageMode, stateRebuildTimeoutSeconds); // Create recent chain data final FinalizedCheckpointChannel finalizedCheckpointChannel = diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/BeaconNodeDataOptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/BeaconNodeDataOptions.java index 72effbea96d..954eda886ce 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/BeaconNodeDataOptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/BeaconNodeDataOptions.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.cli.options; +import static tech.pegasys.teku.storage.server.StorageConfiguration.DEFAULT_STATE_REBUILD_TIMEOUT_SECONDS; + import java.nio.file.Path; import java.time.Duration; import picocli.CommandLine; @@ -136,6 +138,15 @@ public class BeaconNodeDataOptions extends ValidatorClientDataOptions { arity = "0..1") private int blobsPruningLimit = StorageConfiguration.DEFAULT_BLOBS_PRUNING_LIMIT; + @Option( + names = {"--Xdata-storage-state-rebuild-timeout-seconds"}, + hidden = true, + paramLabel = "", + description = + "Only allow up to an allocated period of time to attempt to rebuild a missing finalized state.", + arity = "1") + private int stateRebuildTimeoutSeconds = DEFAULT_STATE_REBUILD_TIMEOUT_SECONDS; + @Override protected DataConfig.Builder configureDataConfig(final DataConfig.Builder config) { return super.configureDataConfig(config).beaconDataPath(dataBeaconPath); @@ -153,6 +164,7 @@ public void configure(final TekuConfiguration.Builder builder) { .maxKnownNodeCacheSize(maxKnownNodeCacheSize) .blockPruningInterval(Duration.ofSeconds(blockPruningIntervalSeconds)) .blockPruningLimit(blockPruningLimit) + .stateRebuildTimeoutSeconds(stateRebuildTimeoutSeconds) .blobsPruningInterval(Duration.ofSeconds(blobsPruningIntervalSeconds)) .blobsPruningLimit(blobsPruningLimit)); builder.sync( From c4b9a7ad6894eb1e9727ebb59bd5b31baf421f9d Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 11 Apr 2024 22:08:16 +0400 Subject: [PATCH 10/70] Don't encode ids array comma separators in getStateValidator request (#8189) --- .../typedef/OkHttpValidatorTypeDefClientTest.java | 15 +++++++++++++++ .../typedef/handlers/AbstractTypeDefRequest.java | 11 ++++++++++- .../typedef/handlers/CreateBlockRequest.java | 9 ++++++++- .../handlers/GetStateValidatorsRequest.java | 5 ++++- .../typedef/handlers/ProduceBlockRequest.java | 2 ++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java index 25d07b90d94..ce59452d114 100644 --- a/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java +++ b/validator/remote/src/integration-test/java/tech/pegasys/teku/validator/remote/typedef/OkHttpValidatorTypeDefClientTest.java @@ -366,6 +366,21 @@ void postValidators_MakesExpectedRequest() throws Exception { assertThat(request.getBody().readUtf8()).isEqualTo("{\"ids\":[\"1\",\"0x1234\"]}"); } + @TestTemplate + void getStateValidators_MakesExpectedRequest() throws Exception { + mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); + + okHttpValidatorTypeDefClient.getStateValidators(List.of("1", "0x1234")); + + final RecordedRequest request = mockWebServer.takeRequest(); + assertThat(request.getMethod()).isEqualTo("GET"); + + assertThat(request.getPath()).contains(ValidatorApiMethod.GET_VALIDATORS.getPath(emptyMap())); + // comma-separated GET query array parameters shouldn't be encoded + // and must pass AS IS as per RFC-3986 + assertThat(request.getPath()).contains("?id=1,0x1234"); + } + @TestTemplate public void postValidators_WhenNoContent_ReturnsEmpty() { mockWebServer.enqueue(new MockResponse().setResponseCode(SC_NO_CONTENT)); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java index 01f53f70302..0a17e1cc0a6 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/AbstractTypeDefRequest.java @@ -84,13 +84,14 @@ protected Optional get( final Map urlParams, final Map queryParams, final ResponseHandler responseHandler) { - return get(apiMethod, urlParams, queryParams, Map.of(), responseHandler); + return get(apiMethod, urlParams, queryParams, emptyMap(), emptyMap(), responseHandler); } protected Optional get( final ValidatorApiMethod apiMethod, final Map urlParams, final Map queryParams, + final Map encodedQueryParams, final Map headers, final ResponseHandler responseHandler) { final HttpUrl.Builder httpUrlBuilder = urlBuilder(apiMethod, urlParams); @@ -98,6 +99,14 @@ protected Optional get( queryParams.forEach(httpUrlBuilder::addQueryParameter); } + // The encodedQueryParams are considered to be encoded already + // and should not be encoded again. This is useful to prevent + // the comma in an array of values (e.g. id=1,2,3) from being + // encoded. + if (encodedQueryParams != null && !encodedQueryParams.isEmpty()) { + encodedQueryParams.forEach(httpUrlBuilder::addEncodedQueryParameter); + } + final Request.Builder builder = requestBuilder().url(httpUrlBuilder.build()); if (headers != null && !headers.isEmpty()) { headers.forEach(builder::addHeader); diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateBlockRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateBlockRequest.java index 2c31d83bc78..402b65fa03c 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateBlockRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/CreateBlockRequest.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.validator.remote.typedef.handlers; +import static java.util.Collections.emptyMap; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_UNSIGNED_BLINDED_BLOCK; @@ -109,7 +110,13 @@ public Optional createUnsignedBlock( // application/octet-stream is preferred, but will accept application/json headers.put("Accept", "application/octet-stream;q=0.9, application/json;q=0.4"); } - return get(apiMethod, Map.of("slot", slot.toString()), queryParams, headers, responseHandler) + return get( + apiMethod, + Map.of("slot", slot.toString()), + queryParams, + emptyMap(), + headers, + responseHandler) .map( response -> new BlockContainerAndMetaData( diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequest.java index 706e2fe1c58..63ae05f54cb 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/GetStateValidatorsRequest.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.validator.remote.typedef.handlers; +import static java.util.Collections.emptyMap; import static tech.pegasys.teku.ethereum.json.types.beacon.StateValidatorDataBuilder.STATE_VALIDATORS_RESPONSE_TYPE; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.PARAM_ID; import static tech.pegasys.teku.validator.remote.apiclient.ValidatorApiMethod.GET_VALIDATORS; @@ -35,8 +36,10 @@ public Optional>> getStateValidators( final List validatorIds) { return get( GET_VALIDATORS, - Map.of(), + emptyMap(), + emptyMap(), Map.of(PARAM_ID, String.join(",", validatorIds)), + emptyMap(), new ResponseHandler<>(STATE_VALIDATORS_RESPONSE_TYPE)); } } diff --git a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/ProduceBlockRequest.java b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/ProduceBlockRequest.java index f083394c678..6f8981041a0 100644 --- a/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/ProduceBlockRequest.java +++ b/validator/remote/src/main/java/tech/pegasys/teku/validator/remote/typedef/handlers/ProduceBlockRequest.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.validator.remote.typedef.handlers; +import static java.util.Collections.emptyMap; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_NOT_FOUND; import static tech.pegasys.teku.infrastructure.http.HttpStatusCodes.SC_OK; import static tech.pegasys.teku.infrastructure.http.RestApiConstants.BUILDER_BOOST_FACTOR; @@ -126,6 +127,7 @@ public Optional createUnsignedBlock( GET_UNSIGNED_BLOCK_V3, Map.of("slot", slot.toString()), queryParams, + emptyMap(), headers, this.responseHandler) .map( From ad5e63f651b8db03396b07855a2ed998aa5887b8 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 11 Apr 2024 22:33:47 +0400 Subject: [PATCH 11/70] Disable requirement for EIP-7251 configuration in Electra config (#8188) --- .../pegasys/teku/spec/config/builder/ElectraBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index dc9924dd9c7..06f36bfa9c5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -30,8 +30,10 @@ public class ElectraBuilder implements ForkConfigBuilder Date: Fri, 12 Apr 2024 08:27:09 +1000 Subject: [PATCH 12/70] Electra MaxEB state changes (#8186) Signed-off-by: Paul Harris --- .../beacon/schema/BeaconStateElectra.json | 50 ++++++++- .../beacon/schema/PendingBalanceDeposit.json | 19 ++++ .../beacon/schema/PendingConsolidation.json | 19 ++++ .../schema/PendingPartialWithdrawal.json | 25 +++++ .../schema/electra/BeaconStateElectra.java | 101 +++++++++++++++++- .../schema/electra/PendingBalanceDeposit.java | 57 ++++++++++ .../schema/electra/PendingConsolidation.java | 59 ++++++++++ .../electra/PendingPartialWithdrawal.java | 66 ++++++++++++ .../teku/spec/config/SpecConfigElectra.java | 6 +- .../spec/config/SpecConfigElectraImpl.java | 18 ++-- .../spec/config/builder/ElectraBuilder.java | 12 +-- .../beaconstate/common/BeaconStateFields.java | 11 +- .../versions/electra/BeaconStateElectra.java | 52 +++++++++ .../electra/BeaconStateSchemaElectra.java | 96 ++++++++++++++++- .../electra/MutableBeaconStateElectra.java | 47 ++++++++ .../electra/PendingBalanceDeposit.java | 69 ++++++++++++ .../electra/PendingConsolidation.java | 65 +++++++++++ .../electra/PendingPartialWithdrawal.java | 78 ++++++++++++++ .../schemas/SchemaDefinitionsElectra.java | 26 +++++ .../spec/config/SpecConfigElectraTest.java | 6 +- .../spec/util/BeaconStateBuilderElectra.java | 40 +++++++ 21 files changed, 896 insertions(+), 26 deletions(-) create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json create mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java create mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java create mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json index dafbfaab9a7..eb3779670fa 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json @@ -1,7 +1,7 @@ { "title" : "BeaconStateElectra", "type" : "object", - "required" : [ "genesis_time", "genesis_validators_root", "slot", "fork", "latest_block_header", "block_roots", "state_roots", "historical_roots", "eth1_data", "eth1_data_votes", "eth1_deposit_index", "validators", "balances", "randao_mixes", "slashings", "previous_epoch_participation", "current_epoch_participation", "justification_bits", "previous_justified_checkpoint", "current_justified_checkpoint", "finalized_checkpoint", "inactivity_scores", "current_sync_committee", "next_sync_committee", "latest_execution_payload_header", "next_withdrawal_index", "next_withdrawal_validator_index", "historical_summaries", "deposit_receipts_start_index" ], + "required" : [ "genesis_time", "genesis_validators_root", "slot", "fork", "latest_block_header", "block_roots", "state_roots", "historical_roots", "eth1_data", "eth1_data_votes", "eth1_deposit_index", "validators", "balances", "randao_mixes", "slashings", "previous_epoch_participation", "current_epoch_participation", "justification_bits", "previous_justified_checkpoint", "current_justified_checkpoint", "finalized_checkpoint", "inactivity_scores", "current_sync_committee", "next_sync_committee", "latest_execution_payload_header", "next_withdrawal_index", "next_withdrawal_validator_index", "historical_summaries", "deposit_receipts_start_index", "deposit_balance_to_consume", "exit_balance_to_consume", "earliest_exit_epoch", "consolidation_balance_to_consume", "earliest_consolidation_epoch", "pending_balance_deposits", "pending_partial_withdrawals", "pending_consolidations" ], "properties" : { "genesis_time" : { "type" : "string", @@ -176,6 +176,54 @@ "description" : "unsigned 64 bit integer", "example" : "1", "format" : "uint64" + }, + "deposit_balance_to_consume" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "exit_balance_to_consume" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "earliest_exit_epoch" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "consolidation_balance_to_consume" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "earliest_consolidation_epoch" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "pending_balance_deposits" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PendingBalanceDeposit" + } + }, + "pending_partial_withdrawals" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PendingPartialWithdrawal" + } + }, + "pending_consolidations" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/PendingConsolidation" + } } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json new file mode 100644 index 00000000000..9f93161f54c --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json @@ -0,0 +1,19 @@ +{ + "title" : "PendingBalanceDeposit", + "type" : "object", + "required" : [ "index", "amount" ], + "properties" : { + "index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "amount" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json new file mode 100644 index 00000000000..aa9b77f2895 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json @@ -0,0 +1,19 @@ +{ + "title" : "PendingConsolidation", + "type" : "object", + "required" : [ "source_index", "target_index" ], + "properties" : { + "source_index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "target_index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json new file mode 100644 index 00000000000..8347212c107 --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json @@ -0,0 +1,25 @@ +{ + "title" : "PendingPartialWithdrawal", + "type" : "object", + "required" : [ "index", "amount", "withdrawable_epoch" ], + "properties" : { + "index" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "amount" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + }, + "withdrawable_epoch" : { + "type" : "string", + "description" : "unsigned 64 bit integer", + "example" : "1", + "format" : "uint64" + } + } +} \ No newline at end of file diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java index d7d8e33a3e0..9e36efb5542 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java @@ -57,6 +57,30 @@ public class BeaconStateElectra extends BeaconStateAltair { @JsonProperty("deposit_receipts_start_index") public final UInt64 depositReceiptsStartIndex; + @JsonProperty("deposit_balance_to_consume") + public final UInt64 depositBalanceToConsume; + + @JsonProperty("exit_balance_to_consume") + public final UInt64 exitBalanceToConsume; + + @JsonProperty("earliest_exit_epoch") + public final UInt64 earliestExitEpoch; + + @JsonProperty("consolidation_balance_to_consume") + public final UInt64 consolidationBalanceToConsume; + + @JsonProperty("earliest_consolidation_epoch") + public final UInt64 earliestConsolidationEpoch; + + @JsonProperty("pending_balance_deposits") + public final List pendingBalanceDeposits; + + @JsonProperty("pending_partial_withdrawals") + public final List pendingPartialWithdrawals; + + @JsonProperty("pending_consolidations") + public final List pendingConsolidations; + public BeaconStateElectra( @JsonProperty("genesis_time") final UInt64 genesisTime, @JsonProperty("genesis_validators_root") final Bytes32 genesisValidatorsRoot, @@ -87,7 +111,18 @@ public BeaconStateElectra( @JsonProperty("next_withdrawal_index") final UInt64 nextWithdrawalIndex, @JsonProperty("next_withdrawal_validator_index") final UInt64 nextWithdrawalValidatorIndex, @JsonProperty("historical_summaries") final List historicalSummaries, - @JsonProperty("deposit_receipts_start_index") final UInt64 depositReceiptsStartIndex) { + @JsonProperty("deposit_receipts_start_index") final UInt64 depositReceiptsStartIndex, + @JsonProperty("deposit_balance_to_consume") final UInt64 depositBalanceToConsume, + @JsonProperty("exit_balance_to_consume") final UInt64 exitBalanceToConsume, + @JsonProperty("earliest_exit_epoch") final UInt64 earliestExitEpoch, + @JsonProperty("consolidation_balance_to_consume") final UInt64 consolidationBalanceToConsume, + @JsonProperty("earliest_consolidation_epoch") final UInt64 earliestConsolidationEpoch, + @JsonProperty("pending_balance_deposits") + final List pendingBalanceDeposits, + @JsonProperty("pending_partial_withdrawals") + final List pendingPartialWithdrawals, + @JsonProperty("pending_consolidations") + final List pendingConsolidations) { super( genesisTime, genesisValidatorsRoot, @@ -118,6 +153,14 @@ public BeaconStateElectra( this.nextWithdrawalValidatorIndex = nextWithdrawalValidatorIndex; this.historicalSummaries = historicalSummaries; this.depositReceiptsStartIndex = depositReceiptsStartIndex; + this.depositBalanceToConsume = depositBalanceToConsume; + this.exitBalanceToConsume = exitBalanceToConsume; + this.earliestExitEpoch = earliestExitEpoch; + this.consolidationBalanceToConsume = consolidationBalanceToConsume; + this.earliestConsolidationEpoch = earliestConsolidationEpoch; + this.pendingBalanceDeposits = pendingBalanceDeposits; + this.pendingPartialWithdrawals = pendingPartialWithdrawals; + this.pendingConsolidations = pendingConsolidations; } public BeaconStateElectra(final BeaconState beaconState) { @@ -132,6 +175,17 @@ public BeaconStateElectra(final BeaconState beaconState) { this.historicalSummaries = electra.getHistoricalSummaries().stream().map(HistoricalSummary::new).toList(); this.depositReceiptsStartIndex = electra.getDepositReceiptsStartIndex(); + this.depositBalanceToConsume = electra.getDepositBalanceToConsume(); + this.exitBalanceToConsume = electra.getExitBalanceToConsume(); + this.earliestExitEpoch = electra.getEarliestExitEpoch(); + this.consolidationBalanceToConsume = electra.getConsolidationBalanceToConsume(); + this.earliestConsolidationEpoch = electra.getEarliestConsolidationEpoch(); + this.pendingBalanceDeposits = + electra.getPendingBalanceDeposits().stream().map(PendingBalanceDeposit::new).toList(); + this.pendingPartialWithdrawals = + electra.getPendingPartialWithdrawals().stream().map(PendingPartialWithdrawal::new).toList(); + this.pendingConsolidations = + electra.getPendingConsolidations().stream().map(PendingConsolidation::new).toList(); } @Override @@ -153,6 +207,15 @@ protected void applyAdditionalFields( BeaconStateSchemaElectra.required( mutableBeaconStateElectra.getBeaconStateSchema()) .getHistoricalSummariesSchema(), + BeaconStateSchemaElectra.required( + mutableBeaconStateElectra.getBeaconStateSchema()) + .getPendingBalanceDepositsSchema(), + BeaconStateSchemaElectra.required( + mutableBeaconStateElectra.getBeaconStateSchema()) + .getPendingPartialWithdrawalsSchema(), + BeaconStateSchemaElectra.required( + mutableBeaconStateElectra.getBeaconStateSchema()) + .getPendingConsolidationsSchema(), this)); } @@ -164,6 +227,16 @@ protected static void applyElectraFields( final SszListSchema< tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary, ?> historicalSummariesSchema, + final SszListSchema< + tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit, ?> + pendingBalanceDepositsSchema, + final SszListSchema< + tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal, + ?> + pendingPartialWithdrawalsSchema, + final SszListSchema< + tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation, ?> + pendingConsolidationsSchema, final BeaconStateElectra instance) { BeaconStateAltair.applyAltairFields(state, syncCommitteeSchema, instance); @@ -181,5 +254,31 @@ protected static void applyElectraFields( historicalSummary -> historicalSummary.asInternalHistoricalSummary(specVersion)) .toList())); state.setDepositReceiptsStartIndex(instance.depositReceiptsStartIndex); + state.setDepositBalanceToConsume(instance.depositBalanceToConsume); + state.setExitBalanceToConsume(instance.exitBalanceToConsume); + state.setEarliestExitEpoch(instance.earliestExitEpoch); + state.setConsolidationBalanceToConsume(instance.consolidationBalanceToConsume); + state.setEarliestConsolidationEpoch(instance.earliestConsolidationEpoch); + state.setPendingBalanceDeposits( + pendingBalanceDepositsSchema.createFromElements( + instance.pendingBalanceDeposits.stream() + .map( + pendingBalanceDeposit -> + pendingBalanceDeposit.asInternalPendingBalanceDeposit(specVersion)) + .toList())); + state.setPendingPartialWithdrawals( + pendingPartialWithdrawalsSchema.createFromElements( + instance.pendingPartialWithdrawals.stream() + .map( + pendingPartialWithdrawal -> + pendingPartialWithdrawal.asInternalPendingPartialWithdrawal(specVersion)) + .toList())); + state.setPendingConsolidations( + pendingConsolidationsSchema.createFromElements( + instance.pendingConsolidations.stream() + .map( + pendingConsolidation -> + pendingConsolidation.asInternalPendingConsolidation(specVersion)) + .toList())); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java new file mode 100644 index 00000000000..b3105b77fe3 --- /dev/null +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java @@ -0,0 +1,57 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.api.schema.electra; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class PendingBalanceDeposit { + + @JsonProperty("index") + public final int index; + + @JsonProperty("amount") + public final UInt64 amount; + + public PendingBalanceDeposit( + @JsonProperty("index") int index, @JsonProperty("amount") UInt64 amount) { + this.index = index; + this.amount = amount; + } + + public PendingBalanceDeposit( + final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit + internalPendingBalanceDeposit) { + this.index = internalPendingBalanceDeposit.getIndex(); + this.amount = internalPendingBalanceDeposit.getAmount(); + } + + public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit + asInternalPendingBalanceDeposit(final SpecVersion spec) { + final Optional schemaDefinitionsElectra = + spec.getSchemaDefinitions().toVersionElectra(); + if (schemaDefinitionsElectra.isEmpty()) { + throw new IllegalArgumentException( + "Could not create PendingBalanceDeposit for pre-electra spec"); + } + return schemaDefinitionsElectra + .get() + .getPendingBalanceDepositSchema() + .create(SszUInt64.of(UInt64.valueOf(this.index)), SszUInt64.of(this.amount)); + } +} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java new file mode 100644 index 00000000000..cdf90d4400c --- /dev/null +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java @@ -0,0 +1,59 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.api.schema.electra; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class PendingConsolidation { + @JsonProperty("source_index") + public final int sourceIndex; + + @JsonProperty("target_index") + public final int targetIndex; + + PendingConsolidation( + @JsonProperty("source_index") int sourceIndex, + @JsonProperty("target_index") int targetIndex) { + this.sourceIndex = sourceIndex; + this.targetIndex = targetIndex; + } + + public PendingConsolidation( + final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation + internalPendingConsolidation) { + this.sourceIndex = internalPendingConsolidation.getSourceIndex(); + this.targetIndex = internalPendingConsolidation.getTargetIndex(); + } + + public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation + asInternalPendingConsolidation(final SpecVersion spec) { + final Optional schemaDefinitionsElectra = + spec.getSchemaDefinitions().toVersionElectra(); + if (schemaDefinitionsElectra.isEmpty()) { + throw new IllegalArgumentException( + "Could not create PendingBalanceDeposit for pre-electra spec"); + } + return schemaDefinitionsElectra + .get() + .getPendingConsolidationSchema() + .create( + SszUInt64.of(UInt64.valueOf(this.sourceIndex)), + SszUInt64.of(UInt64.valueOf(this.targetIndex))); + } +} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java new file mode 100644 index 00000000000..96f295c84d2 --- /dev/null +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java @@ -0,0 +1,66 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.api.schema.electra; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; + +public class PendingPartialWithdrawal { + @JsonProperty("index") + public final int index; + + @JsonProperty("amount") + public final UInt64 amount; + + @JsonProperty("withdrawable_epoch") + public final UInt64 withdrawableEpoch; + + public PendingPartialWithdrawal( + @JsonProperty("index") int index, + @JsonProperty("amount") UInt64 amount, + @JsonProperty("withdrawable_epoch") UInt64 withdrawableEpoch) { + this.index = index; + this.amount = amount; + this.withdrawableEpoch = withdrawableEpoch; + } + + public PendingPartialWithdrawal( + final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal + pendingPartialWithdrawal) { + this.index = pendingPartialWithdrawal.getIndex(); + this.amount = pendingPartialWithdrawal.getAmount(); + this.withdrawableEpoch = pendingPartialWithdrawal.getWithdrawableEpoch(); + } + + public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal + asInternalPendingPartialWithdrawal(final SpecVersion spec) { + final Optional schemaDefinitionsElectra = + spec.getSchemaDefinitions().toVersionElectra(); + if (schemaDefinitionsElectra.isEmpty()) { + throw new IllegalArgumentException( + "Could not create PendingBalanceDeposit for pre-electra spec"); + } + return schemaDefinitionsElectra + .get() + .getPendingPartialWithdrawalSchema() + .create( + SszUInt64.of(UInt64.valueOf(this.index)), + SszUInt64.of(this.amount), + SszUInt64.of(this.withdrawableEpoch)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java index 88f93843e2a..7703a6dd486 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java @@ -35,11 +35,11 @@ static SpecConfigElectra required(final SpecConfig specConfig) { UInt64 getMaxEffectiveBalanceElectra(); - UInt64 getPendingBalanceDepositsLimit(); + int getPendingBalanceDepositsLimit(); - UInt64 getPendingPartialWithdrawalsLimit(); + int getPendingPartialWithdrawalsLimit(); - UInt64 getPendingConsolidationsLimit(); + int getPendingConsolidationsLimit(); int getWhistleblowerRewardQuotientElectra(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 5bb79a1249f..7245ac97a54 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -29,9 +29,9 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int maxExecutionLayerExits; private final UInt64 minActivationBalance; private final UInt64 maxEffectiveBalanceElectra; - private final UInt64 pendingBalanceDepositsLimit; - private final UInt64 pendingPartialWithdrawalsLimit; - private final UInt64 pendingConsolidationsLimit; + private final int pendingBalanceDepositsLimit; + private final int pendingPartialWithdrawalsLimit; + private final int pendingConsolidationsLimit; private final int whistleblowerRewardQuotientElectra; private final int minSlashingPenaltyQuotientElectra; private final int maxPartialWithdrawalsPerPayload; @@ -49,9 +49,9 @@ public SpecConfigElectraImpl( final UInt64 maxPerEpochActivationExitChurnLimit, final UInt64 minActivationBalance, final UInt64 maxEffectiveBalanceElectra, - final UInt64 pendingBalanceDepositsLimit, - final UInt64 pendingPartialWithdrawalsLimit, - final UInt64 pendingConsolidationsLimit, + final int pendingBalanceDepositsLimit, + final int pendingPartialWithdrawalsLimit, + final int pendingConsolidationsLimit, final int whistleblowerRewardQuotientElectra, final int minSlashingPenaltyQuotientElectra, final int maxPartialWithdrawalsPerPayload, @@ -109,17 +109,17 @@ public UInt64 getMaxEffectiveBalanceElectra() { } @Override - public UInt64 getPendingBalanceDepositsLimit() { + public int getPendingBalanceDepositsLimit() { return pendingBalanceDepositsLimit; } @Override - public UInt64 getPendingPartialWithdrawalsLimit() { + public int getPendingPartialWithdrawalsLimit() { return pendingPartialWithdrawalsLimit; } @Override - public UInt64 getPendingConsolidationsLimit() { + public int getPendingConsolidationsLimit() { return pendingConsolidationsLimit; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index 06f36bfa9c5..ba44067388c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -38,9 +38,9 @@ public class ElectraBuilder implements ForkConfigBuilder getPendingBalanceDeposits() { + final int index = getSchema().getFieldIndex(PENDING_BALANCE_DEPOSITS); + return getAny(index); + } + + default SszList getPendingPartialWithdrawals() { + final int index = getSchema().getFieldIndex(PENDING_PARTIAL_WITHDRAWALS); + return getAny(index); + } + + default SszList getPendingConsolidations() { + final int index = getSchema().getFieldIndex(PENDING_CONSOLIDATIONS); + return getAny(index); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java index b0215c5259b..d09f52f863a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java @@ -38,10 +38,21 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateSchemaAltair; import tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; public class BeaconStateSchemaElectra extends AbstractBeaconStateSchema { public static final int DEPOSIT_RECEIPTS_START_INDEX = 28; + public static final int DEPOSIT_BALANCE_TO_CONSUME_INDEX = 29; + public static final int EXIT_BALANCE_TO_CONSUME_INDEX = 30; + public static final int EARLIEST_EXIT_EPOCH_INDEX = 31; + public static final int CONSOLIDATION_BALANCE_TO_CONSUME_INDEX = 32; + public static final int EARLIEST_CONSOLIDATION_EPOCH_INDEX = 33; + public static final int PENDING_BALANCE_DEPOSITS_INDEX = 34; + public static final int PENDING_PARTIAL_WITHDRAWALS_INDEX = 35; + public static final int PENDING_CONSOLIDATIONS_INDEX = 36; @VisibleForTesting BeaconStateSchemaElectra(final SpecConfig specConfig) { @@ -51,11 +62,18 @@ public class BeaconStateSchemaElectra private static List getUniqueFields(final SpecConfig specConfig) { final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = new HistoricalSummary.HistoricalSummarySchema(); + final PendingBalanceDeposit.PendingBalanceDepositSchema pendingBalanceDepositSchema = + new PendingBalanceDeposit.PendingBalanceDepositSchema(); + final PendingPartialWithdrawal.PendingPartialWithdrawalSchema pendingPartialWithdrawalSchema = + new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); + final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(specConfig); + final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema = + new PendingConsolidation.PendingConsolidationSchema(); final SszField latestExecutionPayloadHeaderField = new SszField( LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, - () -> new ExecutionPayloadHeaderSchemaElectra(SpecConfigElectra.required(specConfig))); + () -> new ExecutionPayloadHeaderSchemaElectra(specConfigElectra)); final SszField nextWithdrawalIndexField = new SszField( NEXT_WITHDRAWAL_INDEX, @@ -79,6 +97,54 @@ private static List getUniqueFields(final SpecConfig specConfig) { DEPOSIT_RECEIPTS_START_INDEX, BeaconStateFields.DEPOSIT_RECEIPTS_START_INDEX, () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField depositBalanceToConsumeField = + new SszField( + DEPOSIT_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField exitBalanceToConsumeField = + new SszField( + EXIT_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.EXIT_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField earliestExitEpochField = + new SszField( + EARLIEST_EXIT_EPOCH_INDEX, + BeaconStateFields.EARLIEST_EXIT_EPOCH, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField consolidationBalanceToConsumeField = + new SszField( + CONSOLIDATION_BALANCE_TO_CONSUME_INDEX, + BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField earliestConsolidationEpochField = + new SszField( + EARLIEST_CONSOLIDATION_EPOCH_INDEX, + BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField pendingBalanceDepositsField = + new SszField( + PENDING_BALANCE_DEPOSITS_INDEX, + BeaconStateFields.PENDING_BALANCE_DEPOSITS, + () -> + SszListSchema.create( + pendingBalanceDepositSchema, + specConfigElectra.getPendingBalanceDepositsLimit())); + final SszField pendingPartialWithdrawalsField = + new SszField( + PENDING_PARTIAL_WITHDRAWALS_INDEX, + BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS, + () -> + SszListSchema.create( + pendingPartialWithdrawalSchema, + specConfigElectra.getPendingPartialWithdrawalsLimit())); + final SszField pendingConsolidationsField = + new SszField( + PENDING_CONSOLIDATIONS_INDEX, + BeaconStateFields.PENDING_CONSOLIDATIONS, + () -> + SszListSchema.create( + pendingConsolidationSchema, specConfigElectra.getPendingConsolidationsLimit())); return Stream.concat( BeaconStateSchemaAltair.getUniqueFields(specConfig).stream(), Stream.of( @@ -86,7 +152,15 @@ private static List getUniqueFields(final SpecConfig specConfig) { nextWithdrawalIndexField, nextWithdrawalValidatorIndexField, historicalSummariesField, - depositReceiptsStartIndexField)) + depositReceiptsStartIndexField, + depositBalanceToConsumeField, + exitBalanceToConsumeField, + earliestExitEpochField, + consolidationBalanceToConsumeField, + earliestConsolidationEpochField, + pendingBalanceDepositsField, + pendingPartialWithdrawalsField, + pendingConsolidationsField)) .toList(); } @@ -153,4 +227,22 @@ private BeaconStateElectraImpl createEmptyBeaconStateImpl() { public BeaconStateElectraImpl createFromBackingNode(TreeNode node) { return new BeaconStateElectraImpl(this, node); } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingBalanceDepositsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_BALANCE_DEPOSITS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingPartialWithdrawalsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getPendingConsolidationsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PENDING_CONSOLIDATIONS)); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java index 7e03486adad..7db3042e85e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java @@ -14,11 +14,15 @@ package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; import java.util.Optional; +import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.MutableBeaconStateDeneb; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; public interface MutableBeaconStateElectra extends MutableBeaconStateDeneb, BeaconStateElectra { static MutableBeaconStateElectra required(final MutableBeaconState state) { @@ -43,4 +47,47 @@ default void setDepositReceiptsStartIndex(final UInt64 depositReceiptsStartIndex default Optional toMutableVersionElectra() { return Optional.of(this); } + + default void setDepositBalanceToConsume(final UInt64 depositBalanceToConsume) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME); + set(fieldIndex, SszUInt64.of(depositBalanceToConsume)); + } + + default void setExitBalanceToConsume(final UInt64 exitBalanceToConsume) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.EXIT_BALANCE_TO_CONSUME); + set(fieldIndex, SszUInt64.of(exitBalanceToConsume)); + } + + default void setEarliestExitEpoch(final UInt64 earliestExitEpoch) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.EARLIEST_EXIT_EPOCH); + set(fieldIndex, SszUInt64.of(earliestExitEpoch)); + } + + default void setConsolidationBalanceToConsume(final UInt64 consolidationBalanceToConsume) { + final int fieldIndex = + getSchema().getFieldIndex(BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME); + set(fieldIndex, SszUInt64.of(consolidationBalanceToConsume)); + } + + default void setEarliestConsolidationEpoch(final UInt64 earliestConsolidationEpoch) { + final int fieldIndex = + getSchema().getFieldIndex(BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH); + set(fieldIndex, SszUInt64.of(earliestConsolidationEpoch)); + } + + default void setPendingBalanceDeposits(SszList pendingBalanceDeposits) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_BALANCE_DEPOSITS); + set(fieldIndex, pendingBalanceDeposits); + } + + default void setPendingPartialWithdrawals( + SszList pendingPartialWithdrawals) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS); + set(fieldIndex, pendingPartialWithdrawals); + } + + default void setPendingConsolidations(SszList pendingConsolidations) { + final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_CONSOLIDATIONS); + set(fieldIndex, pendingConsolidations); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java new file mode 100644 index 00000000000..6973406b8d8 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java @@ -0,0 +1,69 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PendingBalanceDeposit extends Container2 { + public static class PendingBalanceDepositSchema + extends ContainerSchema2 { + + public PendingBalanceDepositSchema() { + super( + "PendingBalanceDeposit", + namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("amount", SszPrimitiveSchemas.UINT64_SCHEMA)); + } + + @Override + public PendingBalanceDeposit createFromBackingNode(final TreeNode node) { + return new PendingBalanceDeposit(this, node); + } + + public PendingBalanceDeposit create(final SszUInt64 index, final SszUInt64 amount) { + return new PendingBalanceDeposit(this, index, amount); + } + + public SszUInt64 getIndexSchema() { + return (SszUInt64) getFieldSchema0(); + } + + public SszUInt64 getAmountSchema() { + return (SszUInt64) getFieldSchema1(); + } + } + + private PendingBalanceDeposit( + final PendingBalanceDepositSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + private PendingBalanceDeposit( + PendingBalanceDepositSchema type, final SszUInt64 index, final SszUInt64 amount) { + super(type, index, amount); + } + + public int getIndex() { + return ((SszUInt64) get(0)).get().intValue(); + } + + public UInt64 getAmount() { + return ((SszUInt64) get(1)).get(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java new file mode 100644 index 00000000000..f67b4a2329e --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java @@ -0,0 +1,65 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; + +public class PendingConsolidation extends Container2 { + protected PendingConsolidation( + ContainerSchema2 schema) { + super(schema); + } + + public PendingConsolidation( + final PendingConsolidationSchema pendingConsolidationSchema, + final SszUInt64 sourceIndex, + final SszUInt64 targetIndex) { + super(pendingConsolidationSchema, sourceIndex, targetIndex); + } + + public static class PendingConsolidationSchema + extends ContainerSchema2 { + public PendingConsolidationSchema() { + super( + "PendingConsolidation", + namedSchema("source_index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("target_index", SszPrimitiveSchemas.UINT64_SCHEMA)); + } + + @Override + public PendingConsolidation createFromBackingNode(TreeNode node) { + return new PendingConsolidation(this, node); + } + + public PendingConsolidation create(final SszUInt64 sourceIndex, final SszUInt64 targetIndex) { + return new PendingConsolidation(this, sourceIndex, targetIndex); + } + } + + private PendingConsolidation(final PendingConsolidationSchema type, final TreeNode backingNode) { + super(type, backingNode); + } + + public int getSourceIndex() { + return ((SszUInt64) get(0)).get().intValue(); + } + + public int getTargetIndex() { + return ((SszUInt64) get(1)).get().intValue(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java new file mode 100644 index 00000000000..b0dacfae215 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java @@ -0,0 +1,78 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class PendingPartialWithdrawal + extends Container3 { + + public static class PendingPartialWithdrawalSchema + extends ContainerSchema3 { + public PendingPartialWithdrawalSchema() { + super( + "PendingPartialWithdrawal", + namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("amount", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("withdrawable_epoch", SszPrimitiveSchemas.UINT64_SCHEMA)); + } + + public PendingPartialWithdrawal create( + final SszUInt64 index, final SszUInt64 amount, final SszUInt64 withdrawableEpoch) { + return new PendingPartialWithdrawal(this, index, amount, withdrawableEpoch); + } + + public SszUInt64 getIndexSchema() { + return (SszUInt64) getFieldSchema0(); + } + + public SszUInt64 getAmountSchema() { + return (SszUInt64) getFieldSchema1(); + } + + public SszUInt64 getWithdrawableEpochSchema() { + return (SszUInt64) getFieldSchema2(); + } + + @Override + public PendingPartialWithdrawal createFromBackingNode(TreeNode node) { + return null; + } + } + + private PendingPartialWithdrawal( + PendingPartialWithdrawal.PendingPartialWithdrawalSchema type, + final SszUInt64 index, + final SszUInt64 amount, + final SszUInt64 withdrawableEpoch) { + super(type, index, amount, withdrawableEpoch); + } + + public int getIndex() { + return ((SszUInt64) get(0)).get().intValue(); + } + + public UInt64 getAmount() { + return ((SszUInt64) get(1)).get(); + } + + public UInt64 getWithdrawableEpoch() { + return ((SszUInt64) get(2)).get(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 5bc528b2576..811c82bf3ac 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -48,6 +48,9 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { @@ -76,6 +79,12 @@ public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { private final ExecutionLayerExitSchema executionLayerExitSchema; + private final PendingBalanceDeposit.PendingBalanceDepositSchema pendingBalanceDepositSchema; + + private final PendingPartialWithdrawal.PendingPartialWithdrawalSchema + pendingPartialWithdrawalSchema; + private final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema; + public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { super(specConfig); this.executionPayloadSchemaElectra = new ExecutionPayloadSchemaElectra(specConfig); @@ -126,6 +135,10 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { this.depositReceiptSchema = DepositReceipt.SSZ_SCHEMA; this.executionLayerExitSchema = ExecutionLayerExit.SSZ_SCHEMA; + this.pendingBalanceDepositSchema = new PendingBalanceDeposit.PendingBalanceDepositSchema(); + this.pendingPartialWithdrawalSchema = + new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); + this.pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); } public static SchemaDefinitionsElectra required(final SchemaDefinitions schemaDefinitions) { @@ -251,8 +264,21 @@ public ExecutionLayerExitSchema getExecutionLayerExitSchema() { return executionLayerExitSchema; } + public PendingBalanceDeposit.PendingBalanceDepositSchema getPendingBalanceDepositSchema() { + return pendingBalanceDepositSchema; + } + + public PendingPartialWithdrawal.PendingPartialWithdrawalSchema + getPendingPartialWithdrawalSchema() { + return pendingPartialWithdrawalSchema; + } + @Override public Optional toVersionElectra() { return Optional.of(this); } + + public PendingConsolidation.PendingConsolidationSchema getPendingConsolidationSchema() { + return pendingConsolidationSchema; + } } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index e0b79f22f73..06bd69d7a14 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -88,9 +88,9 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomUInt64(256000000000L), dataStructureUtil.randomUInt64(32000000000L), dataStructureUtil.randomUInt64(2048000000000L), - dataStructureUtil.randomUInt64(134217728L), - dataStructureUtil.randomUInt64(134217728L), - dataStructureUtil.randomUInt64(262144L), + dataStructureUtil.randomPositiveInt(134217728), + dataStructureUtil.randomPositiveInt(134217728), + dataStructureUtil.randomPositiveInt(262144), dataStructureUtil.randomPositiveInt(4096), dataStructureUtil.randomPositiveInt(4096), dataStructureUtil.randomPositiveInt(8), diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java index 3352d73aa28..381910f75d3 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java @@ -15,6 +15,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import java.util.List; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; @@ -28,6 +29,9 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; +import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; public class BeaconStateBuilderElectra extends AbstractBeaconStateBuilder< @@ -43,6 +47,17 @@ public class BeaconStateBuilderElectra private ExecutionPayloadHeader latestExecutionPayloadHeader; private UInt64 depositReceiptsStartIndex; + private UInt64 depositBalanceToConsume; + private UInt64 exitBalanceToConsume; + private UInt64 earliestExitEpoch; + + private UInt64 consolidationBalanceToConsume; + + private UInt64 earliestConsolidationEpoch; + + private SszList pendingBalanceDeposits; + private SszList pendingPartialWithdrawals; + private SszList pendingConsolidations; protected BeaconStateBuilderElectra( final SpecVersion spec, @@ -68,6 +83,14 @@ protected void setUniqueFields(final MutableBeaconStateElectra state) { state.setNextWithdrawalIndex(nextWithdrawalIndex); state.setNextWithdrawalValidatorIndex(nextWithdrawalValidatorIndex); state.setDepositReceiptsStartIndex(depositReceiptsStartIndex); + state.setDepositBalanceToConsume(depositBalanceToConsume); + state.setExitBalanceToConsume(exitBalanceToConsume); + state.setEarliestExitEpoch(earliestExitEpoch); + state.setConsolidationBalanceToConsume(consolidationBalanceToConsume); + state.setEarliestConsolidationEpoch(earliestConsolidationEpoch); + state.setPendingBalanceDeposits(pendingBalanceDeposits); + state.setPendingPartialWithdrawals(pendingPartialWithdrawals); + state.setPendingConsolidations(pendingConsolidations); } public static BeaconStateBuilderElectra create( @@ -102,6 +125,12 @@ public BeaconStateBuilderElectra depositReceiptsStartIndex( return this; } + public BeaconStateBuilderElectra depositBalanceToConsume(final UInt64 depositBalanceToConsume) { + checkNotNull(depositBalanceToConsume); + this.depositBalanceToConsume = depositBalanceToConsume; + return this; + } + private BeaconStateSchemaElectra getBeaconStateSchema() { return (BeaconStateSchemaElectra) spec.getSchemaDefinitions().getBeaconStateSchema(); } @@ -138,5 +167,16 @@ protected void initDefaults() { : UInt64.ZERO; this.depositReceiptsStartIndex = SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX; + this.depositBalanceToConsume = UInt64.ZERO; + this.exitBalanceToConsume = UInt64.ZERO; + this.earliestExitEpoch = UInt64.ZERO; + this.consolidationBalanceToConsume = UInt64.ZERO; + this.earliestConsolidationEpoch = UInt64.ZERO; + this.pendingBalanceDeposits = + schema.getPendingBalanceDepositsSchema().createFromElements(List.of()); + this.pendingPartialWithdrawals = + schema.getPendingPartialWithdrawalsSchema().createFromElements(List.of()); + this.pendingConsolidations = + schema.getPendingConsolidationsSchema().createFromElements(List.of()); } } From 220d422c419db882f4889b8853806e83087dcc40 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 13:56:14 +0400 Subject: [PATCH 13/70] Add DAS Ssz data structures and config constants (#12) * Add KZG prefixes to all struct in the 'kzg' package to avoid further ambiguity * Add DAS Ssz data structures and config constants --- .../teku/spec/config/SpecConfigElectra.java | 2 + .../spec/config/SpecConfigElectraImpl.java | 11 +- .../spec/config/builder/ElectraBuilder.java | 10 +- .../blobs/versions/electra/Cell.java | 33 ++++ .../blobs/versions/electra/CellSchema.java | 23 +++ .../blobs/versions/electra/DataColumn.java | 29 ++++ .../versions/electra/DataColumnSchema.java | 18 +++ .../versions/electra/DataColumnSidecar.java | 131 ++++++++++++++++ .../electra/DataColumnSidecarSchema.java | 148 ++++++++++++++++++ .../libp2p/rpc/DataColumnIdentifier.java | 60 +++++++ .../spec/config/presets/mainnet/electra.yaml | 4 +- .../spec/config/presets/minimal/electra.yaml | 4 +- .../spec/config/presets/swift/electra.yaml | 4 +- .../spec/config/SpecConfigElectraTest.java | 3 +- .../java/tech/pegasys/teku/kzg/CKZG4844.java | 16 +- .../tech/pegasys/teku/kzg/CellWithID.java | 11 -- .../main/java/tech/pegasys/teku/kzg/KZG.java | 24 +-- .../teku/kzg/{Cell.java => KZGCell.java} | 10 +- ...CellAndProof.java => KZGCellAndProof.java} | 4 +- .../teku/kzg/{CellID.java => KZGCellID.java} | 6 +- .../tech/pegasys/teku/kzg/KZGCellWithID.java | 11 ++ .../tech/pegasys/teku/kzg/CKZG4844Test.java | 15 +- .../impl/SszByteVectorSchemaImpl.java | 4 + 23 files changed, 525 insertions(+), 56 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/Cell.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java delete mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java rename infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/{Cell.java => KZGCell.java} (51%) rename infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/{CellAndProof.java => KZGCellAndProof.java} (53%) rename infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/{CellID.java => KZGCellID.java} (53%) create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java index 73fd3e8c364..7efe433769d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java @@ -39,6 +39,8 @@ static SpecConfigElectra required(final SpecConfig specConfig) { int getMaxExecutionLayerExits(); + UInt64 getFieldElementsPerCell(); + @Override Optional toVersionElectra(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 3d0ba197b03..4f66b51de67 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -26,17 +26,21 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int maxDepositReceiptsPerPayload; private final int maxExecutionLayerExits; + private final UInt64 fieldElementsPerCell; + public SpecConfigElectraImpl( final SpecConfigDeneb specConfig, final Bytes4 electraForkVersion, final UInt64 electraForkEpoch, final int maxDepositReceiptsPerPayload, - final int maxExecutionLayerExits) { + final int maxExecutionLayerExits, + final UInt64 fieldElementsPerCell) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; this.maxDepositReceiptsPerPayload = maxDepositReceiptsPerPayload; this.maxExecutionLayerExits = maxExecutionLayerExits; + this.fieldElementsPerCell = fieldElementsPerCell; } @Override @@ -59,6 +63,11 @@ public int getMaxExecutionLayerExits() { return maxExecutionLayerExits; } + @Override + public UInt64 getFieldElementsPerCell() { + return fieldElementsPerCell; + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index f1ab10bf21b..81f9633f275 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -32,6 +32,7 @@ public class ElectraBuilder implements ForkConfigBuilder { + + public CellSchema(final SpecConfigElectra specConfig) { + super(SpecConfigDeneb.BYTES_PER_FIELD_ELEMENT.longValue() * specConfig.getFieldElementsPerCell().longValue()); + } + + public Cell create(final Bytes bytes) { + return new Cell(this, bytes); + } + + @Override + public Cell createFromBackingNode(TreeNode node) { + return new Cell(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java new file mode 100644 index 00000000000..09747dbdb0b --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java @@ -0,0 +1,29 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.impl.SszListImpl; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; + +public class DataColumn extends SszListImpl implements SszList { + + DataColumn(DataColumnSchema schema, TreeNode node) { + super(schema, node); + } + + public String toBriefString() { + return isEmpty() ? "" : get(0).toBriefString(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java new file mode 100644 index 00000000000..efbca80520b --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java @@ -0,0 +1,18 @@ +package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigElectra; + +public class DataColumnSchema + extends AbstractSszListSchema { + + public DataColumnSchema(final SpecConfigElectra specConfig) { + super(new CellSchema(specConfig), specConfig.getMaxBlobCommitmentsPerBlock()); + } + + @Override + public DataColumn createFromBackingNode(TreeNode node) { + return new DataColumn(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java new file mode 100644 index 00000000000..ad80310c8e8 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java @@ -0,0 +1,131 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.logging.LogFormatter; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBytes32Vector; +import tech.pegasys.teku.infrastructure.ssz.containers.Container6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZGProof; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; +import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.datastructures.type.SszKZGProof; + +public class DataColumnSidecar + extends Container6< + DataColumnSidecar, + SszUInt64, + DataColumn, + SszList, + SszList, + SignedBeaconBlockHeader, + SszBytes32Vector> { + + DataColumnSidecar(final DataColumnSidecarSchema dataColumnSidecarSchema, final TreeNode backingTreeNode) { + super(dataColumnSidecarSchema, backingTreeNode); + } + +// public DataColumnSidecar( +// final DataColumnSidecarSchema schema, +// final UInt64 index, +// final Blob blob, +// final SszKZGCommitment sszKzgCommitment, +// final SszKZGProof sszKzgProof, +// final SignedBeaconBlockHeader signedBeaconBlockHeader, +// final List kzgCommitmentInclusionProof) { +// super( +// schema, +// SszUInt64.of(index), +// schema.getDataColumnSszSchema().create(blob.getBytes()), +// sszKzgCommitment, +// sszKzgProof, +// signedBeaconBlockHeader, +// schema +// .getKzgCommitmentInclusionProofSchema() +// .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); +// } +// +// public DataColumnSidecar( +// final DataColumnSidecarSchema schema, +// final UInt64 index, +// final Blob blob, +// final KZGCommitment kzgCommitment, +// final KZGProof kzgProof, +// final SignedBeaconBlockHeader signedBeaconBlockHeader, +// final List kzgCommitmentInclusionProof) { +// this( +// schema, +// index, +// blob, +// new SszKZGCommitment(kzgCommitment), +// new SszKZGProof(kzgProof), +// signedBeaconBlockHeader, +// kzgCommitmentInclusionProof); +// } + + public UInt64 getIndex() { + return getField0().get(); + } + + public DataColumn getDataColumn() { + return getField1(); + } + + public SszList getSszKZGCommitments() { + return getField2(); + } + + public SszList getSszKZGProofs() { + return getField3(); + } + + public SignedBeaconBlockHeader getSignedBeaconBlockHeader() { + return getField4(); + } + + public SszBytes32Vector getKzgCommitmentInclusionProof() { + return getField5(); + } + + public UInt64 getSlot() { + return getSignedBeaconBlockHeader().getMessage().getSlot(); + } + + public Bytes32 getBlockBodyRoot() { + return getSignedBeaconBlockHeader().getMessage().getBodyRoot(); + } + + public Bytes32 getBlockRoot() { + return getSignedBeaconBlockHeader().getMessage().getRoot(); + } + + public SlotAndBlockRoot getSlotAndBlockRoot() { + return new SlotAndBlockRoot(getSlot(), getBlockRoot()); + } + + public String toLogString() { + return LogFormatter.formatBlobSidecar( + getSlot(), + getBlockRoot(), + getIndex(), + getDataColumn().toBriefString(), + "" + getSszKZGCommitments().size(), + "" + getSszKZGProofs().size()); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java new file mode 100644 index 00000000000..b31bad2d35e --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java @@ -0,0 +1,148 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; + +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBytes32Vector; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszFieldName; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBytes32VectorSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeaderSchema; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitmentSchema; +import tech.pegasys.teku.spec.datastructures.type.SszKZGProof; +import tech.pegasys.teku.spec.datastructures.type.SszKZGProofSchema; + +public class DataColumnSidecarSchema + extends ContainerSchema6< + DataColumnSidecar, + SszUInt64, + DataColumn, + SszList, + SszList, + SignedBeaconBlockHeader, + SszBytes32Vector> { + + static final SszFieldName FIELD_BLOB = () -> "column"; + static final SszFieldName FIELD_SIGNED_BLOCK_HEADER = () -> "signed_block_header"; + static final SszFieldName FIELD_KZG_COMMITMENT_INCLUSION_PROOF = + () -> "kzg_commitment_inclusion_proof"; + + DataColumnSidecarSchema( + final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, + final DataColumnSchema dataColumnSchema, + SpecConfigElectra specConfig) { + super( + "DataColumnSidecar", + namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema(FIELD_BLOB, dataColumnSchema), + namedSchema("kzg_commitments", + SszListSchema.create( + SszKZGCommitmentSchema.INSTANCE, + specConfig.getMaxBlobCommitmentsPerBlock() + )), + namedSchema("kzg_proofs", + SszListSchema.create( + SszKZGProofSchema.INSTANCE, + specConfig.getMaxBlobCommitmentsPerBlock() + )), + namedSchema(FIELD_SIGNED_BLOCK_HEADER, signedBeaconBlockHeaderSchema), + namedSchema( + FIELD_KZG_COMMITMENT_INCLUSION_PROOF, + SszBytes32VectorSchema.create(specConfig.getKzgCommitmentInclusionProofDepth()))); + } + + @SuppressWarnings("unchecked") + public DataColumnSchema getDataColumnSszSchema() { + return (DataColumnSchema) getChildSchema(getFieldIndex(FIELD_BLOB)); + } + + public SignedBeaconBlockHeaderSchema getSignedBlockHeaderSchema() { + return (SignedBeaconBlockHeaderSchema) getFieldSchema4(); + } + + public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { + return (SszBytes32VectorSchema) + getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENT_INCLUSION_PROOF)); + } + +// public DataColumnSidecar create( +// final UInt64 index, +// final Blob blob, +// final SszKZGCommitment sszKzgCommitment, +// final SszKZGProof sszKzgProof, +// final SignedBeaconBlockHeader signedBeaconBlockHeader, +// final List kzgCommitmentInclusionProof) { +// return new DataColumnSidecar( +// this, +// index, +// blob, +// sszKzgCommitment, +// sszKzgProof, +// signedBeaconBlockHeader, +// kzgCommitmentInclusionProof); +// } +// +// public DataColumnSidecar create( +// final UInt64 index, +// final Bytes blob, +// final Bytes48 kzgCommitment, +// final Bytes48 kzgProof, +// final SignedBeaconBlockHeader signedBeaconBlockHeader, +// final List kzgCommitmentInclusionProof) { +// return create( +// index, +// new Blob(getBlobSchema(), blob), +// KZGCommitment.fromBytesCompressed(kzgCommitment), +// KZGProof.fromBytesCompressed(kzgProof), +// signedBeaconBlockHeader, +// kzgCommitmentInclusionProof); +// } +// +// public DataColumnSidecar create( +// final UInt64 index, +// final Blob blob, +// final KZGCommitment kzgCommitment, +// final KZGProof kzgProof, +// final SignedBeaconBlockHeader signedBeaconBlockHeader, +// final List kzgCommitmentInclusionProof) { +// return new DataColumnSidecar( +// this, +// index, +// blob, +// kzgCommitment, +// kzgProof, +// signedBeaconBlockHeader, +// kzgCommitmentInclusionProof); +// } +// +// public static DataColumnSidecarSchema create( +// final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, +// final BlobSchema blobSchema, +// final int kzgCommitmentInclusionProofDepth) { +// return new DataColumnSidecarSchema( +// signedBeaconBlockHeaderSchema, blobSchema, kzgCommitmentInclusionProofDepth); +// } + + @Override + public DataColumnSidecar createFromBackingNode(TreeNode node) { + return new DataColumnSidecar(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java new file mode 100644 index 00000000000..a1d43106c99 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java @@ -0,0 +1,60 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.ssz.containers.Container2; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class DataColumnIdentifier extends Container2 { + + public static class DataColumnIdentifierSchema + extends ContainerSchema2 { + + private DataColumnIdentifierSchema() { + super( + "DataColumnIdentifier", + namedSchema("block_root", SszPrimitiveSchemas.BYTES32_SCHEMA), + namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA)); + } + + @Override + public DataColumnIdentifier createFromBackingNode(final TreeNode node) { + return new DataColumnIdentifier(node); + } + } + + public static final DataColumnIdentifierSchema SSZ_SCHEMA = new DataColumnIdentifierSchema(); + + private DataColumnIdentifier(final TreeNode node) { + super(SSZ_SCHEMA, node); + } + + public DataColumnIdentifier(final Bytes32 root, final UInt64 index) { + super(SSZ_SCHEMA, SszBytes32.of(root), SszUInt64.of(index)); + } + + public Bytes32 getBlockRoot() { + return getField0().get(); + } + + public UInt64 getIndex() { + return getField1().get(); + } +} diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml index b920664fff2..7f0ce832b25 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml @@ -5,4 +5,6 @@ # 2**13 (= 8192) receipts MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192 # 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 \ No newline at end of file +MAX_EXECUTION_LAYER_EXITS: 16 + +FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml index ff5bd201834..c408446d874 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml @@ -5,4 +5,6 @@ # [customized] MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 # 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 \ No newline at end of file +MAX_EXECUTION_LAYER_EXITS: 16 + +FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml index d8a8b77dd38..49dc08a8be2 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml @@ -8,4 +8,6 @@ MAX_EXECUTION_LAYER_EXITS: 16 # Execution # --------------------------------------------------------------- # [customized] -MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 \ No newline at end of file +MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 + +FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index fce8ab80718..49ba3a04225 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -83,6 +83,7 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomBytes4(), dataStructureUtil.randomUInt64(999_999), dataStructureUtil.randomPositiveInt(16), - dataStructureUtil.randomPositiveInt(16)) {}; + dataStructureUtil.randomPositiveInt(16), + dataStructureUtil.randomUInt64(64)) {}; } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index db073d4f391..ad177c13de1 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -155,24 +155,24 @@ public KZGProof computeBlobKzgProof(final Bytes blob, final KZGCommitment kzgCom } @Override - public List computeCells(Bytes blob) { + public List computeCells(Bytes blob) { byte[] cellBytes = CKZG4844JNI.computeCells(blob.toArrayUnsafe()); - return Cell.splitBytes(Bytes.wrap(cellBytes)); + return KZGCell.splitBytes(Bytes.wrap(cellBytes)); } @Override - public List computeCellsAndProofs(Bytes blob) { + public List computeCellsAndProofs(Bytes blob) { CellsAndProofs cellsAndProofs = CKZG4844JNI.computeCellsAndProofs(blob.toArrayUnsafe()); - List cells = Cell.splitBytes(Bytes.wrap(cellsAndProofs.getCells())); + List cells = KZGCell.splitBytes(Bytes.wrap(cellsAndProofs.getCells())); List proofs = KZGProof.splitBytes(Bytes.wrap(cellsAndProofs.getProofs())); if (cells.size() != proofs.size()) throw new KZGException("Cells and proofs size differ"); return IntStream.range(0, cells.size()) - .mapToObj(i -> new CellAndProof(cells.get(i), proofs.get(i))) + .mapToObj(i -> new KZGCellAndProof(cells.get(i), proofs.get(i))) .toList(); } @Override - public boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof) { + public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { return CKZG4844JNI.verifyCellProof( commitment.toArrayUnsafe(), cellWithID.id().id().longValue(), @@ -181,13 +181,13 @@ public boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, } @Override - public List recoverCells(List cells) { + public List recoverCells(List cells) { long[] cellIds = cells.stream().mapToLong(c -> c.id().id().longValue()).toArray(); byte[] cellBytes = CKZG4844Utils.flattenBytes( cells.stream().map(c -> c.cell().bytes()).toList(), cells.size() * BYTES_PER_CELL ); byte[] recovered = CKZG4844JNI.recoverCells(cellIds, cellBytes); - return Cell.splitBytes(Bytes.wrap(recovered)); + return KZGCell.splitBytes(Bytes.wrap(recovered)); } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java deleted file mode 100644 index 2c29ad14ec9..00000000000 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellWithID.java +++ /dev/null @@ -1,11 +0,0 @@ -package tech.pegasys.teku.kzg; - -public record CellWithID( - Cell cell, - CellID id -) { - - static CellWithID fromCellAndColumn(Cell cell, int index) { - return new CellWithID(cell, CellID.fromCellColumnIndex(index)); - } -} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index 43f0fc67127..16de9db19aa 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -70,33 +70,33 @@ public KZGProof computeBlobKzgProof(final Bytes blob, final KZGCommitment kzgCom } @Override - public List computeCells(Bytes blob) { - List blobCells = Cell.splitBytes(blob); + public List computeCells(Bytes blob) { + List blobCells = KZGCell.splitBytes(blob); return Stream.concat( blobCells.stream(), - Stream.generate(() -> Cell.ZERO).limit(blobCells.size()) + Stream.generate(() -> KZGCell.ZERO).limit(blobCells.size()) ).toList(); } @Override - public List computeCellsAndProofs(Bytes blob) { + public List computeCellsAndProofs(Bytes blob) { return computeCells(blob) .stream() - .map(cell -> new CellAndProof(cell, KZGProof.fromBytesCompressed(Bytes48.ZERO))) + .map(cell -> new KZGCellAndProof(cell, KZGProof.fromBytesCompressed(Bytes48.ZERO))) .toList(); } @Override - public boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof) { + public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { return true; } @Override - public List recoverCells(List cells) { + public List recoverCells(List cells) { if (cells.size() < CELLS_PER_BLOB) throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); return cells.stream() - .map(CellWithID::cell) + .map(KZGCellWithID::cell) .limit(CELLS_PER_BLOB) .toList(); } @@ -119,13 +119,13 @@ boolean verifyBlobKzgProofBatch( // EIP-7594 methods - List computeCells(Bytes blob); + List computeCells(Bytes blob); - List computeCellsAndProofs(Bytes blob); + List computeCellsAndProofs(Bytes blob); - boolean verifyCellProof(KZGCommitment commitment, CellWithID cellWithID, KZGProof proof); + boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof); // TODO veryCellProofBatch() - List recoverCells(List cells); + List recoverCells(List cells); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java similarity index 51% rename from infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java rename to infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java index f5a2949436c..9a40aefb0ad 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/Cell.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java @@ -1,21 +1,19 @@ package tech.pegasys.teku.kzg; -import ethereum.ckzg4844.CKZG4844JNI; import org.apache.tuweni.bytes.Bytes; import java.util.List; -import java.util.stream.IntStream; import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; -public record Cell(Bytes bytes) { +public record KZGCell(Bytes bytes) { - static Cell ZERO = new Cell(Bytes.wrap(new byte[BYTES_PER_CELL])); + static KZGCell ZERO = new KZGCell(Bytes.wrap(new byte[BYTES_PER_CELL])); - static List splitBytes(Bytes bytes) { + static List splitBytes(Bytes bytes) { return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_CELL) .stream() - .map(Cell::new) + .map(KZGCell::new) .toList(); } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java similarity index 53% rename from infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java rename to infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java index 518aa4c842d..0c4c8aa1ef0 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellAndProof.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java @@ -1,7 +1,7 @@ package tech.pegasys.teku.kzg; -public record CellAndProof( - Cell cell, +public record KZGCellAndProof( + KZGCell cell, KZGProof proof ) { } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java similarity index 53% rename from infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java rename to infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java index 5b06a886df6..b6710d06ca4 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CellID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java @@ -2,10 +2,10 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; -public record CellID(UInt64 id) { +public record KZGCellID(UInt64 id) { - static CellID fromCellColumnIndex(int idx) { - return new CellID(UInt64.valueOf(idx)); + static KZGCellID fromCellColumnIndex(int idx) { + return new KZGCellID(UInt64.valueOf(idx)); } int getColumnIndex() { diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java new file mode 100644 index 00000000000..966e12074ec --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java @@ -0,0 +1,11 @@ +package tech.pegasys.teku.kzg; + +public record KZGCellWithID( + KZGCell cell, + KZGCellID id +) { + + static KZGCellWithID fromCellAndColumn(KZGCell cell, int index) { + return new KZGCellWithID(cell, KZGCellID.fromCellColumnIndex(index)); + } +} diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index d61c0b1ce1d..de0bdbaed0b 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -35,7 +35,6 @@ import java.util.stream.IntStream; import java.util.stream.Stream; -import kotlin.ranges.IntRange; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.AfterAll; @@ -292,14 +291,14 @@ public void testInvalidLengthG2PointInNewTrustedSetup() { @Test public void testComputeRecoverCells() { Bytes blob = getSampleBlob(); - List cells = CKZG.computeCells(blob); + List cells = CKZG.computeCells(blob); assertThat(cells).hasSize(CELLS_PER_EXT_BLOB); - List cellsToRecover = IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) - .mapToObj(i -> new CellWithID(cells.get(i), CellID.fromCellColumnIndex(i))) + List cellsToRecover = IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) + .mapToObj(i -> new KZGCellWithID(cells.get(i), KZGCellID.fromCellColumnIndex(i))) .toList(); - List recoveredCells = CKZG.recoverCells(cellsToRecover); + List recoveredCells = CKZG.recoverCells(cellsToRecover); assertThat(recoveredCells).isEqualTo(cells); } @@ -310,16 +309,16 @@ private List getSampleBlobs(final int count) { @Test public void testComputeAndVerifyCellProof() { Bytes blob = getSampleBlob(); - List cellAndProofs = CKZG.computeCellsAndProofs(blob); + List cellAndProofs = CKZG.computeCellsAndProofs(blob); KZGCommitment kzgCommitment = CKZG.blobToKzgCommitment(blob); for (int i = 0; i < cellAndProofs.size(); i++) { assertThat( - CKZG.verifyCellProof(kzgCommitment, CellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), cellAndProofs.get(i).proof()) + CKZG.verifyCellProof(kzgCommitment, KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), cellAndProofs.get(i).proof()) ).isTrue(); var invalidProof = cellAndProofs.get((i + 1) % cellAndProofs.size()).proof(); assertThat( - CKZG.verifyCellProof(kzgCommitment, CellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), invalidProof) + CKZG.verifyCellProof(kzgCommitment, KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), invalidProof) ).isFalse(); } } diff --git a/infrastructure/ssz/src/main/java/tech/pegasys/teku/infrastructure/ssz/schema/collections/impl/SszByteVectorSchemaImpl.java b/infrastructure/ssz/src/main/java/tech/pegasys/teku/infrastructure/ssz/schema/collections/impl/SszByteVectorSchemaImpl.java index de2fb29c730..c7d0abdc539 100644 --- a/infrastructure/ssz/src/main/java/tech/pegasys/teku/infrastructure/ssz/schema/collections/impl/SszByteVectorSchemaImpl.java +++ b/infrastructure/ssz/src/main/java/tech/pegasys/teku/infrastructure/ssz/schema/collections/impl/SszByteVectorSchemaImpl.java @@ -38,6 +38,10 @@ public SszByteVectorSchemaImpl( super(elementSchema, vectorLength); } + public SszByteVectorSchemaImpl(final long vectorLength) { + this(SszPrimitiveSchemas.BYTE_SCHEMA, vectorLength); + } + @Override protected DeserializableTypeDefinition createTypeDefinition() { return getElementSchema().equals(SszPrimitiveSchemas.BYTE_SCHEMA) From 139edadba150bc76b243f36776d4cba566fcb0cf Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 16:26:43 +0400 Subject: [PATCH 14/70] Add config options to set extra DAS subnets and advertise it with ENR (#13) --- .../teku/networking/eth2/ActiveEth2P2PNetwork.java | 4 ++++ .../networking/eth2/Eth2P2PNetworkBuilder.java | 6 ++++++ .../pegasys/teku/networking/eth2/P2PConfig.java | 14 ++++++++++++++ .../networking/eth2/ActiveEth2P2PNetworkTest.java | 2 ++ .../networking/eth2/Eth2P2PNetworkFactory.java | 1 + .../networking/p2p/discovery/DiscoveryNetwork.java | 7 +++++++ 6 files changed, 34 insertions(+) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 4851bf01133..295c591a743 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -70,6 +70,7 @@ public class ActiveEth2P2PNetwork extends DelegatingP2PNetwork impleme private final SubnetSubscriptionService syncCommitteeSubnetService; private final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; private final AtomicBoolean gossipStarted = new AtomicBoolean(false); + private final Optional dasExtraCustodySubnetCount; private final GossipForkManager gossipForkManager; @@ -93,6 +94,7 @@ public ActiveEth2P2PNetwork( final GossipEncoding gossipEncoding, final GossipConfigurator gossipConfigurator, final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider, + final Optional dasExtraCustodySubnetCount, final boolean allTopicsFilterEnabled) { super(discoveryNetwork); this.spec = spec; @@ -107,6 +109,7 @@ public ActiveEth2P2PNetwork( this.attestationSubnetService = attestationSubnetService; this.syncCommitteeSubnetService = syncCommitteeSubnetService; this.processedAttestationSubscriptionProvider = processedAttestationSubscriptionProvider; + this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount; this.allTopicsFilterEnabled = allTopicsFilterEnabled; } @@ -148,6 +151,7 @@ private synchronized void startGossip() { discoveryNetworkSyncCommitteeSubnetsSubscription = syncCommitteeSubnetService.subscribeToUpdates( discoveryNetwork::setSyncCommitteeSubnetSubscriptions); + dasExtraCustodySubnetCount.ifPresent(discoveryNetwork::setDASExtraCustodySubnetCount); gossipForkManager.configureGossipForEpoch(recentChainData.getCurrentEpoch().orElseThrow()); if (allTopicsFilterEnabled) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 4ba768c9504..352c06f0f63 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -171,6 +171,11 @@ public Eth2P2PNetwork build() { final GossipForkManager gossipForkManager = buildGossipForkManager(gossipEncoding, network); + final Optional dasExtraCustodySubnetCount = + config.getDasExtraCustodySubnetCount() == 0 + ? Optional.empty() + : Optional.of(config.getDasExtraCustodySubnetCount()); + return new ActiveEth2P2PNetwork( config.getSpec(), asyncRunner, @@ -184,6 +189,7 @@ public Eth2P2PNetwork build() { gossipEncoding, config.getGossipConfigurator(), processedAttestationSubscriptionProvider, + dasExtraCustodySubnetCount, config.isAllTopicsFilterEnabled()); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 1edeac1a496..99a8dcf46a4 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -52,6 +52,7 @@ public class P2PConfig { private final GossipEncoding gossipEncoding; private final int targetSubnetSubscriberCount; private final boolean subscribeAllSubnetsEnabled; + private final int dasExtraCustodySubnetCount; private final int peerRateLimit; private final int peerRequestLimit; private final int batchVerifyMaxThreads; @@ -69,6 +70,7 @@ private P2PConfig( final GossipEncoding gossipEncoding, final int targetSubnetSubscriberCount, final boolean subscribeAllSubnetsEnabled, + final int dasExtraCustodySubnetCount, final int peerRateLimit, final int peerRequestLimit, final int batchVerifyMaxThreads, @@ -83,6 +85,7 @@ private P2PConfig( this.gossipEncoding = gossipEncoding; this.targetSubnetSubscriberCount = targetSubnetSubscriberCount; this.subscribeAllSubnetsEnabled = subscribeAllSubnetsEnabled; + this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount; this.peerRateLimit = peerRateLimit; this.peerRequestLimit = peerRequestLimit; this.batchVerifyMaxThreads = batchVerifyMaxThreads; @@ -125,6 +128,10 @@ public boolean isSubscribeAllSubnetsEnabled() { return subscribeAllSubnetsEnabled; } + public int getDasExtraCustodySubnetCount() { + return dasExtraCustodySubnetCount; + } + public int getPeerRateLimit() { return peerRateLimit; } @@ -166,6 +173,7 @@ public static class Builder { private GossipEncoding gossipEncoding = GossipEncoding.SSZ_SNAPPY; private Integer targetSubnetSubscriberCount = DEFAULT_P2P_TARGET_SUBNET_SUBSCRIBER_COUNT; private Boolean subscribeAllSubnetsEnabled = DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; + private int dasExtraCustodySubnetCount = 0; private Integer peerRateLimit = DEFAULT_PEER_RATE_LIMIT; private Integer peerRequestLimit = DEFAULT_PEER_REQUEST_LIMIT; private int batchVerifyMaxThreads = DEFAULT_BATCH_VERIFY_MAX_THREADS; @@ -210,6 +218,7 @@ public P2PConfig build() { gossipEncoding, targetSubnetSubscriberCount, subscribeAllSubnetsEnabled, + dasExtraCustodySubnetCount, peerRateLimit, peerRequestLimit, batchVerifyMaxThreads, @@ -261,6 +270,11 @@ public Builder subscribeAllSubnetsEnabled(final Boolean subscribeAllSubnetsEnabl return this; } + public Builder dasExtraCustodySubnetCount(int dasExtraCustodySubnetCount) { + this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount; + return this; + } + public Builder peerRateLimit(final Integer peerRateLimit) { checkNotNull(peerRateLimit); if (peerRateLimit < 0) { diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java index 91810bc2cf4..d63b6dc26e8 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java @@ -289,6 +289,7 @@ void isCloseToInSync_shouldReturnFalseWhenEmptyCurrentEpoch() { gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, + Optional.empty(), true); assertThat(network.isCloseToInSync()).isFalse(); @@ -325,6 +326,7 @@ ActiveEth2P2PNetwork createNetwork() { gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, + Optional.empty(), true); } } diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index f6da52cb668..d5f4e5bb752 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -332,6 +332,7 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { gossipEncoding, GossipConfigurator.NOOP, processedAttestationSubscriptionProvider, + Optional.empty(), config.isAllTopicsFilterEnabled()); } } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java index fa731b22b9b..bde7a5dfa89 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.logging.StatusLogger; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.p2p.connection.ConnectionManager; import tech.pegasys.teku.networking.p2p.network.DelegatingP2PNetwork; @@ -44,6 +45,7 @@ public class DiscoveryNetwork

extends DelegatingP2PNetwork

{ public static final String ATTESTATION_SUBNET_ENR_FIELD = "attnets"; public static final String SYNC_COMMITTEE_SUBNET_ENR_FIELD = "syncnets"; + public static final String DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD = "custody_subnet_count"; public static final String ETH2_ENR_FIELD = "eth2"; private final Spec spec; @@ -137,6 +139,11 @@ public void setSyncCommitteeSubnetSubscriptions(Iterable subnetIds) { .sszSerialize()); } + public void setDASExtraCustodySubnetCount(int count) { + discoveryService.updateCustomENRField( + DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD, SszUInt64.of(UInt64.valueOf(count)).sszSerialize()); + } + public void setPreGenesisForkInfo() { final SpecVersion genesisSpec = spec.getGenesisSpec(); final Bytes4 genesisForkVersion = genesisSpec.getConfig().getGenesisForkVersion(); From 63ff01c45daa766ee7e0f8b6b084f235096cd01c Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 18:01:52 +0400 Subject: [PATCH 15/70] Add DATA_COLUMN_SIDECAR_SUBNET_COUNT to yaml configs --- .../tech/pegasys/teku/spec/config/configs/mainnet.yaml | 5 ++++- .../tech/pegasys/teku/spec/config/configs/minimal.yaml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 1def514bbf2..0cb5b6b5b82 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -145,4 +145,7 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in Electra:EIP7251] MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) + +# [New in Electra:EIP7594] +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index d913bb3b9cb..2d7b0722aaa 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -145,4 +145,7 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in Electra:EIP7251] MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) \ No newline at end of file +MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) + +# [New in Electra:EIP7594] +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 \ No newline at end of file From 8a7a866956df54baf7a000ff8c8ce262c95544cc Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 18:10:42 +0400 Subject: [PATCH 16/70] Add SpecConfigElectra.getDataColumnSidecarSubnetCount() --- .../java/tech/pegasys/teku/spec/Spec.java | 12 +++++++++ .../config/NetworkingSpecConfigElectra.java | 25 +++++++++++++++++++ .../teku/spec/config/SpecConfigElectra.java | 2 +- .../spec/config/SpecConfigElectraImpl.java | 10 +++++++- .../spec/config/builder/ElectraBuilder.java | 10 +++++++- .../spec/config/SpecConfigElectraTest.java | 3 ++- 6 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 2638f8b21f7..2c50e5da3b7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -17,6 +17,7 @@ import static tech.pegasys.teku.infrastructure.time.TimeUtilities.millisToSeconds; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.spec.SpecMilestone.DENEB; +import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; @@ -46,6 +47,7 @@ import tech.pegasys.teku.spec.cache.IndexedAttestationCache; import tech.pegasys.teku.spec.config.NetworkingSpecConfig; import tech.pegasys.teku.spec.config.NetworkingSpecConfigDeneb; +import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.config.SpecConfigAltair; import tech.pegasys.teku.spec.config.SpecConfigDeneb; @@ -215,6 +217,16 @@ public Optional getNetworkingConfigDeneb() { .map(specConfig -> (NetworkingSpecConfigDeneb) specConfig.getNetworkingConfig()); } + /** + * Networking config with Electra constants. Use {@link tech.pegasys.teku.spec.config.SpecConfigElectra#required(SpecConfig)} when + * you are sure that Electra is available, otherwise use this method + */ + public Optional getNetworkingConfigElectra() { + return Optional.ofNullable(forMilestone(ELECTRA)) + .map(SpecVersion::getConfig) + .map(specConfig -> (NetworkingSpecConfigElectra) specConfig.getNetworkingConfig()); + } + public SchemaDefinitions getGenesisSchemaDefinitions() { return getGenesisSpec().getSchemaDefinitions(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java new file mode 100644 index 00000000000..27e33e35bca --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java @@ -0,0 +1,25 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.config; + +/** + * Networking constants + * + *

These constants are unified among forks and are not overridden, new constant name is used if + * it's changed in the new fork + */ +public interface NetworkingSpecConfigElectra extends NetworkingSpecConfig { + + int getDataColumnSidecarSubnetCount(); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java index 674c8516808..dc43d5762dc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java @@ -17,7 +17,7 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -public interface SpecConfigElectra extends SpecConfigDeneb { +public interface SpecConfigElectra extends SpecConfigDeneb, NetworkingSpecConfigElectra { UInt64 UNSET_DEPOSIT_RECEIPTS_START_INDEX = UInt64.MAX_VALUE; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 1e2001341ea..ffa81652c08 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -38,6 +38,7 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int maxAttesterSlashingsElectra; private final int maxAttestationsElectra; private final int maxConsolidations; + private final int dataColumnSidecarSubnetCount; private final UInt64 fieldElementsPerCell; @@ -60,7 +61,8 @@ public SpecConfigElectraImpl( final int maxAttesterSlashingsElectra, final int maxAttestationsElectra, final int maxConsolidations, - final UInt64 fieldElementsPerCell) { + final UInt64 fieldElementsPerCell, + final int dataColumnSidecarSubnetCount) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; @@ -80,6 +82,7 @@ public SpecConfigElectraImpl( this.maxAttestationsElectra = maxAttestationsElectra; this.maxConsolidations = maxConsolidations; this.fieldElementsPerCell = fieldElementsPerCell; + this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; } @Override @@ -172,6 +175,11 @@ public UInt64 getFieldElementsPerCell() { return fieldElementsPerCell; } + @Override + public int getDataColumnSidecarSubnetCount() { + return dataColumnSidecarSubnetCount; + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index a74388b2575..9a0993bfd0b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -48,6 +48,7 @@ public class ElectraBuilder implements ForkConfigBuilder getValidationMap() { constants.put("maxAttesterSlashingsElectra", maxAttesterSlashingsElectra); constants.put("maxAttestationsElectra", maxAttestationsElectra); constants.put("maxConsolidations", maxConsolidations); + constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); return constants; } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index 4142b16ced7..2a8a9a5a56c 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -97,6 +97,7 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomPositiveInt(8), - dataStructureUtil.randomUInt64(64)) {}; + dataStructureUtil.randomUInt64(64), + dataStructureUtil.randomPositiveInt(64)) {}; } } From ce351f59ac9b947e4e8d9b961881858c131f5df4 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 18:17:48 +0400 Subject: [PATCH 17/70] Add data_column_sidecar_ gossip topics --- .../eth2/gossip/topics/GossipTopicName.java | 4 ++++ .../networking/eth2/gossip/topics/GossipTopics.java | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java index 87b48b507fb..fd45a5a6d32 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopicName.java @@ -40,4 +40,8 @@ public static String getSyncCommitteeSubnetTopicName(final int subnetId) { public static String getBlobSidecarSubnetTopicName(final int subnetId) { return "blob_sidecar_" + subnetId; } + + public static String getDataColumnSidecarSubnetTopicName(final int subnetId) { + return "data_column_sidecar_" + subnetId; + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java index 8f71a1ac466..af3740f9643 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java @@ -63,6 +63,12 @@ public static String getBlobSidecarSubnetTopic( forkDigest, GossipTopicName.getBlobSidecarSubnetTopicName(subnetId), gossipEncoding); } + public static String getDataColumnSidecarSubnetTopic( + final Bytes4 forkDigest, final int subnetId, final GossipEncoding gossipEncoding) { + return getTopic( + forkDigest, GossipTopicName.getDataColumnSidecarSubnetTopicName(subnetId), gossipEncoding); + } + public static Set getAllTopics( final GossipEncoding gossipEncoding, final Bytes4 forkDigest, final Spec spec) { final Set topics = new HashSet<>(); @@ -78,6 +84,11 @@ public static Set getAllTopics( topics.add(getBlobSidecarSubnetTopic(forkDigest, i, gossipEncoding)); } } + spec.getNetworkingConfigElectra().ifPresent(electraNetworkConfig -> { + for (int i = 0; i < electraNetworkConfig.getDataColumnSidecarSubnetCount(); i++) { + topics.add(getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)); + } + }); for (GossipTopicName topicName : GossipTopicName.values()) { topics.add(GossipTopics.getTopic(forkDigest, topicName, gossipEncoding)); } From 4374c71f98843030426cb02631d86f81ffa8a973 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 19:16:41 +0400 Subject: [PATCH 18/70] Add DataColumnSidecarSchema spec definitions --- .../electra/DataColumnSidecarSchema.java | 16 ++++++++-------- .../spec/schemas/SchemaDefinitionsElectra.java | 13 +++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java index b31bad2d35e..84dd691867e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java @@ -48,7 +48,7 @@ public class DataColumnSidecarSchema DataColumnSidecarSchema( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, final DataColumnSchema dataColumnSchema, - SpecConfigElectra specConfig) { + final SpecConfigElectra specConfig) { super( "DataColumnSidecar", namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), @@ -133,13 +133,13 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { // kzgCommitmentInclusionProof); // } // -// public static DataColumnSidecarSchema create( -// final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, -// final BlobSchema blobSchema, -// final int kzgCommitmentInclusionProofDepth) { -// return new DataColumnSidecarSchema( -// signedBeaconBlockHeaderSchema, blobSchema, kzgCommitmentInclusionProofDepth); -// } + public static DataColumnSidecarSchema create( + final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, + final DataColumnSchema dataColumnSchema, + final SpecConfigElectra specConfig) { + return new DataColumnSidecarSchema( + signedBeaconBlockHeaderSchema, dataColumnSchema, specConfig); + } @Override public DataColumnSidecar createFromBackingNode(TreeNode node) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 811c82bf3ac..6434e122a48 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -17,9 +17,12 @@ import java.util.Optional; import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSchema; import tech.pegasys.teku.spec.datastructures.blocks.BlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.BlockContainerSchema; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockSchema; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainerSchema; @@ -85,6 +88,9 @@ public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { pendingPartialWithdrawalSchema; private final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema; + private final DataColumnSchema dataColumnSchema; + private final DataColumnSidecarSchema dataColumnSidecarSchema; + public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { super(specConfig); this.executionPayloadSchemaElectra = new ExecutionPayloadSchemaElectra(specConfig); @@ -139,6 +145,9 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { this.pendingPartialWithdrawalSchema = new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); this.pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); + + this.dataColumnSchema = new DataColumnSchema(specConfig); + this.dataColumnSidecarSchema = DataColumnSidecarSchema.create(SignedBeaconBlockHeader.SSZ_SCHEMA, dataColumnSchema, specConfig); } public static SchemaDefinitionsElectra required(final SchemaDefinitions schemaDefinitions) { @@ -281,4 +290,8 @@ public Optional toVersionElectra() { public PendingConsolidation.PendingConsolidationSchema getPendingConsolidationSchema() { return pendingConsolidationSchema; } + + public DataColumnSidecarSchema getDataColumnSidecarSchema() { + return dataColumnSidecarSchema; + } } From 4cd8af2c0833275c85e025c4889cd6abe1181b27 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 19:17:03 +0400 Subject: [PATCH 19/70] Add DataColumnSidecarGossipManager and DataColumnSidecarGossipChannel --- .../java/tech/pegasys/teku/spec/Spec.java | 8 + .../DataColumnSidecarGossipChannel.java | 29 +++ .../DataColumnSidecarGossipManager.java | 173 ++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 2c50e5da3b7..a391eb6af59 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -51,10 +51,12 @@ import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.config.SpecConfigAltair; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; @@ -943,6 +945,12 @@ public UInt64 computeSubnetForBlobSidecar(final BlobSidecar blobSidecar) { return blobSidecar.getIndex().mod(specConfigDeneb.getBlobSidecarSubnetCount()); } + public UInt64 computeSubnetForDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { + final SpecConfig config = atSlot(dataColumnSidecar.getSlot()).getConfig(); + final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(config); + return dataColumnSidecar.getIndex().mod(specConfigElectra.getDataColumnSidecarSubnetCount()); + } + public Optional computeFirstSlotWithBlobSupport() { return getSpecConfigDeneb() .map(SpecConfigDeneb::getDenebForkEpoch) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java new file mode 100644 index 00000000000..6627093373f --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java @@ -0,0 +1,29 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip; + +import java.util.List; +import tech.pegasys.teku.infrastructure.events.VoidReturningChannelInterface; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +public interface DataColumnSidecarGossipChannel extends VoidReturningChannelInterface { + + DataColumnSidecarGossipChannel NOOP = dataColumnSidecar -> {}; + + default void publishDataColumnSidecars(final List dataColumnSidecars) { + dataColumnSidecars.forEach(this::publishDataColumnSidecar); + } + + void publishDataColumnSidecar(DataColumnSidecar dataColumnSidecar); +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java new file mode 100644 index 00000000000..04e57407116 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -0,0 +1,173 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip; + +import com.google.common.annotations.VisibleForTesting; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import java.util.Optional; +import java.util.stream.IntStream; +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationMilestoneValidator; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.Eth2TopicHandler; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.networking.p2p.gossip.TopicChannel; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecarSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class DataColumnSidecarGossipManager implements GossipManager { + + private final Spec spec; + private final GossipNetwork gossipNetwork; + private final GossipEncoding gossipEncoding; + private final Int2ObjectMap> subnetIdToTopicHandler; + + private final Int2ObjectMap subnetIdToChannel = new Int2ObjectOpenHashMap<>(); + + public static DataColumnSidecarGossipManager create( + final RecentChainData recentChainData, + final Spec spec, + final AsyncRunner asyncRunner, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final ForkInfo forkInfo, + final OperationProcessor processor) { + final SpecVersion forkSpecVersion = spec.atEpoch(forkInfo.getFork().getEpoch()); + final DataColumnSidecarSchema gossipType = + SchemaDefinitionsElectra.required(forkSpecVersion.getSchemaDefinitions()) + .getDataColumnSidecarSchema(); + final Int2ObjectMap> subnetIdToTopicHandler = + new Int2ObjectOpenHashMap<>(); + final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(forkSpecVersion.getConfig()); + IntStream.range(0, specConfigElectra.getDataColumnSidecarSubnetCount()) + .forEach( + subnetId -> { + final Eth2TopicHandler topicHandler = + createDataColumnSidecarTopicHandler( + subnetId, + recentChainData, + spec, + asyncRunner, + processor, + gossipEncoding, + forkInfo, + gossipType); + subnetIdToTopicHandler.put(subnetId, topicHandler); + }); + return new DataColumnSidecarGossipManager( + spec, gossipNetwork, gossipEncoding, subnetIdToTopicHandler); + } + + private DataColumnSidecarGossipManager( + final Spec spec, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final Int2ObjectMap> subnetIdToTopicHandler) { + this.spec = spec; + this.gossipNetwork = gossipNetwork; + this.gossipEncoding = gossipEncoding; + this.subnetIdToTopicHandler = subnetIdToTopicHandler; + } + + public void publishDataColumnSidecar(final DataColumnSidecar message) { + final int subnetId = spec.computeSubnetForDataColumnSidecar(message).intValue(); + Optional.ofNullable(subnetIdToChannel.get(subnetId)) + .ifPresent(channel -> channel.gossip(gossipEncoding.encode(message))); + } + + @VisibleForTesting + Eth2TopicHandler getTopicHandler(final int subnetId) { + return subnetIdToTopicHandler.get(subnetId); + } + + @Override + public void subscribe() { + subnetIdToTopicHandler + .int2ObjectEntrySet() + .forEach( + entry -> { + final Eth2TopicHandler topicHandler = entry.getValue(); + final TopicChannel channel = + gossipNetwork.subscribe(topicHandler.getTopic(), topicHandler); + subnetIdToChannel.put(entry.getIntKey(), channel); + }); + } + + @Override + public void unsubscribe() { + subnetIdToChannel.values().forEach(TopicChannel::close); + subnetIdToChannel.clear(); + } + + @Override + public boolean isEnabledDuringOptimisticSync() { + return true; + } + + private static Eth2TopicHandler createDataColumnSidecarTopicHandler( + final int subnetId, + final RecentChainData recentChainData, + final Spec spec, + final AsyncRunner asyncRunner, + final OperationProcessor processor, + final GossipEncoding gossipEncoding, + final ForkInfo forkInfo, + final DataColumnSidecarSchema gossipType) { + return new Eth2TopicHandler<>( + recentChainData, + asyncRunner, + new TopicSubnetIdAwareOperationProcessor(spec, subnetId, processor), + gossipEncoding, + forkInfo.getForkDigest(spec), + GossipTopicName.getDataColumnSidecarSubnetTopicName(subnetId), + new OperationMilestoneValidator<>( + spec, + forkInfo.getFork(), + dataColumnSidecar -> spec.computeEpochAtSlot(dataColumnSidecar.getSlot())), + gossipType, + spec.getNetworkingConfig()); + } + + private record TopicSubnetIdAwareOperationProcessor( + Spec spec, int subnetId, OperationProcessor delegate) + implements OperationProcessor { + + @Override + public SafeFuture process( + final DataColumnSidecar dataColumnSidecar, final Optional arrivalTimestamp) { + final int dataColumnSidecarSubnet = spec.computeSubnetForDataColumnSidecar(dataColumnSidecar).intValue(); + if (dataColumnSidecarSubnet != subnetId) { + return SafeFuture.completedFuture( + InternalValidationResult.reject( + "blob sidecar with subnet_id %s does not match the topic subnet_id %d", + dataColumnSidecarSubnet, subnetId)); + } + return delegate.process(dataColumnSidecar, arrivalTimestamp); + } + } +} From b37374e8acbad1591a883fe3b29feebe610c64c5 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 12 Apr 2024 19:26:01 +0400 Subject: [PATCH 20/70] Add DataColumn to GossipForkManager --- .../teku/networking/eth2/ActiveEth2P2PNetwork.java | 2 ++ .../networking/eth2/gossip/forks/GossipForkManager.java | 9 +++++++++ .../eth2/gossip/forks/GossipForkSubscriptions.java | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 4851bf01133..2fd88b234c1 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.config.Eth2Context; import tech.pegasys.teku.networking.eth2.gossip.config.GossipConfigurator; @@ -130,6 +131,7 @@ private synchronized void startup() { processedAttestationSubscriptionProvider.subscribe(gossipForkManager::publishAttestation); eventChannels.subscribe(BlockGossipChannel.class, gossipForkManager::publishBlock); eventChannels.subscribe(BlobSidecarGossipChannel.class, gossipForkManager::publishBlobSidecar); + eventChannels.subscribe(DataColumnSidecarGossipChannel.class, gossipForkManager::publishDataColumnSidecar); if (isCloseToInSync()) { startGossip(); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java index 31577e554c9..e7fe30ff904 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java @@ -33,6 +33,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -171,6 +172,14 @@ public synchronized void publishBlobSidecar(final BlobSidecar blobSidecar) { GossipForkSubscriptions::publishBlobSidecar); } + public synchronized void publishDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { + publishMessage( + dataColumnSidecar.getSlot(), + dataColumnSidecar, + "data column sidecar", + GossipForkSubscriptions::publishDataColumnSidecar); + } + public synchronized void publishSyncCommitteeMessage( final ValidatableSyncCommitteeMessage message) { publishMessage( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java index 0bee5049bff..5e3ddd6a951 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java @@ -17,6 +17,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -39,6 +40,10 @@ public interface GossipForkSubscriptions { void publishBlock(SignedBeaconBlock block); + default void publishDataColumnSidecar(DataColumnSidecar blobSidecar) { + // since Electra + } + default void publishBlobSidecar(BlobSidecar blobSidecar) { // since Deneb } From ace797f9ea268e7e3f3f060613ee3bac92300d18 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Mon, 15 Apr 2024 21:02:08 +0400 Subject: [PATCH 21/70] Add DataColumnSidecarSubnetSubscriptions Replace DataColumnSidecarGossipManager with attestation like version --- .../DataColumnSidecarGossipManager.java | 174 ++++-------------- .../DataColumnSidecarSubnetSubscriptions.java | 107 +++++++++++ 2 files changed, 143 insertions(+), 138 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index 04e57407116..cfa96756554 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -1,5 +1,5 @@ /* - * Copyright Consensys Software Inc., 2023 + * Copyright Consensys Software Inc., 2022 * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at @@ -13,161 +13,59 @@ package tech.pegasys.teku.networking.eth2.gossip; -import com.google.common.annotations.VisibleForTesting; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.Optional; -import java.util.stream.IntStream; -import tech.pegasys.teku.infrastructure.async.AsyncRunner; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; -import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; -import tech.pegasys.teku.networking.eth2.gossip.topics.OperationMilestoneValidator; -import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; -import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.Eth2TopicHandler; -import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; -import tech.pegasys.teku.networking.p2p.gossip.TopicChannel; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecarSchema; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetSubscriptions; +import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetSubscriptions; +import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; -import tech.pegasys.teku.spec.datastructures.state.ForkInfo; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; -import tech.pegasys.teku.statetransition.validation.InternalValidationResult; -import tech.pegasys.teku.storage.client.RecentChainData; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; public class DataColumnSidecarGossipManager implements GossipManager { + private static final Logger LOG = LogManager.getLogger(); - private final Spec spec; - private final GossipNetwork gossipNetwork; - private final GossipEncoding gossipEncoding; - private final Int2ObjectMap> subnetIdToTopicHandler; + private final DataColumnSidecarSubnetSubscriptions subnetSubscriptions; - private final Int2ObjectMap subnetIdToChannel = new Int2ObjectOpenHashMap<>(); - - public static DataColumnSidecarGossipManager create( - final RecentChainData recentChainData, - final Spec spec, - final AsyncRunner asyncRunner, - final GossipNetwork gossipNetwork, - final GossipEncoding gossipEncoding, - final ForkInfo forkInfo, - final OperationProcessor processor) { - final SpecVersion forkSpecVersion = spec.atEpoch(forkInfo.getFork().getEpoch()); - final DataColumnSidecarSchema gossipType = - SchemaDefinitionsElectra.required(forkSpecVersion.getSchemaDefinitions()) - .getDataColumnSidecarSchema(); - final Int2ObjectMap> subnetIdToTopicHandler = - new Int2ObjectOpenHashMap<>(); - final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(forkSpecVersion.getConfig()); - IntStream.range(0, specConfigElectra.getDataColumnSidecarSubnetCount()) - .forEach( - subnetId -> { - final Eth2TopicHandler topicHandler = - createDataColumnSidecarTopicHandler( - subnetId, - recentChainData, - spec, - asyncRunner, - processor, - gossipEncoding, - forkInfo, - gossipType); - subnetIdToTopicHandler.put(subnetId, topicHandler); - }); - return new DataColumnSidecarGossipManager( - spec, gossipNetwork, gossipEncoding, subnetIdToTopicHandler); + public DataColumnSidecarGossipManager( + final DataColumnSidecarSubnetSubscriptions attestationSubnetSubscriptions) { + subnetSubscriptions = attestationSubnetSubscriptions; } - private DataColumnSidecarGossipManager( - final Spec spec, - final GossipNetwork gossipNetwork, - final GossipEncoding gossipEncoding, - final Int2ObjectMap> subnetIdToTopicHandler) { - this.spec = spec; - this.gossipNetwork = gossipNetwork; - this.gossipEncoding = gossipEncoding; - this.subnetIdToTopicHandler = subnetIdToTopicHandler; + public void onNewDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { + subnetSubscriptions + .gossip(dataColumnSidecar) + .finish( + __ -> { + LOG.debug( + "Successfully published attestation for slot {}", dataColumnSidecar); + }, + error -> { + LOG.warn( + "Error publishing data column for slot {}", dataColumnSidecar); + }); } - public void publishDataColumnSidecar(final DataColumnSidecar message) { - final int subnetId = spec.computeSubnetForDataColumnSidecar(message).intValue(); - Optional.ofNullable(subnetIdToChannel.get(subnetId)) - .ifPresent(channel -> channel.gossip(gossipEncoding.encode(message))); + public void subscribeToSubnetId(final int subnetId) { + LOG.trace("Subscribing to subnet ID {}", subnetId); + subnetSubscriptions.subscribeToSubnetId(subnetId); } - @VisibleForTesting - Eth2TopicHandler getTopicHandler(final int subnetId) { - return subnetIdToTopicHandler.get(subnetId); + public void unsubscribeFromSubnetId(final int subnetId) { + LOG.trace("Unsubscribing to subnet ID {}", subnetId); + subnetSubscriptions.unsubscribeFromSubnetId(subnetId); } @Override public void subscribe() { - subnetIdToTopicHandler - .int2ObjectEntrySet() - .forEach( - entry -> { - final Eth2TopicHandler topicHandler = entry.getValue(); - final TopicChannel channel = - gossipNetwork.subscribe(topicHandler.getTopic(), topicHandler); - subnetIdToChannel.put(entry.getIntKey(), channel); - }); + subnetSubscriptions.subscribe(); } @Override public void unsubscribe() { - subnetIdToChannel.values().forEach(TopicChannel::close); - subnetIdToChannel.clear(); - } - - @Override - public boolean isEnabledDuringOptimisticSync() { - return true; - } - - private static Eth2TopicHandler createDataColumnSidecarTopicHandler( - final int subnetId, - final RecentChainData recentChainData, - final Spec spec, - final AsyncRunner asyncRunner, - final OperationProcessor processor, - final GossipEncoding gossipEncoding, - final ForkInfo forkInfo, - final DataColumnSidecarSchema gossipType) { - return new Eth2TopicHandler<>( - recentChainData, - asyncRunner, - new TopicSubnetIdAwareOperationProcessor(spec, subnetId, processor), - gossipEncoding, - forkInfo.getForkDigest(spec), - GossipTopicName.getDataColumnSidecarSubnetTopicName(subnetId), - new OperationMilestoneValidator<>( - spec, - forkInfo.getFork(), - dataColumnSidecar -> spec.computeEpochAtSlot(dataColumnSidecar.getSlot())), - gossipType, - spec.getNetworkingConfig()); - } - - private record TopicSubnetIdAwareOperationProcessor( - Spec spec, int subnetId, OperationProcessor delegate) - implements OperationProcessor { - - @Override - public SafeFuture process( - final DataColumnSidecar dataColumnSidecar, final Optional arrivalTimestamp) { - final int dataColumnSidecarSubnet = spec.computeSubnetForDataColumnSidecar(dataColumnSidecar).intValue(); - if (dataColumnSidecarSubnet != subnetId) { - return SafeFuture.completedFuture( - InternalValidationResult.reject( - "blob sidecar with subnet_id %s does not match the topic subnet_id %d", - dataColumnSidecarSubnet, subnetId)); - } - return delegate.process(dataColumnSidecar, arrivalTimestamp); - } + subnetSubscriptions.unsubscribe(); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java new file mode 100644 index 00000000000..b54ee877c97 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java @@ -0,0 +1,107 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip.subnets; + +import com.google.common.annotations.VisibleForTesting; +import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopicName; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopics; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationMilestoneValidator; +import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; +import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.Eth2TopicHandler; +import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.SingleAttestationTopicHandler; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.networking.p2p.gossip.TopicChannel; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.datastructures.operations.Attestation; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.storage.client.RecentChainData; + +import java.util.Optional; + +public class DataColumnSidecarSubnetSubscriptions extends CommitteeSubnetSubscriptions { + + private final Spec spec; + private final AsyncRunner asyncRunner; + private final RecentChainData recentChainData; + private final OperationProcessor processor; + private final ForkInfo forkInfo; + private final int subnetCount; + private final DataColumnSidecarSchema dataColumnSidecarSchema; + + public DataColumnSidecarSubnetSubscriptions( + final Spec spec, + final AsyncRunner asyncRunner, + final GossipNetwork gossipNetwork, + final GossipEncoding gossipEncoding, + final RecentChainData recentChainData, + final OperationProcessor processor, + final ForkInfo forkInfo) { + super(gossipNetwork, gossipEncoding); + this.spec = spec; + this.asyncRunner = asyncRunner; + this.recentChainData = recentChainData; + this.processor = processor; + this.forkInfo = forkInfo; + SpecVersion specVersion = spec.forMilestone(SpecMilestone.ELECTRA); + this.dataColumnSidecarSchema = specVersion.getSchemaDefinitions().toVersionElectra().orElseThrow() + .getDataColumnSidecarSchema(); + this.subnetCount = specVersion.getConfig().toVersionElectra().orElseThrow() + .getDataColumnSidecarSubnetCount(); + } + + public SafeFuture gossip(final DataColumnSidecar sidecar) { + int subnetId = computeSubnetForSidecar(sidecar); + final String topic = + GossipTopics.getAttestationSubnetTopic( + forkInfo.getForkDigest(spec), subnetId, gossipEncoding); + return gossipNetwork.gossip(topic, gossipEncoding.encode(sidecar)); + } + + @VisibleForTesting + Optional getChannel(final DataColumnSidecar sidecar) { + int subnetId = computeSubnetForSidecar(sidecar); + return getChannelForSubnet(subnetId); + } + + @Override + protected Eth2TopicHandler createTopicHandler(final int subnetId) { + final String topicName = GossipTopicName.getDataColumnSidecarSubnetTopicName(subnetId); + return new Eth2TopicHandler<>( + recentChainData, + asyncRunner, + processor, + gossipEncoding, + forkInfo.getForkDigest(spec), + topicName, + new OperationMilestoneValidator<>( + spec, + forkInfo.getFork(), + message -> spec.computeEpochAtSlot(message.getSlot())), + dataColumnSidecarSchema, + spec.getNetworkingConfig()); + } + + private int computeSubnetForSidecar(final DataColumnSidecar sidecar) { + return sidecar.getIndex().mod(subnetCount).intValue(); + } +} From 25cb4cd30c5178fee34f73a3b24fcda41f5eddc9 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Mon, 15 Apr 2024 21:23:55 +0400 Subject: [PATCH 22/70] Add DataColumnSidecar support to GossipForkSubscriptions --- .../DataColumnSidecarGossipManager.java | 2 +- .../gossip/forks/GossipForkSubscriptions.java | 16 ++++-- .../GossipForkSubscriptionsElectra.java | 51 ++++++++++++++++++- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index cfa96756554..21d8d91b13e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -35,7 +35,7 @@ public DataColumnSidecarGossipManager( subnetSubscriptions = attestationSubnetSubscriptions; } - public void onNewDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { + public void publish(final DataColumnSidecar dataColumnSidecar) { subnetSubscriptions .gossip(dataColumnSidecar) .finish( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java index 5e3ddd6a951..cea2c99e5c3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java @@ -40,10 +40,6 @@ public interface GossipForkSubscriptions { void publishBlock(SignedBeaconBlock block); - default void publishDataColumnSidecar(DataColumnSidecar blobSidecar) { - // since Electra - } - default void publishBlobSidecar(BlobSidecar blobSidecar) { // since Deneb } @@ -75,4 +71,16 @@ default void unsubscribeFromSyncCommitteeSubnet(int subnetId) { } default void publishSignedBlsToExecutionChangeMessage(SignedBlsToExecutionChange message) {} + + default void publishDataColumnSidecar(DataColumnSidecar blobSidecar) { + // since Electra + } + + default void subscribeToDataColumnSidecarSubnet(int subnetId) { + // since Electra + } + + default void unsubscribeFromDataColumnSidecarSubnet(int subnetId) { + // since Electra + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java index 5ef8d21e34b..fcf543cbc44 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java @@ -15,12 +15,15 @@ import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.async.AsyncRunner; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipManager; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -29,10 +32,14 @@ import tech.pegasys.teku.spec.datastructures.operations.versions.altair.SignedContributionAndProof; import tech.pegasys.teku.spec.datastructures.operations.versions.altair.ValidatableSyncCommitteeMessage; import tech.pegasys.teku.spec.datastructures.state.Fork; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; import tech.pegasys.teku.storage.client.RecentChainData; public class GossipForkSubscriptionsElectra extends GossipForkSubscriptionsDeneb { + private final OperationProcessor dataColumnSidecarOperationProcessor; + private DataColumnSidecarGossipManager dataColumnSidecarGossipManager; + public GossipForkSubscriptionsElectra( final Fork fork, final Spec spec, @@ -53,7 +60,9 @@ public GossipForkSubscriptionsElectra( final OperationProcessor syncCommitteeMessageOperationProcessor, final OperationProcessor - signedBlsToExecutionChangeOperationProcessor) { + signedBlsToExecutionChangeOperationProcessor, + final OperationProcessor + dataColumnSidecarOperationProcessor) { super( fork, spec, @@ -72,5 +81,45 @@ public GossipForkSubscriptionsElectra( signedContributionAndProofOperationProcessor, syncCommitteeMessageOperationProcessor, signedBlsToExecutionChangeOperationProcessor); + this.dataColumnSidecarOperationProcessor = dataColumnSidecarOperationProcessor; + } + + @Override + protected void addGossipManagers(ForkInfo forkInfo) { + super.addGossipManagers(forkInfo); + + addDataColumnSidecarGossipManager(forkInfo); + } + + void addDataColumnSidecarGossipManager(final ForkInfo forkInfo) { + DataColumnSidecarSubnetSubscriptions dataColumnSidecarSubnetSubscriptions = + new DataColumnSidecarSubnetSubscriptions( + spec, + asyncRunner, + discoveryNetwork, + gossipEncoding, + recentChainData, + dataColumnSidecarOperationProcessor, + forkInfo + ); + + dataColumnSidecarGossipManager = new DataColumnSidecarGossipManager(dataColumnSidecarSubnetSubscriptions); + + addGossipManager(dataColumnSidecarGossipManager); + } + + @Override + public void publishDataColumnSidecar(DataColumnSidecar blobSidecar) { + dataColumnSidecarGossipManager.publish(blobSidecar); + } + + @Override + public void subscribeToDataColumnSidecarSubnet(int subnetId) { + dataColumnSidecarGossipManager.subscribeToSubnetId(subnetId); + } + + @Override + public void unsubscribeFromDataColumnSidecarSubnet(int subnetId) { + dataColumnSidecarGossipManager.unsubscribeFromSubnetId(subnetId); } } From 1350793353ad029a6f2e630effe5ed401fef71d8 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Mon, 15 Apr 2024 21:32:18 +0400 Subject: [PATCH 23/70] Update Eth2P2PNetworkBuilder --- .../teku/networking/eth2/Eth2P2PNetworkBuilder.java | 11 ++++++++++- .../teku/networking/eth2/Eth2P2PNetworkFactory.java | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 4ba768c9504..432042a0c8a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -70,6 +70,7 @@ import tech.pegasys.teku.spec.config.Constants; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -121,6 +122,7 @@ public class Eth2P2PNetworkBuilder { gossipedSignedContributionAndProofProcessor; protected OperationProcessor gossipedSyncCommitteeMessageProcessor; + protected OperationProcessor dataColumnSidecarOperationProcessor; protected StatusMessageFactory statusMessageFactory; protected KZG kzg; protected boolean recordMessageArrival; @@ -304,7 +306,8 @@ private GossipForkSubscriptions createSubscriptions( gossipedVoluntaryExitConsumer, gossipedSignedContributionAndProofProcessor, gossipedSyncCommitteeMessageProcessor, - gossipedSignedBlsToExecutionChangeProcessor); + gossipedSignedBlsToExecutionChangeProcessor, + dataColumnSidecarOperationProcessor); }; } @@ -527,6 +530,12 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } + public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor(OperationProcessor dataColumnSidecarOperationProcessor) { + checkNotNull(dataColumnSidecarOperationProcessor); + this.dataColumnSidecarOperationProcessor = dataColumnSidecarOperationProcessor; + return this; + } + public Eth2P2PNetworkBuilder metricsSystem(final MetricsSystem metricsSystem) { checkNotNull(metricsSystem); this.metricsSystem = metricsSystem; diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index f6da52cb668..58b00d8aedc 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -85,6 +85,7 @@ import tech.pegasys.teku.spec.datastructures.attestation.ProcessedAttestationListener; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -142,6 +143,7 @@ public class Eth2P2PNetworkBuilder { protected OperationProcessor signedContributionAndProofProcessor; protected OperationProcessor syncCommitteeMessageProcessor; protected OperationProcessor signedBlsToExecutionChangeProcessor; + protected OperationProcessor dataColumnSidecarOperationProcessor; protected ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; protected VerifiedBlockAttestationsSubscriptionProvider verifiedBlockAttestationsSubscriptionProvider; @@ -440,7 +442,8 @@ private GossipForkSubscriptions createSubscriptions( voluntaryExitProcessor, signedContributionAndProofProcessor, syncCommitteeMessageProcessor, - signedBlsToExecutionChangeProcessor); + signedBlsToExecutionChangeProcessor, + dataColumnSidecarOperationProcessor); }; } @@ -662,6 +665,12 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } + public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor(OperationProcessor dataColumnSidecarOperationProcessor) { + checkNotNull(dataColumnSidecarOperationProcessor); + this.dataColumnSidecarOperationProcessor = dataColumnSidecarOperationProcessor; + return this; + } + public Eth2P2PNetworkBuilder processedAttestationSubscriptionProvider( final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider) { checkNotNull(processedAttestationSubscriptionProvider); From 5ea4f8d48afb2b55451d49e0a3b890c0803a530c Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 16 Apr 2024 12:02:17 +0400 Subject: [PATCH 24/70] Fix copy/paste artifacts --- .../eth2/gossip/DataColumnSidecarGossipManager.java | 8 ++++---- .../subnets/DataColumnSidecarSubnetSubscriptions.java | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index 21d8d91b13e..bbe86bd2a58 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -31,8 +31,8 @@ public class DataColumnSidecarGossipManager implements GossipManager { private final DataColumnSidecarSubnetSubscriptions subnetSubscriptions; public DataColumnSidecarGossipManager( - final DataColumnSidecarSubnetSubscriptions attestationSubnetSubscriptions) { - subnetSubscriptions = attestationSubnetSubscriptions; + final DataColumnSidecarSubnetSubscriptions dataColumnSidecarSubnetSubscriptions) { + subnetSubscriptions = dataColumnSidecarSubnetSubscriptions; } public void publish(final DataColumnSidecar dataColumnSidecar) { @@ -41,11 +41,11 @@ public void publish(final DataColumnSidecar dataColumnSidecar) { .finish( __ -> { LOG.debug( - "Successfully published attestation for slot {}", dataColumnSidecar); + "Successfully published data column sidecar for slot {}", dataColumnSidecar); }, error -> { LOG.warn( - "Error publishing data column for slot {}", dataColumnSidecar); + "Error publishing data column sidecar for slot {}", dataColumnSidecar); }); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java index b54ee877c97..c4164163471 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java @@ -63,16 +63,16 @@ public DataColumnSidecarSubnetSubscriptions( this.processor = processor; this.forkInfo = forkInfo; SpecVersion specVersion = spec.forMilestone(SpecMilestone.ELECTRA); - this.dataColumnSidecarSchema = specVersion.getSchemaDefinitions().toVersionElectra().orElseThrow() + this.dataColumnSidecarSchema = SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) .getDataColumnSidecarSchema(); - this.subnetCount = specVersion.getConfig().toVersionElectra().orElseThrow() + this.subnetCount = SpecConfigElectra.required(specVersion.getConfig()) .getDataColumnSidecarSubnetCount(); } public SafeFuture gossip(final DataColumnSidecar sidecar) { int subnetId = computeSubnetForSidecar(sidecar); final String topic = - GossipTopics.getAttestationSubnetTopic( + GossipTopics.getDataColumnSidecarSubnetTopic( forkInfo.getForkDigest(spec), subnetId, gossipEncoding); return gossipNetwork.gossip(topic, gossipEncoding.encode(sidecar)); } From b96f8b13f79269f3121f0c1dc7ebce91462d25ec Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 16 Apr 2024 19:34:33 +0400 Subject: [PATCH 25/70] Gossip (part 2) (#15) * Update GossipForkManager and Eth2P2PNetwork * Add CUSTODY_REQUIREMENT constant * Create DataColumnSidecarSubnetBackboneSubscriber --- .../config/NetworkingSpecConfigElectra.java | 2 + .../teku/spec/config/SpecConfigElectra.java | 8 ++ .../spec/config/SpecConfigElectraImpl.java | 12 ++- .../spec/config/builder/ElectraBuilder.java | 11 ++- .../logic/common/helpers/MiscHelpers.java | 7 +- .../electra/helpers/MiscHelpersElectra.java | 18 ++++ .../spec/config/presets/mainnet/electra.yaml | 3 +- .../spec/config/presets/minimal/electra.yaml | 3 +- .../spec/config/presets/swift/electra.yaml | 3 +- .../networking/eth2/ActiveEth2P2PNetwork.java | 10 +++ .../teku/networking/eth2/Eth2P2PNetwork.java | 4 + .../eth2/gossip/forks/GossipForkManager.java | 24 +++++ ...ColumnSidecarSubnetBackboneSubscriber.java | 90 +++++++++++++++++++ .../eth2/mock/NoOpEth2P2PNetwork.java | 6 ++ .../beaconchain/BeaconChainController.java | 13 +++ 15 files changed, 207 insertions(+), 7 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java index 27e33e35bca..6190cf8df7d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java @@ -22,4 +22,6 @@ public interface NetworkingSpecConfigElectra extends NetworkingSpecConfig { int getDataColumnSidecarSubnetCount(); + + int getCustodyRequirement(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java index dc43d5762dc..e4ffd98016f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java @@ -67,6 +67,14 @@ static SpecConfigElectra required(final SpecConfig specConfig) { UInt64 getFieldElementsPerCell(); + default UInt64 getFieldElementsPerExtendedBlob() { + return UInt64.valueOf(getFieldElementsPerBlob()).times(2); + } + + default UInt64 getNumberOfColumns() { + return getFieldElementsPerExtendedBlob().dividedBy(getFieldElementsPerCell()); + } + @Override Optional toVersionElectra(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index ffa81652c08..ccccb05d2bc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -39,7 +39,7 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int maxAttestationsElectra; private final int maxConsolidations; private final int dataColumnSidecarSubnetCount; - + private final int custodyRequirement; private final UInt64 fieldElementsPerCell; public SpecConfigElectraImpl( @@ -62,7 +62,8 @@ public SpecConfigElectraImpl( final int maxAttestationsElectra, final int maxConsolidations, final UInt64 fieldElementsPerCell, - final int dataColumnSidecarSubnetCount) { + final int dataColumnSidecarSubnetCount, + final int custodyRequirement) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; @@ -83,6 +84,7 @@ public SpecConfigElectraImpl( this.maxConsolidations = maxConsolidations; this.fieldElementsPerCell = fieldElementsPerCell; this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; + this.custodyRequirement = custodyRequirement; } @Override @@ -180,6 +182,12 @@ public int getDataColumnSidecarSubnetCount() { return dataColumnSidecarSubnetCount; } + @Override + public int getCustodyRequirement() { + return custodyRequirement; + } + + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index 9a0993bfd0b..ab2ac817f90 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -49,6 +49,7 @@ public class ElectraBuilder implements ForkConfigBuilder getValidationMap() { constants.put("maxAttestationsElectra", maxAttestationsElectra); constants.put("maxConsolidations", maxConsolidations); constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); + constants.put("custodyRequirement", custodyRequirement); return constants; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index ace2a21ac51..57ed6dc36b3 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -46,6 +46,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; public class MiscHelpers { @@ -197,7 +198,7 @@ public List computeSubscribedSubnets(final UInt256 nodeId, final UInt64 .toList(); } - private UInt64 computeSubscribedSubnet( + protected UInt64 computeSubscribedSubnet( final UInt256 nodeId, final UInt64 epoch, final int index) { final int nodeIdPrefix = @@ -381,4 +382,8 @@ public boolean isFormerDepositMechanismDisabled(final BeaconState state) { public Optional toVersionDeneb() { return Optional.empty(); } + + public Optional toVersionElectra() { + return Optional.empty(); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index 76e2148a920..0a040cf951e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.spec.logic.versions.electra.helpers; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; @@ -20,6 +22,10 @@ import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; + public class MiscHelpersElectra extends MiscHelpersDeneb { public MiscHelpersElectra( @@ -38,4 +44,16 @@ public boolean isFormerDepositMechanismDisabled(final BeaconState state) { .getEth1DepositIndex() .equals(BeaconStateElectra.required(state).getDepositReceiptsStartIndex()); } + + public List computeDataColumnSidecarBackboneSubnets(final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { + // TODO: implement whatever formula is finalized + return IntStream.range(0, subnetCount) + .mapToObj(index -> computeSubscribedSubnet(nodeId, epoch, index)) + .toList(); + } + + @Override + public Optional toVersionElectra() { + return Optional.of(this); + } } diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml index b3d3ba0867e..60fce636ad3 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml @@ -37,4 +37,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8 # DAS # --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file +FIELD_ELEMENTS_PER_CELL: 64 +CUSTODY_REQUIREMENT: 1 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml index cae5beda817..342a3da771d 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml @@ -39,4 +39,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 # DAS # --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file +FIELD_ELEMENTS_PER_CELL: 64 +CUSTODY_REQUIREMENT: 1 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml index cae5beda817..342a3da771d 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml @@ -39,4 +39,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 # DAS # --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 \ No newline at end of file +FIELD_ELEMENTS_PER_CELL: 64 +CUSTODY_REQUIREMENT: 1 \ No newline at end of file diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 2b0ec6c2742..43ed6e8f316 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -336,6 +336,16 @@ public void unsubscribeFromSyncCommitteeSubnetId(final int subnetId) { syncCommitteeSubnetService.removeSubscription(subnetId); } + @Override + public void subscribeToDataColumnSidecarSubnetId(int subnetId) { + gossipForkManager.subscribeToDataColumnSidecarSubnetId(subnetId); + } + + @Override + public void unsubscribeFromDataColumnSidecarSubnetId(int subnetId) { + gossipForkManager.unsubscribeFromDataColumnSidecarSubnetId(subnetId); + } + @Override public MetadataMessage getMetadata() { return peerManager.getMetadataMessage(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetwork.java index 0b7dbc34c49..6c78815d5cb 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetwork.java @@ -40,6 +40,10 @@ public interface Eth2P2PNetwork extends P2PNetwork { void unsubscribeFromSyncCommitteeSubnetId(int subnetId); + void subscribeToDataColumnSidecarSubnetId(int subnetId); + + void unsubscribeFromDataColumnSidecarSubnetId(int subnetId); + MetadataMessage getMetadata(); void publishSyncCommitteeMessage(ValidatableSyncCommitteeMessage message); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java index e7fe30ff904..5dde186cd0f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java @@ -61,6 +61,7 @@ public class GossipForkManager { private final Set activeSubscriptions = new HashSet<>(); private final IntSet currentAttestationSubnets = new IntOpenHashSet(); private final IntSet currentSyncCommitteeSubnets = new IntOpenHashSet(); + private final IntSet currentDataColumnSidecarSubnets = new IntOpenHashSet(); private Optional currentEpoch = Optional.empty(); @@ -229,6 +230,15 @@ public void publishSignedBlsToExecutionChanges(final SignedBlsToExecutionChange GossipForkSubscriptions::publishSignedBlsToExecutionChangeMessage); } + public synchronized void publishDataColumnSidecarMessage( + final DataColumnSidecar message) { + publishMessage( + message.getSlot(), + message, + "data column sidecar message", + GossipForkSubscriptions::publishDataColumnSidecar); + } + private void publishMessage( final UInt64 slot, final T message, @@ -273,6 +283,20 @@ public void unsubscribeFromSyncCommitteeSubnetId(final int subnetId) { } } + public void subscribeToDataColumnSidecarSubnetId(final int subnetId) { + if (currentDataColumnSidecarSubnets.add(subnetId)) { + activeSubscriptions.forEach( + subscription -> subscription.subscribeToDataColumnSidecarSubnet(subnetId)); + } + } + + public void unsubscribeFromDataColumnSidecarSubnetId(final int subnetId) { + if (currentDataColumnSidecarSubnets.remove(subnetId)) { + activeSubscriptions.forEach( + subscription -> subscription.unsubscribeFromDataColumnSidecarSubnet(subnetId)); + } + } + private boolean isActive(final GossipForkSubscriptions subscriptions) { return activeSubscriptions.contains(subscriptions); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java new file mode 100644 index 00000000000..70f939f81bf --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -0,0 +1,90 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip.subnets; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.ethereum.events.SlotEventsChannel; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigElectra; + +import java.util.Collection; +import java.util.List; + +public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { + private static final Logger LOG = LogManager.getLogger(); + private final Eth2P2PNetwork eth2P2PNetwork; + private final UInt256 nodeId; + private final int extraVoluntarySubnetCount; + private final Spec spec; + + private IntSet currentSubscribedSubnets = IntSet.of(); + private UInt64 lastEpoch = UInt64.MAX_VALUE; + + public DataColumnSidecarSubnetBackboneSubscriber( + final Spec spec, + final Eth2P2PNetwork eth2P2PNetwork, + UInt256 nodeId, + int extraVoluntarySubnetCount) { + this.spec = spec; + this.eth2P2PNetwork = eth2P2PNetwork; + this.nodeId = nodeId; + this.extraVoluntarySubnetCount = extraVoluntarySubnetCount; + } + + private void subscribeToSubnets( + final Collection newSubscriptions) { + + IntOpenHashSet newSubscriptionsSet = new IntOpenHashSet(newSubscriptions); + + for (int oldSubnet : currentSubscribedSubnets) { + if (!newSubscriptionsSet.contains(oldSubnet)) { + eth2P2PNetwork.unsubscribeFromDataColumnSidecarSubnetId(oldSubnet); + } + } + + for (int newSubnet : newSubscriptionsSet) { + if (!currentSubscribedSubnets.contains(newSubnet)) { + eth2P2PNetwork.subscribeToDataColumnSidecarSubnetId(newSubnet); + } + } + + currentSubscribedSubnets = newSubscriptionsSet; + } + + private void onEpoch(final UInt64 epoch) { + SpecVersion specVersion = spec.atEpoch(epoch); + specVersion.miscHelpers().toVersionElectra().ifPresent(electraSpec -> { + int totalSubnetCount = SpecConfigElectra.required(specVersion.getConfig()) + .getCustodyRequirement() + extraVoluntarySubnetCount; + List subnets = electraSpec.computeDataColumnSidecarBackboneSubnets(nodeId, epoch, totalSubnetCount); + subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); + }); + } + + @Override + public synchronized void onSlot(final UInt64 slot) { + UInt64 epoch = spec.computeEpochAtSlot(slot); + if (!epoch.equals(lastEpoch)) { + lastEpoch = epoch; + onEpoch(epoch); + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/mock/NoOpEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/mock/NoOpEth2P2PNetwork.java index f80fab25fea..6cd42842e8e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/mock/NoOpEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/mock/NoOpEth2P2PNetwork.java @@ -54,6 +54,12 @@ public void subscribeToSyncCommitteeSubnetId(final int subnetId) {} @Override public void unsubscribeFromSyncCommitteeSubnetId(final int subnetId) {} + @Override + public void subscribeToDataColumnSidecarSubnetId(int subnetId) {} + + @Override + public void unsubscribeFromDataColumnSidecarSubnetId(int subnetId) {} + @Override public MetadataMessage getMetadata() { return spec.getGenesisSchemaDefinitions().getMetadataMessageSchema().createDefault(); diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 3ae8b310fb9..fe42c62d5c5 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -38,6 +38,7 @@ import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.api.ChainDataProvider; import tech.pegasys.teku.api.DataProvider; @@ -80,6 +81,7 @@ import tech.pegasys.teku.networking.eth2.gossip.subnets.AllSubnetsSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.AllSyncCommitteeSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber; +import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetBackboneSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.NodeBasedStableSubnetSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.StableSubnetSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; @@ -512,6 +514,7 @@ public void initAll() { initAttestationTopicSubscriber(); initActiveValidatorTracker(); initSubnetSubscriber(); + initDataColumnSidecarSubnetBackboneSubscriber(); initSlashingEventsSubscriptions(); initPerformanceTracker(); initDataProvider(); @@ -873,6 +876,16 @@ protected void initSubnetSubscriber() { eventChannels.subscribe(SlotEventsChannel.class, stableSubnetSubscriber); } + protected void initDataColumnSidecarSubnetBackboneSubscriber() { + LOG.debug("BeaconChainController.initDataColumnSidecarSubnetBackboneSubscriber"); + UInt256 nodeId = p2pNetwork.getDiscoveryNodeId() + .orElseThrow(() -> new InvalidConfigurationException(("NodeID is required for DataColumnSidecarSubnetBackboneSubscriber"))); + DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = + new DataColumnSidecarSubnetBackboneSubscriber(spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); + eventChannels.subscribe(SlotEventsChannel.class, subnetBackboneSubscriber); + } + + public void initExecutionLayerBlockProductionManager() { LOG.debug("BeaconChainController.initExecutionLayerBlockProductionManager()"); this.executionLayerBlockProductionManager = From 6b50aebe1c789facd7eb9c4c8c163b104fc051bf Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 18 Apr 2024 22:23:35 +0400 Subject: [PATCH 26/70] spotless apply (#16) --- .../java/tech/pegasys/teku/spec/Spec.java | 5 +- .../spec/config/SpecConfigElectraImpl.java | 1 - .../blobs/versions/electra/CellSchema.java | 17 ++- .../versions/electra/DataColumnSchema.java | 16 ++- .../versions/electra/DataColumnSidecar.java | 95 ++++++------- .../electra/DataColumnSidecarSchema.java | 131 +++++++++--------- .../electra/helpers/MiscHelpersElectra.java | 10 +- .../schemas/SchemaDefinitionsElectra.java | 4 +- .../java/tech/pegasys/teku/kzg/CKZG4844.java | 22 ++- .../tech/pegasys/teku/kzg/CKZG4844Utils.java | 3 - .../main/java/tech/pegasys/teku/kzg/KZG.java | 27 ++-- .../java/tech/pegasys/teku/kzg/KZGCell.java | 23 ++- .../pegasys/teku/kzg/KZGCellAndProof.java | 19 ++- .../java/tech/pegasys/teku/kzg/KZGCellID.java | 13 ++ .../tech/pegasys/teku/kzg/KZGCellWithID.java | 18 ++- .../java/tech/pegasys/teku/kzg/KZGProof.java | 9 +- .../tech/pegasys/teku/kzg/CKZG4844Test.java | 43 +++--- .../networking/eth2/ActiveEth2P2PNetwork.java | 5 +- .../eth2/Eth2P2PNetworkBuilder.java | 3 +- .../DataColumnSidecarGossipManager.java | 10 +- .../eth2/gossip/forks/GossipForkManager.java | 3 +- .../GossipForkSubscriptionsElectra.java | 9 +- ...ColumnSidecarSubnetBackboneSubscriber.java | 27 ++-- .../DataColumnSidecarSubnetSubscriptions.java | 18 +-- .../eth2/gossip/topics/GossipTopics.java | 12 +- .../eth2/Eth2P2PNetworkFactory.java | 3 +- .../beaconchain/BeaconChainController.java | 13 +- 27 files changed, 307 insertions(+), 252 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index a391eb6af59..e2a03f97fbe 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -220,8 +220,9 @@ public Optional getNetworkingConfigDeneb() { } /** - * Networking config with Electra constants. Use {@link tech.pegasys.teku.spec.config.SpecConfigElectra#required(SpecConfig)} when - * you are sure that Electra is available, otherwise use this method + * Networking config with Electra constants. Use {@link + * tech.pegasys.teku.spec.config.SpecConfigElectra#required(SpecConfig)} when you are sure that + * Electra is available, otherwise use this method */ public Optional getNetworkingConfigElectra() { return Optional.ofNullable(forMilestone(ELECTRA)) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index ccccb05d2bc..4d16260e208 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -187,7 +187,6 @@ public int getCustodyRequirement() { return custodyRequirement; } - @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java index 9e2047058e5..0f425109141 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java @@ -1,3 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; import org.apache.tuweni.bytes.Bytes; @@ -9,7 +22,9 @@ public class CellSchema extends SszByteVectorSchemaImpl { public CellSchema(final SpecConfigElectra specConfig) { - super(SpecConfigDeneb.BYTES_PER_FIELD_ELEMENT.longValue() * specConfig.getFieldElementsPerCell().longValue()); + super( + SpecConfigDeneb.BYTES_PER_FIELD_ELEMENT.longValue() + * specConfig.getFieldElementsPerCell().longValue()); } public Cell create(final Bytes bytes) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java index efbca80520b..40a199d59c1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java @@ -1,11 +1,23 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.config.SpecConfigElectra; -public class DataColumnSchema - extends AbstractSszListSchema { +public class DataColumnSchema extends AbstractSszListSchema { public DataColumnSchema(final SpecConfigElectra specConfig) { super(new CellSchema(specConfig), specConfig.getMaxBlobCommitmentsPerBlock()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java index ad80310c8e8..83e77be05bc 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java @@ -21,7 +21,6 @@ import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.kzg.KZGProof; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; @@ -29,55 +28,57 @@ public class DataColumnSidecar extends Container6< - DataColumnSidecar, - SszUInt64, - DataColumn, - SszList, - SszList, - SignedBeaconBlockHeader, - SszBytes32Vector> { - - DataColumnSidecar(final DataColumnSidecarSchema dataColumnSidecarSchema, final TreeNode backingTreeNode) { + DataColumnSidecar, + SszUInt64, + DataColumn, + SszList, + SszList, + SignedBeaconBlockHeader, + SszBytes32Vector> { + + DataColumnSidecar( + final DataColumnSidecarSchema dataColumnSidecarSchema, final TreeNode backingTreeNode) { super(dataColumnSidecarSchema, backingTreeNode); } -// public DataColumnSidecar( -// final DataColumnSidecarSchema schema, -// final UInt64 index, -// final Blob blob, -// final SszKZGCommitment sszKzgCommitment, -// final SszKZGProof sszKzgProof, -// final SignedBeaconBlockHeader signedBeaconBlockHeader, -// final List kzgCommitmentInclusionProof) { -// super( -// schema, -// SszUInt64.of(index), -// schema.getDataColumnSszSchema().create(blob.getBytes()), -// sszKzgCommitment, -// sszKzgProof, -// signedBeaconBlockHeader, -// schema -// .getKzgCommitmentInclusionProofSchema() -// .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); -// } -// -// public DataColumnSidecar( -// final DataColumnSidecarSchema schema, -// final UInt64 index, -// final Blob blob, -// final KZGCommitment kzgCommitment, -// final KZGProof kzgProof, -// final SignedBeaconBlockHeader signedBeaconBlockHeader, -// final List kzgCommitmentInclusionProof) { -// this( -// schema, -// index, -// blob, -// new SszKZGCommitment(kzgCommitment), -// new SszKZGProof(kzgProof), -// signedBeaconBlockHeader, -// kzgCommitmentInclusionProof); -// } + // public DataColumnSidecar( + // final DataColumnSidecarSchema schema, + // final UInt64 index, + // final Blob blob, + // final SszKZGCommitment sszKzgCommitment, + // final SszKZGProof sszKzgProof, + // final SignedBeaconBlockHeader signedBeaconBlockHeader, + // final List kzgCommitmentInclusionProof) { + // super( + // schema, + // SszUInt64.of(index), + // schema.getDataColumnSszSchema().create(blob.getBytes()), + // sszKzgCommitment, + // sszKzgProof, + // signedBeaconBlockHeader, + // schema + // .getKzgCommitmentInclusionProofSchema() + // + // .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); + // } + // + // public DataColumnSidecar( + // final DataColumnSidecarSchema schema, + // final UInt64 index, + // final Blob blob, + // final KZGCommitment kzgCommitment, + // final KZGProof kzgProof, + // final SignedBeaconBlockHeader signedBeaconBlockHeader, + // final List kzgCommitmentInclusionProof) { + // this( + // schema, + // index, + // blob, + // new SszKZGCommitment(kzgCommitment), + // new SszKZGProof(kzgProof), + // signedBeaconBlockHeader, + // kzgCommitmentInclusionProof); + // } public UInt64 getIndex() { return getField0().get(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java index 84dd691867e..6ae1512bac8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java @@ -32,13 +32,13 @@ public class DataColumnSidecarSchema extends ContainerSchema6< - DataColumnSidecar, - SszUInt64, - DataColumn, - SszList, - SszList, - SignedBeaconBlockHeader, - SszBytes32Vector> { + DataColumnSidecar, + SszUInt64, + DataColumn, + SszList, + SszList, + SignedBeaconBlockHeader, + SszBytes32Vector> { static final SszFieldName FIELD_BLOB = () -> "column"; static final SszFieldName FIELD_SIGNED_BLOCK_HEADER = () -> "signed_block_header"; @@ -53,16 +53,14 @@ public class DataColumnSidecarSchema "DataColumnSidecar", namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), namedSchema(FIELD_BLOB, dataColumnSchema), - namedSchema("kzg_commitments", + namedSchema( + "kzg_commitments", SszListSchema.create( - SszKZGCommitmentSchema.INSTANCE, - specConfig.getMaxBlobCommitmentsPerBlock() - )), - namedSchema("kzg_proofs", + SszKZGCommitmentSchema.INSTANCE, specConfig.getMaxBlobCommitmentsPerBlock())), + namedSchema( + "kzg_proofs", SszListSchema.create( - SszKZGProofSchema.INSTANCE, - specConfig.getMaxBlobCommitmentsPerBlock() - )), + SszKZGProofSchema.INSTANCE, specConfig.getMaxBlobCommitmentsPerBlock())), namedSchema(FIELD_SIGNED_BLOCK_HEADER, signedBeaconBlockHeaderSchema), namedSchema( FIELD_KZG_COMMITMENT_INCLUSION_PROOF, @@ -83,62 +81,61 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENT_INCLUSION_PROOF)); } -// public DataColumnSidecar create( -// final UInt64 index, -// final Blob blob, -// final SszKZGCommitment sszKzgCommitment, -// final SszKZGProof sszKzgProof, -// final SignedBeaconBlockHeader signedBeaconBlockHeader, -// final List kzgCommitmentInclusionProof) { -// return new DataColumnSidecar( -// this, -// index, -// blob, -// sszKzgCommitment, -// sszKzgProof, -// signedBeaconBlockHeader, -// kzgCommitmentInclusionProof); -// } -// -// public DataColumnSidecar create( -// final UInt64 index, -// final Bytes blob, -// final Bytes48 kzgCommitment, -// final Bytes48 kzgProof, -// final SignedBeaconBlockHeader signedBeaconBlockHeader, -// final List kzgCommitmentInclusionProof) { -// return create( -// index, -// new Blob(getBlobSchema(), blob), -// KZGCommitment.fromBytesCompressed(kzgCommitment), -// KZGProof.fromBytesCompressed(kzgProof), -// signedBeaconBlockHeader, -// kzgCommitmentInclusionProof); -// } -// -// public DataColumnSidecar create( -// final UInt64 index, -// final Blob blob, -// final KZGCommitment kzgCommitment, -// final KZGProof kzgProof, -// final SignedBeaconBlockHeader signedBeaconBlockHeader, -// final List kzgCommitmentInclusionProof) { -// return new DataColumnSidecar( -// this, -// index, -// blob, -// kzgCommitment, -// kzgProof, -// signedBeaconBlockHeader, -// kzgCommitmentInclusionProof); -// } -// + // public DataColumnSidecar create( + // final UInt64 index, + // final Blob blob, + // final SszKZGCommitment sszKzgCommitment, + // final SszKZGProof sszKzgProof, + // final SignedBeaconBlockHeader signedBeaconBlockHeader, + // final List kzgCommitmentInclusionProof) { + // return new DataColumnSidecar( + // this, + // index, + // blob, + // sszKzgCommitment, + // sszKzgProof, + // signedBeaconBlockHeader, + // kzgCommitmentInclusionProof); + // } + // + // public DataColumnSidecar create( + // final UInt64 index, + // final Bytes blob, + // final Bytes48 kzgCommitment, + // final Bytes48 kzgProof, + // final SignedBeaconBlockHeader signedBeaconBlockHeader, + // final List kzgCommitmentInclusionProof) { + // return create( + // index, + // new Blob(getBlobSchema(), blob), + // KZGCommitment.fromBytesCompressed(kzgCommitment), + // KZGProof.fromBytesCompressed(kzgProof), + // signedBeaconBlockHeader, + // kzgCommitmentInclusionProof); + // } + // + // public DataColumnSidecar create( + // final UInt64 index, + // final Blob blob, + // final KZGCommitment kzgCommitment, + // final KZGProof kzgProof, + // final SignedBeaconBlockHeader signedBeaconBlockHeader, + // final List kzgCommitmentInclusionProof) { + // return new DataColumnSidecar( + // this, + // index, + // blob, + // kzgCommitment, + // kzgProof, + // signedBeaconBlockHeader, + // kzgCommitmentInclusionProof); + // } + // public static DataColumnSidecarSchema create( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, final DataColumnSchema dataColumnSchema, final SpecConfigElectra specConfig) { - return new DataColumnSidecarSchema( - signedBeaconBlockHeaderSchema, dataColumnSchema, specConfig); + return new DataColumnSidecarSchema(signedBeaconBlockHeaderSchema, dataColumnSchema, specConfig); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index 0a040cf951e..f561f03a649 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -13,6 +13,9 @@ package tech.pegasys.teku.spec.logic.versions.electra.helpers; +import java.util.List; +import java.util.Optional; +import java.util.stream.IntStream; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigElectra; @@ -22,10 +25,6 @@ import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; -import java.util.List; -import java.util.Optional; -import java.util.stream.IntStream; - public class MiscHelpersElectra extends MiscHelpersDeneb { public MiscHelpersElectra( @@ -45,7 +44,8 @@ public boolean isFormerDepositMechanismDisabled(final BeaconState state) { .equals(BeaconStateElectra.required(state).getDepositReceiptsStartIndex()); } - public List computeDataColumnSidecarBackboneSubnets(final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { + public List computeDataColumnSidecarBackboneSubnets( + final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { // TODO: implement whatever formula is finalized return IntStream.range(0, subnetCount) .mapToObj(index -> computeSubscribedSubnet(nodeId, epoch, index)) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 6434e122a48..d5dd4e2e27f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -147,7 +147,9 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { this.pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); this.dataColumnSchema = new DataColumnSchema(specConfig); - this.dataColumnSidecarSchema = DataColumnSidecarSchema.create(SignedBeaconBlockHeader.SSZ_SCHEMA, dataColumnSchema, specConfig); + this.dataColumnSidecarSchema = + DataColumnSidecarSchema.create( + SignedBeaconBlockHeader.SSZ_SCHEMA, dataColumnSchema, specConfig); } public static SchemaDefinitionsElectra required(final SchemaDefinitions schemaDefinitions) { diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index ad177c13de1..6dfb1022ddc 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -13,19 +13,17 @@ package tech.pegasys.teku.kzg; -import ethereum.ckzg4844.CKZG4844JNI; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; +import ethereum.ckzg4844.CKZG4844JNI; +import ethereum.ckzg4844.CellsAndProofs; import java.util.List; import java.util.Optional; import java.util.stream.IntStream; - -import ethereum.ckzg4844.CellsAndProofs; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; -import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; - /** * Wrapper around jc-kzg-4844 * @@ -55,9 +53,7 @@ private CKZG4844() { } } - /** - * Only one trusted setup at a time can be loaded. - */ + /** Only one trusted setup at a time can be loaded. */ @Override public synchronized void loadTrustedSetup(final String trustedSetupFile) throws KZGException { if (loadedTrustedSetupFile.isPresent() @@ -172,7 +168,8 @@ public List computeCellsAndProofs(Bytes blob) { } @Override - public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { + public boolean verifyCellProof( + KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { return CKZG4844JNI.verifyCellProof( commitment.toArrayUnsafe(), cellWithID.id().id().longValue(), @@ -183,10 +180,9 @@ public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithI @Override public List recoverCells(List cells) { long[] cellIds = cells.stream().mapToLong(c -> c.id().id().longValue()).toArray(); - byte[] cellBytes = CKZG4844Utils.flattenBytes( - cells.stream().map(c -> c.cell().bytes()).toList(), - cells.size() * BYTES_PER_CELL - ); + byte[] cellBytes = + CKZG4844Utils.flattenBytes( + cells.stream().map(c -> c.cell().bytes()).toList(), cells.size() * BYTES_PER_CELL); byte[] recovered = CKZG4844JNI.recoverCells(cellIds, cellBytes); return KZGCell.splitBytes(Bytes.wrap(recovered)); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java index 2b5da271f5d..0325329156a 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844Utils.java @@ -25,13 +25,10 @@ import java.util.List; import java.util.function.Function; import java.util.stream.IntStream; - import org.apache.tuweni.bytes.Bytes; import tech.pegasys.teku.infrastructure.http.UrlSanitizer; import tech.pegasys.teku.infrastructure.io.resource.ResourceLoader; -import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; - class CKZG4844Utils { private static final int MAX_BYTES_TO_FLATTEN = 100_663_296; // ~100.66 MB or 768 blobs diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index 16de9db19aa..af7bd3a3ab3 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -13,14 +13,13 @@ package tech.pegasys.teku.kzg; +import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; + import java.util.List; import java.util.stream.Stream; - import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes48; -import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; - /** * This interface specifies all the KZG functions needed for the Deneb specification and is the * entry-point for all KZG operations in Teku. @@ -35,12 +34,10 @@ static KZG getInstance() { new KZG() { @Override - public void loadTrustedSetup(final String trustedSetupFile) throws KZGException { - } + public void loadTrustedSetup(final String trustedSetupFile) throws KZGException {} @Override - public void freeTrustedSetup() throws KZGException { - } + public void freeTrustedSetup() throws KZGException {} @Override public boolean verifyBlobKzgProof( @@ -73,21 +70,20 @@ public KZGProof computeBlobKzgProof(final Bytes blob, final KZGCommitment kzgCom public List computeCells(Bytes blob) { List blobCells = KZGCell.splitBytes(blob); return Stream.concat( - blobCells.stream(), - Stream.generate(() -> KZGCell.ZERO).limit(blobCells.size()) - ).toList(); + blobCells.stream(), Stream.generate(() -> KZGCell.ZERO).limit(blobCells.size())) + .toList(); } @Override public List computeCellsAndProofs(Bytes blob) { - return computeCells(blob) - .stream() + return computeCells(blob).stream() .map(cell -> new KZGCellAndProof(cell, KZGProof.fromBytesCompressed(Bytes48.ZERO))) .toList(); } @Override - public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { + public boolean verifyCellProof( + KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { return true; } @@ -95,10 +91,7 @@ public boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithI public List recoverCells(List cells) { if (cells.size() < CELLS_PER_BLOB) throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); - return cells.stream() - .map(KZGCellWithID::cell) - .limit(CELLS_PER_BLOB) - .toList(); + return cells.stream().map(KZGCellWithID::cell).limit(CELLS_PER_BLOB).toList(); } }; diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java index 9a40aefb0ad..a376699e1e5 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java @@ -1,19 +1,28 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.kzg; -import org.apache.tuweni.bytes.Bytes; +import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; import java.util.List; - -import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; +import org.apache.tuweni.bytes.Bytes; public record KZGCell(Bytes bytes) { static KZGCell ZERO = new KZGCell(Bytes.wrap(new byte[BYTES_PER_CELL])); static List splitBytes(Bytes bytes) { - return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_CELL) - .stream() - .map(KZGCell::new) - .toList(); + return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_CELL).stream().map(KZGCell::new).toList(); } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java index 0c4c8aa1ef0..6f62a30ffd3 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellAndProof.java @@ -1,7 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.kzg; -public record KZGCellAndProof( - KZGCell cell, - KZGProof proof -) { -} +public record KZGCellAndProof(KZGCell cell, KZGProof proof) {} diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java index b6710d06ca4..9ff417a8da9 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java @@ -1,3 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.kzg; import tech.pegasys.teku.infrastructure.unsigned.UInt64; diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java index 966e12074ec..96bb30a2272 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java @@ -1,9 +1,19 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.kzg; -public record KZGCellWithID( - KZGCell cell, - KZGCellID id -) { +public record KZGCellWithID(KZGCell cell, KZGCellID id) { static KZGCellWithID fromCellAndColumn(KZGCell cell, int index) { return new KZGCellWithID(cell, KZGCellID.fromCellColumnIndex(index)); diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java index 5f70255f5c4..72de85a39b9 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGProof.java @@ -14,11 +14,8 @@ package tech.pegasys.teku.kzg; import static com.google.common.base.Preconditions.checkArgument; -import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_CELL; import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_PROOF; -import ethereum.ckzg4844.CKZG4844JNI; - import java.util.List; import java.util.Objects; import org.apache.tuweni.bytes.Bytes; @@ -37,8 +34,7 @@ public static KZGProof fromSSZBytes(final Bytes bytes) { "Expected " + BYTES_PER_PROOF + " bytes but received %s.", bytes.size()); return SSZ.decode( - bytes, - reader -> new KZGProof(Bytes48.wrap(reader.readFixedBytes(BYTES_PER_PROOF)))); + bytes, reader -> new KZGProof(Bytes48.wrap(reader.readFixedBytes(BYTES_PER_PROOF)))); } public static KZGProof fromBytesCompressed(final Bytes48 bytes) throws IllegalArgumentException { @@ -50,8 +46,7 @@ public static KZGProof fromArray(final byte[] bytes) { } static List splitBytes(Bytes bytes) { - return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_PROOF) - .stream() + return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_PROOF).stream() .map(b -> new KZGProof(Bytes48.wrap(b))) .toList(); } diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index de0bdbaed0b..714ab478225 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -26,7 +26,6 @@ import ethereum.ckzg4844.CKZG4844JNI; import ethereum.ckzg4844.CKZGException; import ethereum.ckzg4844.CKZGException.CKZGError; - import java.math.BigInteger; import java.nio.ByteOrder; import java.util.List; @@ -34,7 +33,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; - import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.AfterAll; @@ -118,7 +116,7 @@ public void testComputingAndVerifyingBatchProofs() { assertThat(CKZG.verifyBlobKzgProofBatch(blobs, kzgCommitments, kzgProofs)).isTrue(); assertThat( - CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) + CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) .isFalse(); assertThat(CKZG.verifyBlobKzgProofBatch(blobs, getSampleCommitments(numberOfBlobs), kzgProofs)) .isFalse(); @@ -174,7 +172,7 @@ public void testComputingAndVerifyingBatchSingleProof() { assertThat(CKZG.verifyBlobKzgProofBatch(blobs, kzgCommitments, kzgProofs)).isTrue(); assertThat( - CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) + CKZG.verifyBlobKzgProofBatch(getSampleBlobs(numberOfBlobs), kzgCommitments, kzgProofs)) .isFalse(); assertThat(CKZG.verifyBlobKzgProofBatch(blobs, getSampleCommitments(numberOfBlobs), kzgProofs)) .isFalse(); @@ -223,9 +221,9 @@ public void testVerifyingBatchProofsThrowsIfSizesDoesntMatch() { @ParameterizedTest(name = "blob={0}") @ValueSource( strings = { - "0x0d2024ece3e004271319699b8b00cc010628b6bc0be5457f031fb1db0afd3ff8", - "0x", - "0x925668a49d06f4" + "0x0d2024ece3e004271319699b8b00cc010628b6bc0be5457f031fb1db0afd3ff8", + "0x", + "0x925668a49d06f4" }) public void testComputingProofWithIncorrectLengthBlobDoesNotCauseSegfault(final String blobHex) { final Bytes blob = Bytes.fromHexString(blobHex); @@ -251,15 +249,15 @@ public void testComputingProofWithIncorrectLengthBlobDoesNotCauseSegfault(final @ParameterizedTest(name = "trusted_setup={0}") @ValueSource( strings = { - "broken/trusted_setup_g1_length.txt", - "broken/trusted_setup_g2_length.txt", - "broken/trusted_setup_g2_bytesize.txt" + "broken/trusted_setup_g1_length.txt", + "broken/trusted_setup_g2_length.txt", + "broken/trusted_setup_g2_bytesize.txt" }) public void incorrectTrustedSetupFilesShouldThrow(final String filename) { final Throwable cause = assertThrows( - KZGException.class, - () -> CKZG.loadTrustedSetup(TrustedSetupLoader.getTrustedSetupFile(filename))) + KZGException.class, + () -> CKZG.loadTrustedSetup(TrustedSetupLoader.getTrustedSetupFile(filename))) .getCause(); assertThat(cause.getMessage()).contains("Failed to parse trusted setup file"); } @@ -294,9 +292,10 @@ public void testComputeRecoverCells() { List cells = CKZG.computeCells(blob); assertThat(cells).hasSize(CELLS_PER_EXT_BLOB); - List cellsToRecover = IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) - .mapToObj(i -> new KZGCellWithID(cells.get(i), KZGCellID.fromCellColumnIndex(i))) - .toList(); + List cellsToRecover = + IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) + .mapToObj(i -> new KZGCellWithID(cells.get(i), KZGCellID.fromCellColumnIndex(i))) + .toList(); List recoveredCells = CKZG.recoverCells(cellsToRecover); assertThat(recoveredCells).isEqualTo(cells); @@ -314,12 +313,18 @@ public void testComputeAndVerifyCellProof() { for (int i = 0; i < cellAndProofs.size(); i++) { assertThat( - CKZG.verifyCellProof(kzgCommitment, KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), cellAndProofs.get(i).proof()) - ).isTrue(); + CKZG.verifyCellProof( + kzgCommitment, + KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), + cellAndProofs.get(i).proof())) + .isTrue(); var invalidProof = cellAndProofs.get((i + 1) % cellAndProofs.size()).proof(); assertThat( - CKZG.verifyCellProof(kzgCommitment, KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), invalidProof) - ).isFalse(); + CKZG.verifyCellProof( + kzgCommitment, + KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), + invalidProof)) + .isFalse(); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 43ed6e8f316..9b2b35b2dd1 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -28,8 +28,8 @@ import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; -import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.config.Eth2Context; import tech.pegasys.teku.networking.eth2.gossip.config.GossipConfigurator; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; @@ -134,7 +134,8 @@ private synchronized void startup() { processedAttestationSubscriptionProvider.subscribe(gossipForkManager::publishAttestation); eventChannels.subscribe(BlockGossipChannel.class, gossipForkManager::publishBlock); eventChannels.subscribe(BlobSidecarGossipChannel.class, gossipForkManager::publishBlobSidecar); - eventChannels.subscribe(DataColumnSidecarGossipChannel.class, gossipForkManager::publishDataColumnSidecar); + eventChannels.subscribe( + DataColumnSidecarGossipChannel.class, gossipForkManager::publishDataColumnSidecar); if (isCloseToInSync()) { startGossip(); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index a902776e506..24d83b2798e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -536,7 +536,8 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } - public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor(OperationProcessor dataColumnSidecarOperationProcessor) { + public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor( + OperationProcessor dataColumnSidecarOperationProcessor) { checkNotNull(dataColumnSidecarOperationProcessor); this.dataColumnSidecarOperationProcessor = dataColumnSidecarOperationProcessor; return this; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index bbe86bd2a58..91400021d2c 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -15,15 +15,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.hyperledger.besu.plugin.services.MetricsSystem; -import org.hyperledger.besu.plugin.services.metrics.Counter; -import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; -import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; -import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetSubscriptions; -import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.operations.Attestation; public class DataColumnSidecarGossipManager implements GossipManager { private static final Logger LOG = LogManager.getLogger(); @@ -44,8 +37,7 @@ public void publish(final DataColumnSidecar dataColumnSidecar) { "Successfully published data column sidecar for slot {}", dataColumnSidecar); }, error -> { - LOG.warn( - "Error publishing data column sidecar for slot {}", dataColumnSidecar); + LOG.warn("Error publishing data column sidecar for slot {}", dataColumnSidecar); }); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java index 5dde186cd0f..d8f88c9c4c4 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java @@ -230,8 +230,7 @@ public void publishSignedBlsToExecutionChanges(final SignedBlsToExecutionChange GossipForkSubscriptions::publishSignedBlsToExecutionChangeMessage); } - public synchronized void publishDataColumnSidecarMessage( - final DataColumnSidecar message) { + public synchronized void publishDataColumnSidecarMessage(final DataColumnSidecar message) { publishMessage( message.getSlot(), message, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java index fcf543cbc44..98b5937decd 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java @@ -61,8 +61,7 @@ public GossipForkSubscriptionsElectra( syncCommitteeMessageOperationProcessor, final OperationProcessor signedBlsToExecutionChangeOperationProcessor, - final OperationProcessor - dataColumnSidecarOperationProcessor) { + final OperationProcessor dataColumnSidecarOperationProcessor) { super( fork, spec, @@ -100,10 +99,10 @@ void addDataColumnSidecarGossipManager(final ForkInfo forkInfo) { gossipEncoding, recentChainData, dataColumnSidecarOperationProcessor, - forkInfo - ); + forkInfo); - dataColumnSidecarGossipManager = new DataColumnSidecarGossipManager(dataColumnSidecarSubnetSubscriptions); + dataColumnSidecarGossipManager = + new DataColumnSidecarGossipManager(dataColumnSidecarSubnetSubscriptions); addGossipManager(dataColumnSidecarGossipManager); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index 70f939f81bf..aef093ec12e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -15,6 +15,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import java.util.Collection; +import java.util.List; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; @@ -25,9 +27,6 @@ import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.SpecConfigElectra; -import java.util.Collection; -import java.util.List; - public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { private static final Logger LOG = LogManager.getLogger(); private final Eth2P2PNetwork eth2P2PNetwork; @@ -49,8 +48,7 @@ public DataColumnSidecarSubnetBackboneSubscriber( this.extraVoluntarySubnetCount = extraVoluntarySubnetCount; } - private void subscribeToSubnets( - final Collection newSubscriptions) { + private void subscribeToSubnets(final Collection newSubscriptions) { IntOpenHashSet newSubscriptionsSet = new IntOpenHashSet(newSubscriptions); @@ -71,12 +69,19 @@ private void subscribeToSubnets( private void onEpoch(final UInt64 epoch) { SpecVersion specVersion = spec.atEpoch(epoch); - specVersion.miscHelpers().toVersionElectra().ifPresent(electraSpec -> { - int totalSubnetCount = SpecConfigElectra.required(specVersion.getConfig()) - .getCustodyRequirement() + extraVoluntarySubnetCount; - List subnets = electraSpec.computeDataColumnSidecarBackboneSubnets(nodeId, epoch, totalSubnetCount); - subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); - }); + specVersion + .miscHelpers() + .toVersionElectra() + .ifPresent( + electraSpec -> { + int totalSubnetCount = + SpecConfigElectra.required(specVersion.getConfig()).getCustodyRequirement() + + extraVoluntarySubnetCount; + List subnets = + electraSpec.computeDataColumnSidecarBackboneSubnets( + nodeId, epoch, totalSubnetCount); + subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); + }); } @Override diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java index c4164163471..5fb5db3f5f4 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.networking.eth2.gossip.subnets; import com.google.common.annotations.VisibleForTesting; +import java.util.Optional; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; @@ -22,7 +23,6 @@ import tech.pegasys.teku.networking.eth2.gossip.topics.OperationMilestoneValidator; import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.Eth2TopicHandler; -import tech.pegasys.teku.networking.eth2.gossip.topics.topichandlers.SingleAttestationTopicHandler; import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; import tech.pegasys.teku.networking.p2p.gossip.TopicChannel; import tech.pegasys.teku.spec.Spec; @@ -31,13 +31,10 @@ import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; -import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.state.ForkInfo; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.storage.client.RecentChainData; -import java.util.Optional; - public class DataColumnSidecarSubnetSubscriptions extends CommitteeSubnetSubscriptions { private final Spec spec; @@ -63,10 +60,11 @@ public DataColumnSidecarSubnetSubscriptions( this.processor = processor; this.forkInfo = forkInfo; SpecVersion specVersion = spec.forMilestone(SpecMilestone.ELECTRA); - this.dataColumnSidecarSchema = SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) - .getDataColumnSidecarSchema(); - this.subnetCount = SpecConfigElectra.required(specVersion.getConfig()) - .getDataColumnSidecarSubnetCount(); + this.dataColumnSidecarSchema = + SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) + .getDataColumnSidecarSchema(); + this.subnetCount = + SpecConfigElectra.required(specVersion.getConfig()).getDataColumnSidecarSubnetCount(); } public SafeFuture gossip(final DataColumnSidecar sidecar) { @@ -94,9 +92,7 @@ protected Eth2TopicHandler createTopicHandler(final int subnetId) { forkInfo.getForkDigest(spec), topicName, new OperationMilestoneValidator<>( - spec, - forkInfo.getFork(), - message -> spec.computeEpochAtSlot(message.getSlot())), + spec, forkInfo.getFork(), message -> spec.computeEpochAtSlot(message.getSlot())), dataColumnSidecarSchema, spec.getNetworkingConfig()); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java index af3740f9643..edb8e42e99a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java @@ -84,11 +84,13 @@ public static Set getAllTopics( topics.add(getBlobSidecarSubnetTopic(forkDigest, i, gossipEncoding)); } } - spec.getNetworkingConfigElectra().ifPresent(electraNetworkConfig -> { - for (int i = 0; i < electraNetworkConfig.getDataColumnSidecarSubnetCount(); i++) { - topics.add(getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)); - } - }); + spec.getNetworkingConfigElectra() + .ifPresent( + electraNetworkConfig -> { + for (int i = 0; i < electraNetworkConfig.getDataColumnSidecarSubnetCount(); i++) { + topics.add(getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)); + } + }); for (GossipTopicName topicName : GossipTopicName.values()) { topics.add(GossipTopics.getTopic(forkDigest, topicName, gossipEncoding)); } diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index 90a56a0c790..315f974e461 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -666,7 +666,8 @@ public Eth2P2PNetworkBuilder gossipedSignedBlsToExecutionChangeProcessor( return this; } - public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor(OperationProcessor dataColumnSidecarOperationProcessor) { + public Eth2P2PNetworkBuilder gossipedDataColumnSidecarOperationProcessor( + OperationProcessor dataColumnSidecarOperationProcessor) { checkNotNull(dataColumnSidecarOperationProcessor); this.dataColumnSidecarOperationProcessor = dataColumnSidecarOperationProcessor; return this; diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index fe42c62d5c5..2e3e37e9a8f 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -878,14 +878,19 @@ protected void initSubnetSubscriber() { protected void initDataColumnSidecarSubnetBackboneSubscriber() { LOG.debug("BeaconChainController.initDataColumnSidecarSubnetBackboneSubscriber"); - UInt256 nodeId = p2pNetwork.getDiscoveryNodeId() - .orElseThrow(() -> new InvalidConfigurationException(("NodeID is required for DataColumnSidecarSubnetBackboneSubscriber"))); + UInt256 nodeId = + p2pNetwork + .getDiscoveryNodeId() + .orElseThrow( + () -> + new InvalidConfigurationException( + ("NodeID is required for DataColumnSidecarSubnetBackboneSubscriber"))); DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = - new DataColumnSidecarSubnetBackboneSubscriber(spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); + new DataColumnSidecarSubnetBackboneSubscriber( + spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); eventChannels.subscribe(SlotEventsChannel.class, subnetBackboneSubscriber); } - public void initExecutionLayerBlockProductionManager() { LOG.debug("BeaconChainController.initExecutionLayerBlockProductionManager()"); this.executionLayerBlockProductionManager = From 6dc808b01117ccddbc909890f9e8d607ba4346f9 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 18 Apr 2024 23:35:00 +0400 Subject: [PATCH 27/70] More codestyle fixes --- .../tech/pegasys/teku/spec/config/SpecConfigElectraTest.java | 1 + .../kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java | 4 +++- .../kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java | 3 ++- .../kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java | 2 +- .../kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java | 4 ++-- .../subnets/DataColumnSidecarSubnetBackboneSubscriber.java | 3 --- .../teku/services/beaconchain/BeaconChainController.java | 2 +- 7 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index 2a8a9a5a56c..fb942a91165 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -98,6 +98,7 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomUInt64(64), + dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(64)) {}; } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index 6dfb1022ddc..08edbd4db6b 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -161,7 +161,9 @@ public List computeCellsAndProofs(Bytes blob) { CellsAndProofs cellsAndProofs = CKZG4844JNI.computeCellsAndProofs(blob.toArrayUnsafe()); List cells = KZGCell.splitBytes(Bytes.wrap(cellsAndProofs.getCells())); List proofs = KZGProof.splitBytes(Bytes.wrap(cellsAndProofs.getProofs())); - if (cells.size() != proofs.size()) throw new KZGException("Cells and proofs size differ"); + if (cells.size() != proofs.size()) { + throw new KZGException("Cells and proofs size differ"); + } return IntStream.range(0, cells.size()) .mapToObj(i -> new KZGCellAndProof(cells.get(i), proofs.get(i))) .toList(); diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index af7bd3a3ab3..a765b3c5fe2 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -89,8 +89,9 @@ public boolean verifyCellProof( @Override public List recoverCells(List cells) { - if (cells.size() < CELLS_PER_BLOB) + if (cells.size() < CELLS_PER_BLOB) { throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); + } return cells.stream().map(KZGCellWithID::cell).limit(CELLS_PER_BLOB).toList(); } }; diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java index a376699e1e5..48724e87c7d 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCell.java @@ -20,7 +20,7 @@ public record KZGCell(Bytes bytes) { - static KZGCell ZERO = new KZGCell(Bytes.wrap(new byte[BYTES_PER_CELL])); + static final KZGCell ZERO = new KZGCell(Bytes.wrap(new byte[BYTES_PER_CELL])); static List splitBytes(Bytes bytes) { return CKZG4844Utils.bytesChunked(bytes, BYTES_PER_CELL).stream().map(KZGCell::new).toList(); diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index 714ab478225..18c449d0ed1 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -283,8 +283,8 @@ public void testInvalidLengthG2PointInNewTrustedSetup() { .hasMessage("Expected G2 point to be 96 bytes"); } - static int CELLS_PER_EXT_BLOB = CELLS_PER_BLOB; - static int CELLS_PER_ORIG_BLOB = CELLS_PER_EXT_BLOB / 2; + static final int CELLS_PER_EXT_BLOB = CELLS_PER_BLOB; + static final int CELLS_PER_ORIG_BLOB = CELLS_PER_EXT_BLOB / 2; @Test public void testComputeRecoverCells() { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index aef093ec12e..412838dc30a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -17,8 +17,6 @@ import it.unimi.dsi.fastutil.ints.IntSet; import java.util.Collection; import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.ethereum.events.SlotEventsChannel; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -28,7 +26,6 @@ import tech.pegasys.teku.spec.config.SpecConfigElectra; public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { - private static final Logger LOG = LogManager.getLogger(); private final Eth2P2PNetwork eth2P2PNetwork; private final UInt256 nodeId; private final int extraVoluntarySubnetCount; diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 2e3e37e9a8f..81133fb360c 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -884,7 +884,7 @@ protected void initDataColumnSidecarSubnetBackboneSubscriber() { .orElseThrow( () -> new InvalidConfigurationException( - ("NodeID is required for DataColumnSidecarSubnetBackboneSubscriber"))); + "NodeID is required for DataColumnSidecarSubnetBackboneSubscriber")); DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = new DataColumnSidecarSubnetBackboneSubscriber( spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); From c3a2ca4ebabf6859ecd37fe4cb26e7a47cb2c040 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 19 Apr 2024 13:58:43 +0400 Subject: [PATCH 28/70] Central DAS interfaces sketches and some Custody implementation (#17) * Add a number of interface sketches * Add draft implementations of Custody and CustodySync * Add necessary spec config constants and functions --- .../config/NetworkingSpecConfigElectra.java | 2 + .../spec/config/SpecConfigElectraImpl.java | 10 +- .../spec/config/builder/ElectraBuilder.java | 11 +- .../electra/helpers/MiscHelpersElectra.java | 33 ++++ .../spec/config/presets/mainnet/electra.yaml | 3 +- .../spec/config/presets/minimal/electra.yaml | 3 +- .../spec/config/presets/swift/electra.yaml | 3 +- .../spec/config/SpecConfigElectraTest.java | 3 +- .../datacolumns/ColumnSlotAndIdentifier.java | 19 ++ .../datacolumns/CustodySync.java | 102 ++++++++++ .../datacolumns/DataColumnSidecarCustody.java | 30 +++ .../DataColumnSidecarCustodyImpl.java | 181 ++++++++++++++++++ .../datacolumns/DataColumnSidecarDB.java | 39 ++++ .../datacolumns/DataColumnSidecarManager.java | 35 ++++ .../DataColumnSidecarRetriever.java | 28 +++ .../DataColumnSidecarGossipValidator.java | 36 ++++ .../DataColumnSidecarValidator.java | 30 +++ .../beaconchain/BeaconChainController.java | 18 ++ 18 files changed, 580 insertions(+), 6 deletions(-) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/ColumnSlotAndIdentifier.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java index 6190cf8df7d..3990e592353 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java @@ -24,4 +24,6 @@ public interface NetworkingSpecConfigElectra extends NetworkingSpecConfig { int getDataColumnSidecarSubnetCount(); int getCustodyRequirement(); + + int getMinEpochsForDataColumnSidecarsRequests(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index 4d16260e208..aa1b554c9d8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -41,6 +41,7 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int dataColumnSidecarSubnetCount; private final int custodyRequirement; private final UInt64 fieldElementsPerCell; + private final int minEpochsForDataColumnSidecarsRequests; public SpecConfigElectraImpl( final SpecConfigDeneb specConfig, @@ -63,7 +64,8 @@ public SpecConfigElectraImpl( final int maxConsolidations, final UInt64 fieldElementsPerCell, final int dataColumnSidecarSubnetCount, - final int custodyRequirement) { + final int custodyRequirement, + int minEpochsForDataColumnSidecarsRequests) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; @@ -85,6 +87,7 @@ public SpecConfigElectraImpl( this.fieldElementsPerCell = fieldElementsPerCell; this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; this.custodyRequirement = custodyRequirement; + this.minEpochsForDataColumnSidecarsRequests = minEpochsForDataColumnSidecarsRequests; } @Override @@ -187,6 +190,11 @@ public int getCustodyRequirement() { return custodyRequirement; } + @Override + public int getMinEpochsForDataColumnSidecarsRequests() { + return minEpochsForDataColumnSidecarsRequests; + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index ab2ac817f90..83514eb2bc7 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -50,6 +50,7 @@ public class ElectraBuilder implements ForkConfigBuilder getValidationMap() { constants.put("maxConsolidations", maxConsolidations); constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); constants.put("custodyRequirement", custodyRequirement); + constants.put("minEpochsForDataColumnSidecarsRequests", minEpochsForDataColumnSidecarsRequests); return constants; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index f561f03a649..d4a27a1b764 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -13,25 +13,43 @@ package tech.pegasys.teku.spec.logic.versions.electra.helpers; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; public class MiscHelpersElectra extends MiscHelpersDeneb { + public static MiscHelpersElectra required(final MiscHelpers miscHelpers) { + return miscHelpers + .toVersionElectra() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected Electra misc helpers but got: " + + miscHelpers.getClass().getSimpleName())); + } + + private final SpecConfigElectra specConfigElectra; + public MiscHelpersElectra( final SpecConfigElectra specConfig, final Predicates predicates, final SchemaDefinitionsElectra schemaDefinitions) { super(specConfig, predicates, schemaDefinitions); + this.specConfigElectra = specConfig; } @Override @@ -44,6 +62,21 @@ public boolean isFormerDepositMechanismDisabled(final BeaconState state) { .equals(BeaconStateElectra.required(state).getDepositReceiptsStartIndex()); } + public UInt64 computeSubnetForDataColumnSidecar(UInt64 columnIndex) { + return columnIndex.mod(specConfigElectra.getDataColumnSidecarSubnetCount()); + } + + public Set computeCustodyColumnIndexes( + final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { + // TODO: implement whatever formula is finalized + Set subnets = + new HashSet<>(computeDataColumnSidecarBackboneSubnets(nodeId, epoch, subnetCount)); + return Stream.iterate(UInt64.ZERO, UInt64::increment) + .limit(specConfigElectra.getNumberOfColumns().intValue()) + .filter(columnIndex -> subnets.contains(computeSubnetForDataColumnSidecar(columnIndex))) + .collect(Collectors.toSet()); + } + public List computeDataColumnSidecarBackboneSubnets( final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { // TODO: implement whatever formula is finalized diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml index 60fce636ad3..8383ed55fa8 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml @@ -38,4 +38,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8 # DAS # --------------------------------------------------------------- FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 \ No newline at end of file +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml index 342a3da771d..5ecb8da4923 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml @@ -40,4 +40,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 # DAS # --------------------------------------------------------------- FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 \ No newline at end of file +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml index 342a3da771d..5ecb8da4923 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml @@ -40,4 +40,5 @@ MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 # DAS # --------------------------------------------------------------- FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 \ No newline at end of file +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index fb942a91165..6d8d3ae2fe1 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -99,6 +99,7 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomUInt64(64), dataStructureUtil.randomPositiveInt(64), - dataStructureUtil.randomPositiveInt(64)) {}; + dataStructureUtil.randomPositiveInt(64), + dataStructureUtil.randomPositiveInt(4096)) {}; } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/ColumnSlotAndIdentifier.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/ColumnSlotAndIdentifier.java new file mode 100644 index 00000000000..824050ab465 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/ColumnSlotAndIdentifier.java @@ -0,0 +1,19 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public record ColumnSlotAndIdentifier(UInt64 slot, DataColumnIdentifier identifier) {} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java new file mode 100644 index 00000000000..2a3e68de3c2 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java @@ -0,0 +1,102 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import tech.pegasys.teku.ethereum.events.SlotEventsChannel; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +public class CustodySync implements SlotEventsChannel { + + private final DataColumnSidecarCustody custody; + private final DataColumnSidecarRetriever retriever; + private final int maxPendingColumnRequests = 1024; + private final int minPendingColumnRequests = 512; + + private Map pendingRequests = new HashMap<>(); + private boolean started = false; + + public CustodySync(DataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { + this.custody = custody; + this.retriever = retriever; + } + + private synchronized void onRequestComplete(PendingRequest request) { + DataColumnSidecar result = request.columnPromise.join(); + custody.onNewValidatedDataColumnSidecar(result); + pendingRequests.remove(request.columnId.identifier()); + fillUpIfNeeded(); + } + + private void fillUpIfNeeded() { + if (started && pendingRequests.size() <= minPendingColumnRequests) { + fillUp(); + } + } + + private synchronized void fillUp() { + int newRequestCount = maxPendingColumnRequests - pendingRequests.size(); + Set missingColumnsToRequest = + custody + .streamMissingColumns() + .filter(c -> !pendingRequests.containsKey(c.identifier())) + .limit(newRequestCount) + .collect(Collectors.toSet()); + + // cancel those which are not missing anymore for whatever reason + Iterator> it = + pendingRequests.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry pendingEntry = it.next(); + if (!missingColumnsToRequest.contains(pendingEntry.getKey())) { + pendingEntry.getValue().columnPromise().cancel(true); + it.remove(); + } + } + + for (ColumnSlotAndIdentifier missingColumn : missingColumnsToRequest) { + SafeFuture promise = retriever.retrieve(missingColumn); + PendingRequest request = new PendingRequest(missingColumn, promise); + pendingRequests.put(missingColumn, request); + promise.thenAccept(__ -> onRequestComplete(request)); + } + } + + public void start() { + started = true; + fillUp(); + } + + public synchronized void stop() { + started = false; + for (PendingRequest request : pendingRequests.values()) { + request.columnPromise.cancel(true); + } + pendingRequests.clear(); + } + + @Override + public void onSlot(UInt64 slot) { + fillUpIfNeeded(); + } + + private record PendingRequest( + ColumnSlotAndIdentifier columnId, SafeFuture columnPromise) {} +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java new file mode 100644 index 00000000000..11b6debbfca --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public interface DataColumnSidecarCustody { + + void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar); + + SafeFuture> getCustodyDataColumnSidecar( + DataColumnIdentifier columnId); + + Stream streamMissingColumns(); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java new file mode 100644 index 00000000000..118d32ec873 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -0,0 +1,181 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.ethereum.events.SlotEventsChannel; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; + +public class DataColumnSidecarCustodyImpl implements DataColumnSidecarCustody, SlotEventsChannel { + + public interface BlockChainAccessor { + + Optional getCanonicalBlockRootAtSlot(UInt64 slot); + } + + private record SlotCustody( + UInt64 slot, + Optional canonicalBlockRoot, + Collection requiredColumnIndices, + Collection custodiedColumnIndices) { + public Collection getIncompleteColumns() { + return canonicalBlockRoot + .map( + blockRoot -> { + Set collectedIndices = + custodiedColumnIndices.stream() + .filter(identifier -> identifier.getBlockRoot().equals(blockRoot)) + .map(DataColumnIdentifier::getIndex) + .collect(Collectors.toSet()); + return requiredColumnIndices.stream() + .filter(requiredColIdx -> !collectedIndices.contains(requiredColIdx)) + .map(missedColIdx -> new DataColumnIdentifier(blockRoot, missedColIdx)); + }) + .orElse(Stream.empty()) + .toList(); + } + + public boolean isIncomplete() { + return !getIncompleteColumns().isEmpty(); + } + } + + // for how long the custody will wait for a missing column to be gossiped + private final int gossipWaitSlots = 2; + + private final Spec spec; + private final DataColumnSidecarDB db; + private final BlockChainAccessor blockChainAccessor; + private final UInt256 nodeId; + private final int totalCustodySubnetCount; + + private final UInt64 electraStartEpoch; + + private UInt64 currentSlot = null; + + public DataColumnSidecarCustodyImpl( + Spec spec, + DataColumnSidecarDB db, + BlockChainAccessor blockChainAccessor, + UInt256 nodeId, + int totalCustodySubnetCount) { + this.spec = spec; + this.db = db; + this.blockChainAccessor = blockChainAccessor; + this.nodeId = nodeId; + this.totalCustodySubnetCount = totalCustodySubnetCount; + this.electraStartEpoch = spec.getForkSchedule().getFork(SpecMilestone.ELECTRA).getEpoch(); + } + + private UInt64 getEarliestCustodySlot(UInt64 currentSlot) { + UInt64 epoch = getEarliestCustodyEpoch(spec.computeEpochAtSlot(currentSlot)); + return spec.computeStartSlotAtEpoch(epoch); + } + + private UInt64 getEarliestCustodyEpoch(UInt64 currentEpoch) { + int custodyPeriod = + spec.getSpecConfig(currentEpoch) + .toVersionElectra() + .orElseThrow() + .getMinEpochsForDataColumnSidecarsRequests(); + return currentEpoch.minusMinZero(custodyPeriod).max(electraStartEpoch); + } + + private Set getCustodyColumnsForSlot(UInt64 slot) { + return getCustodyColumnsForEpoch(spec.computeEpochAtSlot(slot)); + } + + private Set getCustodyColumnsForEpoch(UInt64 epoch) { + return MiscHelpersElectra.required(spec.atEpoch(epoch).miscHelpers()) + .computeCustodyColumnIndexes(nodeId, epoch, totalCustodySubnetCount); + } + + @Override + public void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar) { + db.addSidecar(dataColumnSidecar); + } + + @Override + public SafeFuture> getCustodyDataColumnSidecar( + DataColumnIdentifier columnId) { + return SafeFuture.completedFuture(db.getSidecar(columnId)); + } + + private void onEpoch(UInt64 epoch) { + UInt64 pruneSlot = spec.computeStartSlotAtEpoch(getEarliestCustodyEpoch(epoch)); + db.pruneAllSidecars(pruneSlot); + advanceLatestCompleteSlot(); + } + + @Override + public void onSlot(UInt64 slot) { + currentSlot = slot; + UInt64 epoch = spec.computeEpochAtSlot(slot); + if (slot.equals(spec.computeStartSlotAtEpoch(epoch))) { + onEpoch(epoch); + } + } + + private void advanceLatestCompleteSlot() { + streamSlotCustodies() + .dropWhile(slotCustody -> !slotCustody.isIncomplete()) + .findFirst() + .ifPresent(firstIncomplete -> db.setFirstIncompleteSlot(firstIncomplete.slot)); + } + + private Stream streamSlotCustodies() { + if (currentSlot == null) { + return Stream.empty(); + } + + UInt64 firstIncompleteSlot = + db.getFirstIncompleteSlot().orElseGet(() -> getEarliestCustodySlot(currentSlot)); + + return Stream.iterate( + firstIncompleteSlot, + slot -> slot.plus(gossipWaitSlots).isLessThanOrEqualTo(currentSlot), + UInt64::increment) + .map( + slot -> { + Optional maybeCanonicalBlockRoot = + blockChainAccessor.getCanonicalBlockRootAtSlot(slot); + Set requiredColumns = getCustodyColumnsForSlot(slot); + List existingColumns = + db.streamColumnIdentifiers(slot).toList(); + return new SlotCustody( + slot, maybeCanonicalBlockRoot, requiredColumns, existingColumns); + }); + } + + public Stream streamMissingColumns() { + return streamSlotCustodies() + .flatMap( + slotCustody -> + slotCustody.getIncompleteColumns().stream() + .map(colId -> new ColumnSlotAndIdentifier(slotCustody.slot(), colId))); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java new file mode 100644 index 00000000000..1b521ea047e --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java @@ -0,0 +1,39 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public interface DataColumnSidecarDB { + + // read + + Optional getFirstIncompleteSlot(); + + Optional getSidecar(DataColumnIdentifier identifier); + + Stream streamColumnIdentifiers(UInt64 slot); + + // update + + void setFirstIncompleteSlot(UInt64 slot); + + void addSidecar(DataColumnSidecar sidecar); + + void pruneAllSidecars(UInt64 tillSlot); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java new file mode 100644 index 00000000000..aaea14f9f88 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java @@ -0,0 +1,35 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarGossipValidator; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; + +public interface DataColumnSidecarManager { + + public static DataColumnSidecarManager NOOP = + (sidecar, arrivalTimestamp) -> SafeFuture.completedFuture(InternalValidationResult.ACCEPT); + + public static DataColumnSidecarManager create(DataColumnSidecarGossipValidator validator) { + // TODO + return (sidecar, arrivalTimestamp) -> validator.validate(sidecar); + } + + SafeFuture onDataColumnSidecarGossip( + DataColumnSidecar sidecar, Optional arrivalTimestamp); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java new file mode 100644 index 00000000000..c06d5d05204 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java @@ -0,0 +1,28 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +/** The class which searches for a specific {@link DataColumnSidecar} across nodes in the network */ +public interface DataColumnSidecarRetriever { + + /** + * Queues the specified sidecar for search + * + * @return a future which may run indefinitely until finds a requested data or cancelled + */ + SafeFuture retrieve(ColumnSlotAndIdentifier columnId); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java new file mode 100644 index 00000000000..f4aa273e7a9 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java @@ -0,0 +1,36 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.validation; + +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +public interface DataColumnSidecarGossipValidator { + + public static DataColumnSidecarGossipValidator NOOP = + dataColumnSidecar -> SafeFuture.completedFuture(InternalValidationResult.ACCEPT); + + public static DataColumnSidecarGossipValidator create(DataColumnSidecarValidator validator) { + return dataColumnSidecar -> + validator + .validate(dataColumnSidecar) + .handle( + (__, err) -> + err == null + ? InternalValidationResult.ACCEPT + : InternalValidationResult.reject(err.toString())); + } + + SafeFuture validate(DataColumnSidecar dataColumnSidecar); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java new file mode 100644 index 00000000000..301c7364385 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.validation; + +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +/** Check the DataColumnSidecar strict validity received either via Pubsub or Req/Resp */ +public interface DataColumnSidecarValidator { + + public static DataColumnSidecarValidator NOOP = sidecar -> SafeFuture.COMPLETE; + + public static DataColumnSidecarValidator create() { + // TODO + return NOOP; + } + + SafeFuture validate(DataColumnSidecar sidecar); +} diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 81133fb360c..5e0aa404790 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -134,6 +134,7 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManager; import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifier; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifierImpl; @@ -160,6 +161,8 @@ import tech.pegasys.teku.statetransition.validation.BlobSidecarGossipValidator; import tech.pegasys.teku.statetransition.validation.BlockGossipValidator; import tech.pegasys.teku.statetransition.validation.BlockValidator; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarGossipValidator; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; import tech.pegasys.teku.statetransition.validation.GossipValidationHelper; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; import tech.pegasys.teku.statetransition.validation.ProposerSlashingValidator; @@ -275,6 +278,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile GossipValidationHelper gossipValidationHelper; protected volatile KZG kzg; protected volatile BlobSidecarManager blobSidecarManager; + protected volatile DataColumnSidecarManager dataColumnSidecarManager; protected volatile Optional terminalPowBlockMonitor = Optional.empty(); protected volatile ProposersDataManager proposersDataManager; protected volatile KeyValueStore keyValueStore; @@ -489,6 +493,7 @@ public void initAll() { initKzg(); initBlockBlobSidecarsTrackersPool(); initBlobSidecarManager(); + initDataColumnSidecarManager(); initForkChoiceStateProvider(); initForkChoiceNotifier(); initMergeMonitors(); @@ -580,6 +585,17 @@ protected void initBlobSidecarManager() { } } + protected void initDataColumnSidecarManager() { + if (spec.isMilestoneSupported(SpecMilestone.ELECTRA)) { + DataColumnSidecarValidator dataColumnSidecarValidator = DataColumnSidecarValidator.create(); + DataColumnSidecarGossipValidator gossipValidator = + DataColumnSidecarGossipValidator.create(dataColumnSidecarValidator); + dataColumnSidecarManager = DataColumnSidecarManager.create(gossipValidator); + } else { + dataColumnSidecarManager = DataColumnSidecarManager.NOOP; + } + } + protected void initMergeMonitors() { if (spec.isMilestoneSupported(SpecMilestone.BELLATRIX)) { terminalPowBlockMonitor = @@ -1106,6 +1122,8 @@ protected void initP2PNetwork() { .combinedChainDataClient(combinedChainDataClient) .gossipedBlockProcessor(blockManager::validateAndImportBlock) .gossipedBlobSidecarProcessor(blobSidecarManager::validateAndPrepareForBlockImport) + .gossipedDataColumnSidecarOperationProcessor( + dataColumnSidecarManager::onDataColumnSidecarGossip) .gossipedAttestationProcessor(attestationManager::addAttestation) .gossipedAggregateProcessor(attestationManager::addAggregate) .gossipedAttesterSlashingProcessor(attesterSlashingPool::addRemote) From 6c40e3cfeeb33fb54a4bb30eb189f36bcb1bce74 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Fri, 19 Apr 2024 15:35:26 +0400 Subject: [PATCH 29/70] PeerDAS Database part1 (#19) --- .../java/tech/pegasys/teku/spec/Spec.java | 11 +++ .../util/ColumnSlotAndIdentifier.java | 37 ++++++++++ .../pegasys/teku/storage/server/Database.java | 21 ++++++ .../server/kvstore/KvStoreDatabase.java | 72 +++++++++++++++++++ .../dataaccess/CombinedKvStoreDao.java | 54 ++++++++++++++ .../dataaccess/KvStoreCombinedDao.java | 17 +++++ .../dataaccess/KvStoreCombinedDaoAdapter.java | 40 +++++++++++ .../dataaccess/V4FinalizedKvStoreDao.java | 51 +++++++++++++ .../server/kvstore/schema/SchemaCombined.java | 5 ++ .../SchemaFinalizedSnapshotStateAdapter.java | 14 +++- .../kvstore/schema/V6SchemaCombined.java | 8 +++ .../schema/V6SchemaCombinedSnapshot.java | 13 ++++ .../schema/V6SchemaCombinedTreeState.java | 13 ++++ .../ColumnSlotAndIdentifierKeySerializer.java | 57 +++++++++++++++ .../serialization/KvStoreSerializer.java | 3 + .../storage/server/noop/NoOpDatabase.java | 28 ++++++++ 16 files changed, 443 insertions(+), 1 deletion(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java create mode 100644 storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/ColumnSlotAndIdentifierKeySerializer.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index e2a03f97fbe..3bb6252cba2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -421,6 +421,17 @@ public ExecutionPayloadHeader deserializeJsonExecutionPayloadHeader( .jsonDeserialize(objectMapper.createParser(jsonFile)); } + public DataColumnSidecar deserializeSidecar(final Bytes serializedSidecar, final UInt64 slot) { + return atSlot(slot) + .getSchemaDefinitions() + .toVersionElectra() + .orElseThrow( + () -> + new RuntimeException("Electra milestone is required to deserialize column sidecar")) + .getDataColumnSidecarSchema() + .sszDeserialize(serializedSidecar); + } + // BeaconState public UInt64 getCurrentEpoch(final BeaconState state) { return atState(state).beaconStateAccessors().getCurrentEpoch(state); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java new file mode 100644 index 00000000000..cf320e92bf0 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java @@ -0,0 +1,37 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.util; + +import java.util.Comparator; +import org.apache.tuweni.bytes.Bytes32; +import org.jetbrains.annotations.NotNull; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public record ColumnSlotAndIdentifier(UInt64 slot, DataColumnIdentifier identifier) + implements Comparable { + public ColumnSlotAndIdentifier( + final UInt64 slot, final Bytes32 blockRoot, final UInt64 columnIndex) { + this(slot, new DataColumnIdentifier(blockRoot, columnIndex)); + } + + @Override + public int compareTo(@NotNull final ColumnSlotAndIdentifier o) { + return Comparator.comparing(ColumnSlotAndIdentifier::slot) + .thenComparing( + columnSlotAndIdentifier -> columnSlotAndIdentifier.identifier().getBlockRoot()) + .thenComparing(columnSlotAndIdentifier -> columnSlotAndIdentifier.identifier().getIndex()) + .compare(this, o); + } +} diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java index a4dacda1d6c..a973da9cd11 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; @@ -34,6 +35,7 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.api.OnDiskStoreData; import tech.pegasys.teku.storage.api.StorageUpdate; @@ -236,4 +238,23 @@ default Stream streamBlobSidecarKeys(final UInt64 * @return actual last pruned slot */ UInt64 pruneFinalizedBlocks(UInt64 lastSlotToPrune, int pruneLimit); + + // Sidecars + Optional getFirstIncompleteSlot(); + + Optional getSidecar(ColumnSlotAndIdentifier identifier); + + @MustBeClosed + Stream streamDataColumnIdentifiers(UInt64 firstSlot, UInt64 lastSlot); + + @MustBeClosed + default Stream streamDataColumnIdentifiers(final UInt64 slot) { + return streamDataColumnIdentifiers(slot, slot); + } + + void setFirstIncompleteSlot(UInt64 slot); + + void addSidecar(DataColumnSidecar sidecar); + + void pruneAllSidecars(UInt64 tillSlot); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index e7de1ecc70d..6a4137754aa 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -49,6 +49,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockInvariants; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; @@ -64,6 +65,7 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.api.OnDiskStoreData; import tech.pegasys.teku.storage.api.StorageUpdate; @@ -922,6 +924,49 @@ public void setFinalizedDepositSnapshot(DepositTreeSnapshot finalizedDepositSnap } } + @Override + public Optional getFirstIncompleteSlot() { + return dao.getFirstIncompleteSlot(); + } + + @Override + public Optional getSidecar(final ColumnSlotAndIdentifier identifier) { + final Optional maybePayload = dao.getSidecar(identifier); + return maybePayload.map(payload -> spec.deserializeSidecar(payload, identifier.slot())); + } + + @Override + @MustBeClosed + public Stream streamDataColumnIdentifiers( + final UInt64 firstSlot, final UInt64 lastSlot) { + return dao.streamDataColumnIdentifiers(firstSlot, lastSlot); + } + + @Override + public void setFirstIncompleteSlot(final UInt64 slot) { + try (final FinalizedUpdater updater = finalizedUpdater()) { + updater.setFirstIncompleteSlot(slot); + updater.commit(); + } + } + + @Override + public void addSidecar(final DataColumnSidecar sidecar) { + try (final FinalizedUpdater updater = finalizedUpdater()) { + updater.addSidecar(sidecar); + updater.commit(); + } + } + + @Override + public void pruneAllSidecars(final UInt64 tillSlotInclusive) { + try (final Stream prunableIdentifiers = + streamDataColumnIdentifiers(UInt64.ZERO, tillSlotInclusive); + final FinalizedUpdater updater = finalizedUpdater()) { + prunableIdentifiers.forEach(updater::removeSidecar); + } + } + @Override public void close() throws Exception { dao.close(); @@ -1112,6 +1157,33 @@ private void removeNonCanonicalBlobSidecars( } } + // TODO: link on storage update when sidecars are enabled + @SuppressWarnings("UnusedMethod") + private void removeNonCanonicalSidecars( + final Map deletedHotBlocks, + final Map finalizedChildToParentMap) { + + final Set nonCanonicalBlocks = + deletedHotBlocks.entrySet().stream() + .filter(entry -> !finalizedChildToParentMap.containsKey(entry.getKey())) + .map(entry -> new SlotAndBlockRoot(entry.getValue(), entry.getKey())) + .collect(Collectors.toSet()); + + LOG.trace("Removing sidecars for non-canonical blocks"); + try (final FinalizedUpdater updater = finalizedUpdater()) { + for (final SlotAndBlockRoot slotAndBlockRoot : nonCanonicalBlocks) { + dao.getDataColumnIdentifiers(slotAndBlockRoot) + .forEach( + identifier -> { + LOG.trace( + "Removing sidecar with identifier {} for non-canonical block", identifier); + updater.removeSidecar(identifier); + }); + } + updater.commit(); + } + } + private void updateFinalizedDataArchiveMode( Map finalizedChildToParentMap, final Map finalizedBlocks, diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java index 1293ac41ddc..afba07ed547 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java @@ -33,6 +33,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -40,6 +41,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.server.kvstore.ColumnEntry; import tech.pegasys.teku.storage.server.kvstore.KvStoreAccessor; @@ -515,6 +517,39 @@ public Optional getFinalizedDepositSnapshot() { return db.get(schema.getVariableFinalizedDepositSnapshot()); } + @Override + public Optional getFirstIncompleteSlot() { + return db.get(schema.getVariableFirstIncompleteSlot()); + } + + @Override + public Optional getSidecar(final ColumnSlotAndIdentifier identifier) { + return db.get(schema.getColumnSidecarByColumnSlotAndIdentifier(), identifier); + } + + @Override + @MustBeClosed + public Stream streamDataColumnIdentifiers( + UInt64 startSlot, UInt64 endSlot) { + return db.streamKeys( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier(startSlot, MIN_BLOCK_ROOT, UInt64.ZERO), + new ColumnSlotAndIdentifier(endSlot, MAX_BLOCK_ROOT, UInt64.MAX_VALUE)); + } + + @Override + public List getDataColumnIdentifiers(SlotAndBlockRoot slotAndBlockRoot) { + try (final Stream columnSlotAndIdentifierStream = + db.streamKeys( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier( + slotAndBlockRoot.getSlot(), slotAndBlockRoot.getBlockRoot(), UInt64.ZERO), + new ColumnSlotAndIdentifier( + slotAndBlockRoot.getSlot(), slotAndBlockRoot.getBlockRoot(), UInt64.MAX_VALUE)); ) { + return columnSlotAndIdentifierStream.toList(); + } + } + static class V4CombinedUpdater implements CombinedUpdater { private final KvStoreTransaction transaction; @@ -773,5 +808,24 @@ public void removeBlobSidecar(final SlotAndBlockRootAndBlobIndex key) { public void removeNonCanonicalBlobSidecar(final SlotAndBlockRootAndBlobIndex key) { transaction.delete(schema.getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex(), key); } + + @Override + public void setFirstIncompleteSlot(final UInt64 slot) { + transaction.put(schema.getVariableFirstIncompleteSlot(), slot); + } + + @Override + public void addSidecar(final DataColumnSidecar sidecar) { + transaction.put( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier( + sidecar.getSlot(), sidecar.getBlockRoot(), sidecar.getIndex()), + sidecar.sszSerialize()); + } + + @Override + public void removeSidecar(final ColumnSlotAndIdentifier identifier) { + transaction.delete(schema.getColumnSidecarByColumnSlotAndIdentifier(), identifier); + } } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java index 60e314e919a..40d02c256ab 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -34,6 +35,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public interface KvStoreCombinedDao extends AutoCloseable { @@ -161,6 +163,15 @@ List getNonCanonicalBlobSidecarKeys( Optional getFinalizedDepositSnapshot(); + Optional getFirstIncompleteSlot(); + + Optional getSidecar(ColumnSlotAndIdentifier identifier); + + @MustBeClosed + Stream streamDataColumnIdentifiers(UInt64 startSlot, UInt64 endSlot); + + List getDataColumnIdentifiers(SlotAndBlockRoot slotAndBlockRoot); + interface CombinedUpdater extends HotUpdater, FinalizedUpdater {} interface HotUpdater extends AutoCloseable { @@ -254,6 +265,12 @@ interface FinalizedUpdater extends AutoCloseable { void setEarliestBlobSidecarSlot(UInt64 slot); + void setFirstIncompleteSlot(UInt64 slot); + + void addSidecar(DataColumnSidecar sidecar); + + void removeSidecar(ColumnSlotAndIdentifier identifier); + void commit(); void cancel(); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java index a69f89fb32f..30f480f5826 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java @@ -29,6 +29,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -36,6 +37,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.server.kvstore.ColumnEntry; import tech.pegasys.teku.storage.server.kvstore.dataaccess.V4FinalizedKvStoreDao.V4FinalizedUpdater; @@ -303,6 +305,29 @@ public Stream> getFinalizedBlockRoots() { return finalizedDao.getFinalizedBlockRoots(); } + @Override + public Optional getFirstIncompleteSlot() { + return finalizedDao.getFirstIncompleteSlot(); + } + + @Override + public Optional getSidecar(final ColumnSlotAndIdentifier identifier) { + return finalizedDao.getSidecar(identifier); + } + + @Override + @MustBeClosed + public Stream streamDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot) { + return finalizedDao.streamDataColumnIdentifiers(startSlot, endSlot); + } + + @Override + public List getDataColumnIdentifiers( + final SlotAndBlockRoot slotAndBlockRoot) { + return finalizedDao.getDataColumnIdentifiers(slotAndBlockRoot); + } + @Override public void ingest( final KvStoreCombinedDao dao, final int batchSize, final Consumer logger) { @@ -573,6 +598,21 @@ public void setEarliestBlobSidecarSlot(final UInt64 slot) { finalizedUpdater.setEarliestBlobSidecarSlot(slot); } + @Override + public void setFirstIncompleteSlot(final UInt64 slot) { + finalizedUpdater.setFirstIncompleteSlot(slot); + } + + @Override + public void addSidecar(final DataColumnSidecar sidecar) { + finalizedUpdater.addSidecar(sidecar); + } + + @Override + public void removeSidecar(final ColumnSlotAndIdentifier identifier) { + finalizedUpdater.removeSidecar(identifier); + } + @Override public void addMinGenesisTimeBlock(final MinGenesisTimeBlockEvent event) { hotUpdater.addMinGenesisTimeBlock(event); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java index 5f49bfcfc16..f045d658f40 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java @@ -30,9 +30,11 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.server.kvstore.ColumnEntry; import tech.pegasys.teku.storage.server.kvstore.KvStoreAccessor; @@ -188,6 +190,36 @@ public Optional getEarliestBlobSidecarSlot() { return db.get(schema.getVariableEarliestBlobSidecarSlot()); } + public Optional getFirstIncompleteSlot() { + return db.get(schema.getVariableFirstIncompleteSlot()); + } + + public Optional getSidecar(final ColumnSlotAndIdentifier identifier) { + return db.get(schema.getColumnSidecarByColumnSlotAndIdentifier(), identifier); + } + + @MustBeClosed + public Stream streamDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot) { + return db.streamKeys( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier(startSlot, MIN_BLOCK_ROOT, UInt64.ZERO), + new ColumnSlotAndIdentifier(endSlot, MAX_BLOCK_ROOT, UInt64.MAX_VALUE)); + } + + public List getDataColumnIdentifiers( + final SlotAndBlockRoot slotAndBlockRoot) { + try (final Stream identifierStream = + db.streamKeys( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier( + slotAndBlockRoot.getSlot(), slotAndBlockRoot.getBlockRoot(), UInt64.ZERO), + new ColumnSlotAndIdentifier( + slotAndBlockRoot.getSlot(), slotAndBlockRoot.getBlockRoot(), UInt64.MAX_VALUE))) { + return identifierStream.toList(); + } + } + public Optional getRawVariable(final KvStoreVariable var) { return db.getRaw(var); } @@ -398,6 +430,25 @@ public void setEarliestBlobSidecarSlot(final UInt64 slot) { transaction.put(schema.getVariableEarliestBlobSidecarSlot(), slot); } + @Override + public void setFirstIncompleteSlot(final UInt64 slot) { + transaction.put(schema.getVariableFirstIncompleteSlot(), slot); + } + + @Override + public void addSidecar(final DataColumnSidecar sidecar) { + transaction.put( + schema.getColumnSidecarByColumnSlotAndIdentifier(), + new ColumnSlotAndIdentifier( + sidecar.getSlot(), sidecar.getBlockRoot(), sidecar.getIndex()), + sidecar.sszSerialize()); + } + + @Override + public void removeSidecar(final ColumnSlotAndIdentifier identifier) { + transaction.delete(schema.getColumnSidecarByColumnSlotAndIdentifier(), identifier); + } + @Override public void commit() { // Commit db updates diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java index c284f6aba5a..ab513119340 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaCombined.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public interface SchemaCombined extends Schema { @@ -62,6 +63,8 @@ public interface SchemaCombined extends Schema { KvStoreColumn getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex(); + KvStoreColumn getColumnSidecarByColumnSlotAndIdentifier(); + // Variables KvStoreVariable getVariableGenesisTime(); @@ -85,6 +88,8 @@ public interface SchemaCombined extends Schema { KvStoreVariable getVariableFinalizedDepositSnapshot(); + KvStoreVariable getVariableFirstIncompleteSlot(); + Map> getColumnMap(); Map> getVariableMap(); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java index 7120bf77f44..e453570ff66 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/SchemaFinalizedSnapshotStateAdapter.java @@ -22,6 +22,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public class SchemaFinalizedSnapshotStateAdapter implements SchemaFinalizedSnapshotState { @@ -49,6 +50,10 @@ public KvStoreColumn getColumnFinalizedStatesBySlot() { return delegate.getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex(); } + public KvStoreColumn getColumnSidecarByColumnSlotAndIdentifier() { + return delegate.getColumnSidecarByColumnSlotAndIdentifier(); + } + public Map> getColumnMap() { return ImmutableMap.>builder() .put("SLOTS_BY_FINALIZED_ROOT", getColumnSlotsByFinalizedRoot()) @@ -63,6 +68,7 @@ public KvStoreColumn getColumnFinalizedStatesBySlot() { .put( "NON_CANONICAL_BLOB_SIDECAR_BY_SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX", getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex()) + .put("SIDECAR_BY_COLUMN_SLOT_AND_IDENTIFIER", getColumnSidecarByColumnSlotAndIdentifier()) .build(); } @@ -106,11 +112,17 @@ public KvStoreVariable getVariableEarliestBlobSidecarSlot() { return delegate.getVariableEarliestBlobSidecarSlot(); } + public KvStoreVariable getVariableFirstIncompleteSlot() { + return delegate.getVariableFirstIncompleteSlot(); + } + public Map> getVariableMap() { return Map.of( "OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot(), "EARLIEST_BLOB_SIDECAR_SLOT", - getVariableEarliestBlobSidecarSlot()); + getVariableEarliestBlobSidecarSlot(), + "FIRST_INCOMPLETE_SLOT", + getVariableFirstIncompleteSlot()); } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java index fa833459c3c..fc6b7ff6244 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombined.java @@ -84,6 +84,7 @@ public abstract class V6SchemaCombined implements SchemaCombined { private final KvStoreVariable optimisticTransitionBlockSlot; private final KvStoreVariable earliestBlobSidecarSlot; + private final KvStoreVariable firstIncompleteSlot; protected V6SchemaCombined(final Spec spec, final int finalizedOffset) { this.finalizedOffset = finalizedOffset; @@ -100,6 +101,7 @@ protected V6SchemaCombined(final Spec spec, final int finalizedOffset) { optimisticTransitionBlockSlot = KvStoreVariable.create(finalizedOffset + 1, UINT64_SERIALIZER); earliestBlobSidecarSlot = KvStoreVariable.create(finalizedOffset + 2, UINT64_SERIALIZER); + firstIncompleteSlot = KvStoreVariable.create(finalizedOffset + 3, UINT64_SERIALIZER); } @Override @@ -192,6 +194,11 @@ public KvStoreVariable getVariableEarliestBlobSidecarSlot() { return earliestBlobSidecarSlot; } + @Override + public KvStoreVariable getVariableFirstIncompleteSlot() { + return firstIncompleteSlot; + } + @Override public Map> getColumnMap() { return ImmutableMap.>builder() @@ -227,6 +234,7 @@ public Map> getVariableMap() { .put("OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot()) .put("FINALIZED_DEPOSIT_SNAPSHOT", getVariableFinalizedDepositSnapshot()) .put("EARLIEST_BLOB_SIDECAR_SLOT", getVariableEarliestBlobSidecarSlot()) + .put("FIRST_INCOMPLETE_SLOT", getVariableFirstIncompleteSlot()) .build(); } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedSnapshot.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedSnapshot.java index cff71d1b931..f95e5c61ba4 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedSnapshot.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedSnapshot.java @@ -17,6 +17,7 @@ import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BLOCK_ROOTS_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BYTES32_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BYTES_SERIALIZER; +import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.COLUMN_SLOT_AND_IDENTIFIER_KEY_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX_KEY_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.UINT64_SERIALIZER; @@ -31,6 +32,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer; @@ -47,6 +49,7 @@ public class V6SchemaCombinedSnapshot extends V6SchemaCombined private final KvStoreColumn blobSidecarBySlotRootBlobIndex; private final KvStoreColumn nonCanonicalBlobSidecarBySlotRootBlobIndex; + private final KvStoreColumn sidecarByColumnSlotAndIdentifier; private final List deletedColumnIds; private V6SchemaCombinedSnapshot(final Spec spec, final int finalizedOffset) { @@ -82,6 +85,10 @@ private V6SchemaCombinedSnapshot(final Spec spec, final int finalizedOffset) { SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX_KEY_SERIALIZER, BYTES_SERIALIZER); + sidecarByColumnSlotAndIdentifier = + KvStoreColumn.create( + finalizedOffset + 14, COLUMN_SLOT_AND_IDENTIFIER_KEY_SERIALIZER, BYTES_SERIALIZER); + deletedColumnIds = List.of( asColumnId(finalizedOffset + 7), @@ -141,6 +148,11 @@ public KvStoreColumn> getColumnNonCanonicalRootsBySlot() { return nonCanonicalBlobSidecarBySlotRootBlobIndex; } + @Override + public KvStoreColumn getColumnSidecarByColumnSlotAndIdentifier() { + return sidecarByColumnSlotAndIdentifier; + } + @Override public Map> getColumnMap() { return ImmutableMap.>builder() @@ -163,6 +175,7 @@ public KvStoreColumn> getColumnNonCanonicalRootsBySlot() { .put( "NON_CANONICAL_BLOB_SIDECAR_BY_SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX", getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex()) + .put("SIDECAR_BY_COLUMN_SLOT_AND_IDENTIFIER", getColumnSidecarByColumnSlotAndIdentifier()) .build(); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java index 5375e869c88..ea842be9e9a 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/schema/V6SchemaCombinedTreeState.java @@ -17,6 +17,7 @@ import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BLOCK_ROOTS_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BYTES32_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.BYTES_SERIALIZER; +import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.COLUMN_SLOT_AND_IDENTIFIER_KEY_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.COMPRESSED_BRANCH_INFO_KV_STORE_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX_KEY_SERIALIZER; import static tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer.UINT64_SERIALIZER; @@ -32,6 +33,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.server.kvstore.serialization.KvStoreSerializer; @@ -49,6 +51,7 @@ public class V6SchemaCombinedTreeState extends V6SchemaCombined implements Schem private final KvStoreColumn blobSidecarBySlotRootBlobIndex; private final KvStoreColumn nonCanonicalBlobSidecarBySlotRootBlobIndex; + private final KvStoreColumn sidecarByColumnSlotAndIdentifier; private final List deletedColumnIds; public V6SchemaCombinedTreeState(final Spec spec) { @@ -88,6 +91,9 @@ public V6SchemaCombinedTreeState(final Spec spec) { finalizedOffset + 15, SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX_KEY_SERIALIZER, BYTES_SERIALIZER); + sidecarByColumnSlotAndIdentifier = + KvStoreColumn.create( + finalizedOffset + 16, COLUMN_SLOT_AND_IDENTIFIER_KEY_SERIALIZER, BYTES_SERIALIZER); deletedColumnIds = List.of( asColumnId(finalizedOffset + 9), @@ -149,6 +155,11 @@ public KvStoreColumn> getColumnNonCanonicalRootsBySlot() { return nonCanonicalBlobSidecarBySlotRootBlobIndex; } + @Override + public KvStoreColumn getColumnSidecarByColumnSlotAndIdentifier() { + return sidecarByColumnSlotAndIdentifier; + } + @Override public Map> getVariableMap() { return ImmutableMap.>builder() @@ -163,6 +174,7 @@ public Map> getVariableMap() { .put("OPTIMISTIC_TRANSITION_BLOCK_SLOT", getOptimisticTransitionBlockSlot()) .put("FINALIZED_DEPOSIT_SNAPSHOT", getVariableFinalizedDepositSnapshot()) .put("EARLIEST_BLOB_SIDECAR_SLOT", getVariableEarliestBlobSidecarSlot()) + .put("FIRST_INCOMPLETE_SLOT", getVariableFirstIncompleteSlot()) .build(); } @@ -190,6 +202,7 @@ public Map> getVariableMap() { .put( "NON_CANONICAL_BLOB_SIDECAR_BY_SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX", getColumnNonCanonicalBlobSidecarBySlotRootBlobIndex()) + .put("SIDECAR_BY_COLUMN_SLOT_AND_IDENTIFIER", getColumnSidecarByColumnSlotAndIdentifier()) .build(); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/ColumnSlotAndIdentifierKeySerializer.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/ColumnSlotAndIdentifierKeySerializer.java new file mode 100644 index 00000000000..5afe3cffab2 --- /dev/null +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/ColumnSlotAndIdentifierKeySerializer.java @@ -0,0 +1,57 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.storage.server.kvstore.serialization; + +import static com.google.common.base.Preconditions.checkArgument; + +import com.google.common.primitives.Longs; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; + +/** + * This serializer is intended to be used as a Key so that it preserve slot ordering when we stream + * data. This is useful for values that are always looked up by root, slot and columnIndex, giving + * us the ability to quickly lookup most recent\oldest values by slot as well as perform pruning + * based on slot + */ +class ColumnSlotAndIdentifierKeySerializer implements KvStoreSerializer { + static final int SLOT_SIZE = Long.BYTES; + static final int BLOCK_ROOT_SIZE = Bytes32.SIZE; + static final int COLUMN_INDEX_SIZE = Long.BYTES; + + static final int SLOT_OFFSET = 0; + static final int BLOCK_ROOT_OFFSET = SLOT_OFFSET + SLOT_SIZE; + static final int COLUMN_INDEX_OFFSET = BLOCK_ROOT_OFFSET + BLOCK_ROOT_SIZE; + static final int DATA_SIZE = COLUMN_INDEX_OFFSET + COLUMN_INDEX_SIZE; + + @Override + public ColumnSlotAndIdentifier deserialize(final byte[] data) { + checkArgument(data.length == DATA_SIZE); + final UInt64 slot = UInt64Serializer.deserialize(data, SLOT_OFFSET); + final Bytes32 blockRoot = Bytes32.wrap(data, BLOCK_ROOT_OFFSET); + final UInt64 columnIndex = UInt64Serializer.deserialize(data, COLUMN_INDEX_OFFSET); + return new ColumnSlotAndIdentifier(slot, blockRoot, columnIndex); + } + + @Override + public byte[] serialize(final ColumnSlotAndIdentifier value) { + return Bytes.concatenate( + Bytes.wrap(Longs.toByteArray(value.slot().longValue())), + value.identifier().getBlockRoot(), + Bytes.wrap(Longs.toByteArray(value.identifier().getIndex().longValue()))) + .toArrayUnsafe(); + } +} diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/KvStoreSerializer.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/KvStoreSerializer.java index ecaa64c3b50..055c0b75397 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/KvStoreSerializer.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/serialization/KvStoreSerializer.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public interface KvStoreSerializer { @@ -56,6 +57,8 @@ public interface KvStoreSerializer { KvStoreSerializer SLOT_AND_BLOCK_ROOT_AND_BLOB_INDEX_KEY_SERIALIZER = new SlotAndBlockRootAndBlobIndexKeySerializer(); + KvStoreSerializer COLUMN_SLOT_AND_IDENTIFIER_KEY_SERIALIZER = + new ColumnSlotAndIdentifierKeySerializer(); static KvStoreSerializer createStateSerializer(final Spec spec) { return new BeaconStateSerializer(spec); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java index ab743596af1..6a6dbf849dc 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java @@ -30,6 +30,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; @@ -37,6 +38,7 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.api.OnDiskStoreData; import tech.pegasys.teku.storage.api.StorageUpdate; @@ -327,6 +329,32 @@ public boolean pruneOldestNonCanonicalBlobSidecars( return false; } + @Override + public Optional getFirstIncompleteSlot() { + return Optional.empty(); + } + + @Override + public Optional getSidecar(final ColumnSlotAndIdentifier identifier) { + return Optional.empty(); + } + + @Override + @MustBeClosed + public Stream streamDataColumnIdentifiers( + final UInt64 firstSlot, final UInt64 lastSlot) { + return Stream.empty(); + } + + @Override + public void setFirstIncompleteSlot(UInt64 slot) {} + + @Override + public void addSidecar(DataColumnSidecar sidecar) {} + + @Override + public void pruneAllSidecars(UInt64 tillSlot) {} + @Override public void close() {} } From 294661b9591a8a03ac5771e3f23e172a7ca715f6 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Mon, 22 Apr 2024 07:56:34 +0400 Subject: [PATCH 30/70] Peerdas db part2 (#20) * Enable non-canonical sidecars cleanup * database part2 --- .../spec/config/builder/ElectraBuilder.java | 3 +- .../datacolumns/CustodySync.java | 6 +- .../DataColumnSidecarCustodyImpl.java | 13 +++- .../datacolumns/DataColumnSidecarDBImpl.java | 68 +++++++++++++++++++ .../DataColumnSidecarGossipValidator.java | 2 +- .../beaconchain/BeaconChainController.java | 2 + .../services/chainstorage/StorageService.java | 4 +- .../storage/api/SidecarUpdateChannel.java | 30 ++++++++ .../teku/storage/api/StorageQueryChannel.java | 8 +++ .../teku/storage/api/StorageUpdate.java | 9 ++- .../storage/server/kvstore/DatabaseTest.java | 12 ++-- .../client/CombinedChainDataClient.java | 35 ++++++++++ .../teku/storage/client/RecentChainData.java | 2 + .../client/StorageBackedRecentChainData.java | 7 ++ .../teku/storage/server/ChainStorage.java | 46 ++++++++++++- .../CombinedStorageChannelSplitter.java | 18 +++++ .../server/kvstore/KvStoreDatabase.java | 6 +- .../store/StoreTransactionUpdates.java | 8 ++- .../store/StoreTransactionUpdatesFactory.java | 4 +- .../StorageBackedRecentChainDataTest.java | 6 ++ .../storage/api/StubSidecarUpdateChannel.java | 28 ++++++++ .../storage/api/StubStorageQueryChannel.java | 18 +++++ .../client/MemoryOnlyRecentChainData.java | 12 ++++ .../storage/storageSystem/StorageSystem.java | 1 + 24 files changed, 329 insertions(+), 19 deletions(-) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java create mode 100644 storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java create mode 100644 storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index 83514eb2bc7..2f6fcf28deb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -47,7 +47,8 @@ public class ElectraBuilder implements ForkConfigBuilder missingColumnsToRequest = custody .streamMissingColumns() - .filter(c -> !pendingRequests.containsKey(c.identifier())) + .filter(c -> !pendingRequests.containsKey(c)) .limit(newRequestCount) .collect(Collectors.toSet()); @@ -75,7 +75,7 @@ private synchronized void fillUp() { SafeFuture promise = retriever.retrieve(missingColumn); PendingRequest request = new PendingRequest(missingColumn, promise); pendingRequests.put(missingColumn, request); - promise.thenAccept(__ -> onRequestComplete(request)); + promise.thenAccept(__ -> onRequestComplete(request)).ifExceptionGetsHereRaiseABug(); } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 118d32ec873..7acf0d77cf1 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -27,8 +27,10 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.storage.client.CombinedChainDataClient; public class DataColumnSidecarCustodyImpl implements DataColumnSidecarCustody, SlotEventsChannel { @@ -79,13 +81,19 @@ public boolean isIncomplete() { public DataColumnSidecarCustodyImpl( Spec spec, + CombinedChainDataClient combinedChainDataClient, DataColumnSidecarDB db, - BlockChainAccessor blockChainAccessor, UInt256 nodeId, int totalCustodySubnetCount) { this.spec = spec; this.db = db; - this.blockChainAccessor = blockChainAccessor; + // FIXME: I stink! + this.blockChainAccessor = + slot -> + combinedChainDataClient + .getBlockAtSlotExact(slot) + .thenApply(maybeBlock -> maybeBlock.map(SignedBeaconBlock::getRoot)) + .join(); this.nodeId = nodeId; this.totalCustodySubnetCount = totalCustodySubnetCount; this.electraStartEpoch = spec.getForkSchedule().getFork(SpecMilestone.ELECTRA).getEpoch(); @@ -171,6 +179,7 @@ private Stream streamSlotCustodies() { }); } + @Override public Stream streamMissingColumns() { return streamSlotCustodies() .flatMap( diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java new file mode 100644 index 00000000000..1309820d530 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; +import tech.pegasys.teku.storage.client.CombinedChainDataClient; + +// FIXME: remove stinky joins +public class DataColumnSidecarDBImpl implements DataColumnSidecarDB { + private final CombinedChainDataClient combinedChainDataClient; + private final SidecarUpdateChannel sidecarUpdateChannel; + + public DataColumnSidecarDBImpl( + final CombinedChainDataClient combinedChainDataClient, + final SidecarUpdateChannel sidecarUpdateChannel) { + this.combinedChainDataClient = combinedChainDataClient; + this.sidecarUpdateChannel = sidecarUpdateChannel; + } + + @Override + public Optional getFirstIncompleteSlot() { + return combinedChainDataClient.getFirstIncompleteSlot().join(); + } + + @Override + public Optional getSidecar(final DataColumnIdentifier identifier) { + return combinedChainDataClient.getSidecar(identifier).join(); + } + + @Override + public Stream streamColumnIdentifiers(final UInt64 slot) { + return combinedChainDataClient.getDataColumnIdentifiers(slot).join().stream() + .map(ColumnSlotAndIdentifier::identifier); + } + + @Override + public void setFirstIncompleteSlot(final UInt64 slot) { + sidecarUpdateChannel.onFirstIncompleteSlot(slot); + } + + @Override + public void addSidecar(final DataColumnSidecar sidecar) { + sidecarUpdateChannel.onNewSidecar(sidecar); + } + + // TODO: clarify, is it inclusive? change input name + @Override + public void pruneAllSidecars(final UInt64 tillSlot) { + sidecarUpdateChannel.onSidecarsAvailabilitySlot(tillSlot); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java index f4aa273e7a9..2b491482e00 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java @@ -29,7 +29,7 @@ public static DataColumnSidecarGossipValidator create(DataColumnSidecarValidator (__, err) -> err == null ? InternalValidationResult.ACCEPT - : InternalValidationResult.reject(err.toString())); + : InternalValidationResult.reject("Error: %s", err)); } SafeFuture validate(DataColumnSidecar dataColumnSidecar); diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 5e0aa404790..217650b7694 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -176,6 +176,7 @@ import tech.pegasys.teku.storage.api.CombinedStorageChannel; import tech.pegasys.teku.storage.api.Eth1DepositStorageChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageQueryChannel; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; @@ -429,6 +430,7 @@ protected SafeFuture initialize() { storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + eventChannels.getPublisher(SidecarUpdateChannel.class), eventChannels.getPublisher(FinalizedCheckpointChannel.class, beaconAsyncRunner), coalescingChainHeadChannel, validatorIsConnectedProvider, diff --git a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java index f2215bdbbb5..6892e18d091 100644 --- a/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java +++ b/services/chainstorage/src/main/java/tech/pegasys/teku/services/chainstorage/StorageService.java @@ -29,6 +29,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.storage.api.CombinedStorageChannel; import tech.pegasys.teku.storage.api.Eth1DepositStorageChannel; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; import tech.pegasys.teku.storage.server.BatchingVoteUpdateChannel; import tech.pegasys.teku.storage.server.ChainStorage; @@ -159,7 +160,8 @@ protected SafeFuture doStart() { eventChannels .subscribe(Eth1DepositStorageChannel.class, depositStorage) .subscribe(Eth1EventsChannel.class, depositStorage) - .subscribe(VoteUpdateChannel.class, batchingVoteUpdateChannel); + .subscribe(VoteUpdateChannel.class, batchingVoteUpdateChannel) + .subscribe(SidecarUpdateChannel.class, chainStorage); }) .thenCompose( __ -> diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java new file mode 100644 index 00000000000..a4fd9c48628 --- /dev/null +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.storage.api; + +import tech.pegasys.teku.infrastructure.events.VoidReturningChannelInterface; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +public interface SidecarUpdateChannel extends VoidReturningChannelInterface { + + // TODO: as it's pushed separately from sidecars, an eventual consistency could occur. + // Clarify that it's safe + void onFirstIncompleteSlot(UInt64 slot); + + void onNewSidecar(DataColumnSidecar sidecar); + + // TODO: Make a dedicated pruner instead + void onSidecarsAvailabilitySlot(UInt64 earliestSlotRequired); +} diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java index 5220fde9c0a..757f61a4763 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java @@ -23,12 +23,14 @@ import tech.pegasys.teku.infrastructure.events.ChannelInterface; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public interface StorageQueryChannel extends ChannelInterface { @@ -107,4 +109,10 @@ SafeFuture> getBlobSidecarKeys( SafeFuture> getBlobSidecarKeys( SlotAndBlockRoot slotAndBlockRoot); + + SafeFuture> getFirstIncompleteSlot(); + + SafeFuture> getSidecar(ColumnSlotAndIdentifier identifier); + + SafeFuture> getDataColumnIdentifiers(UInt64 slot); } diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageUpdate.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageUpdate.java index fc20346493d..2b2395f5470 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageUpdate.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageUpdate.java @@ -47,6 +47,7 @@ public class StorageUpdate { private final boolean optimisticTransitionBlockRootSet; private final Optional optimisticTransitionBlockRoot; private final boolean blobSidecarsEnabled; + private final boolean sidecarsEnabled; private final boolean isEmpty; public StorageUpdate( @@ -62,7 +63,8 @@ public StorageUpdate( final Map stateRoots, final boolean optimisticTransitionBlockRootSet, final Optional optimisticTransitionBlockRoot, - @NonUpdating final boolean blobSidecarsEnabled) { + @NonUpdating final boolean blobSidecarsEnabled, + @NonUpdating final boolean sidecarsEnabled) { this.genesisTime = genesisTime; this.finalizedChainData = finalizedChainData; this.justifiedCheckpoint = justifiedCheckpoint; @@ -76,6 +78,7 @@ public StorageUpdate( this.optimisticTransitionBlockRootSet = optimisticTransitionBlockRootSet; this.optimisticTransitionBlockRoot = optimisticTransitionBlockRoot; this.blobSidecarsEnabled = blobSidecarsEnabled; + this.sidecarsEnabled = sidecarsEnabled; checkArgument( optimisticTransitionBlockRootSet || optimisticTransitionBlockRoot.isEmpty(), "Can't have optimisticTransitionBlockRoot present but not set"); @@ -168,6 +171,10 @@ public boolean isBlobSidecarsEnabled() { return blobSidecarsEnabled; } + public boolean isSidecarsEnabled() { + return sidecarsEnabled; + } + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @interface NonUpdating {} diff --git a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java index ce0e31940fa..6e24e53780e 100644 --- a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java +++ b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java @@ -243,7 +243,8 @@ public void verifyBlobsLifecycle(final DatabaseContext context) throws IOExcepti Map.of(), false, Optional.empty(), - true)); + true, + false)); database.update( new StorageUpdate( Optional.empty(), @@ -258,7 +259,8 @@ public void verifyBlobsLifecycle(final DatabaseContext context) throws IOExcepti Map.of(), false, Optional.empty(), - true)); + true, + false)); // Will not be overridden from Database interface, only initial set assertThat(database.getEarliestBlobSidecarSlot()).contains(ZERO); @@ -387,7 +389,8 @@ public void verifyNonCanonicalBlobsLifecycle(final DatabaseContext context) thro Map.of(), false, Optional.empty(), - true)); + true, + false)); database.update( new StorageUpdate( Optional.empty(), @@ -402,7 +405,8 @@ public void verifyNonCanonicalBlobsLifecycle(final DatabaseContext context) thro Map.of(), false, Optional.empty(), - true)); + true, + false)); // check all non-canonical blobs are present List.of(blobSidecar1_0, blobSidecar2_0, blobSidecar2_1, blobSidecar3_0, blobSidecar5_0) diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java index 6d328b35bc3..6b1577694cc 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java @@ -31,6 +31,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.MinimalBeaconBlockSummary; @@ -42,12 +43,14 @@ import tech.pegasys.teku.spec.datastructures.forkchoice.ReadOnlyStore; import tech.pegasys.teku.spec.datastructures.genesis.GenesisData; import tech.pegasys.teku.spec.datastructures.metadata.BlockAndMetaData; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.CheckpointState; import tech.pegasys.teku.spec.datastructures.state.CommitteeAssignment; import tech.pegasys.teku.spec.datastructures.state.ForkInfo; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.EpochProcessingException; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.SlotProcessingException; @@ -822,4 +825,36 @@ private boolean isOptimistic( public SafeFuture> getInitialAnchor() { return historicalChainData.getAnchor(); } + + public SafeFuture> getFirstIncompleteSlot() { + return historicalChainData.getFirstIncompleteSlot(); + } + + public SafeFuture> getSidecar(final DataColumnIdentifier identifier) { + final Optional hotSlotForBlockRoot = + recentChainData.getSlotForBlockRoot(identifier.getBlockRoot()); + if (hotSlotForBlockRoot.isPresent()) { + return getSidecar(new ColumnSlotAndIdentifier(hotSlotForBlockRoot.get(), identifier)); + } + return historicalChainData + .getBlockByBlockRoot(identifier.getBlockRoot()) + .thenCompose( + blockOptional -> { + if (blockOptional.isPresent()) { + return getSidecar( + new ColumnSlotAndIdentifier(blockOptional.get().getSlot(), identifier)); + } else { + return SafeFuture.completedFuture(Optional.empty()); + } + }); + } + + public SafeFuture> getSidecar( + final ColumnSlotAndIdentifier identifier) { + return historicalChainData.getSidecar(identifier); + } + + public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { + return historicalChainData.getDataColumnIdentifiers(slot); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java b/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java index 5f8a6b9ddf3..a4499d50a89 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/RecentChainData.java @@ -62,6 +62,7 @@ import tech.pegasys.teku.storage.api.ChainHeadChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; import tech.pegasys.teku.storage.api.ReorgContext; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; import tech.pegasys.teku.storage.protoarray.ForkChoiceStrategy; @@ -119,6 +120,7 @@ public abstract class RecentChainData implements StoreUpdateHandler { final EarliestBlobSidecarSlotProvider earliestBlobSidecarSlotProvider, final StorageUpdateChannel storageUpdateChannel, final VoteUpdateChannel voteUpdateChannel, + final SidecarUpdateChannel sidecarUpdateChannel, final FinalizedCheckpointChannel finalizedCheckpointChannel, final ChainHeadChannel chainHeadChannel, final ValidatorIsConnectedProvider validatorIsConnectedProvider, diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainData.java b/storage/src/main/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainData.java index 83766dc9558..6b46ac0b421 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainData.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainData.java @@ -34,6 +34,7 @@ import tech.pegasys.teku.storage.api.ChainHeadChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; import tech.pegasys.teku.storage.api.OnDiskStoreData; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageQueryChannel; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; @@ -58,6 +59,7 @@ public StorageBackedRecentChainData( final StorageQueryChannel storageQueryChannel, final StorageUpdateChannel storageUpdateChannel, final VoteUpdateChannel voteUpdateChannel, + final SidecarUpdateChannel sidecarUpdateChannel, final FinalizedCheckpointChannel finalizedCheckpointChannel, final ChainHeadChannel chainHeadChannel, final ValidatorIsConnectedProvider validatorIsConnectedProvider, @@ -73,6 +75,7 @@ public StorageBackedRecentChainData( storageQueryChannel::getEarliestAvailableBlobSidecarSlot, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -93,6 +96,7 @@ public static SafeFuture create( final StorageQueryChannel storageQueryChannel, final StorageUpdateChannel storageUpdateChannel, final VoteUpdateChannel voteUpdateChannel, + final SidecarUpdateChannel sidecarUpdateChannel, final FinalizedCheckpointChannel finalizedCheckpointChannel, final ChainHeadChannel chainHeadChannel, final ValidatorIsConnectedProvider validatorIsConnectedProvider, @@ -108,6 +112,7 @@ public static SafeFuture create( storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -127,6 +132,7 @@ public static RecentChainData createImmediately( final StorageQueryChannel storageQueryChannel, final StorageUpdateChannel storageUpdateChannel, final VoteUpdateChannel voteUpdateChannel, + final SidecarUpdateChannel sidecarUpdateChannel, final FinalizedCheckpointChannel finalizedCheckpointChannel, final ChainHeadChannel chainHeadChannel, final ValidatorIsConnectedProvider validatorIsConnectedProvider, @@ -142,6 +148,7 @@ public static RecentChainData createImmediately( storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java index 6a7c8b17d2c..e62295a11c8 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -38,9 +39,11 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.api.ChainStorageFacade; import tech.pegasys.teku.storage.api.OnDiskStoreData; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageQueryChannel; import tech.pegasys.teku.storage.api.StorageUpdate; import tech.pegasys.teku.storage.api.StorageUpdateChannel; @@ -51,7 +54,11 @@ import tech.pegasys.teku.storage.server.state.FinalizedStateCache; public class ChainStorage - implements StorageUpdateChannel, StorageQueryChannel, VoteUpdateChannel, ChainStorageFacade { + implements StorageUpdateChannel, + StorageQueryChannel, + VoteUpdateChannel, + SidecarUpdateChannel, + ChainStorageFacade { private static final Logger LOG = LogManager.getLogger(); private final Database database; private final FinalizedStateCache finalizedStateCache; @@ -359,4 +366,41 @@ public SafeFuture> getBlobSidecarKeys( final SlotAndBlockRoot slotAndBlockRoot) { return SafeFuture.of(() -> database.getBlobSidecarKeys(slotAndBlockRoot)); } + + @Override + public SafeFuture> getFirstIncompleteSlot() { + return SafeFuture.of(database::getFirstIncompleteSlot); + } + + @Override + public SafeFuture> getSidecar( + final ColumnSlotAndIdentifier identifier) { + return SafeFuture.of(() -> database.getSidecar(identifier)); + } + + @Override + public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { + return SafeFuture.of( + () -> { + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(slot)) { + return dataColumnIdentifiersStream.toList(); + } + }); + } + + @Override + public void onFirstIncompleteSlot(final UInt64 slot) { + database.setFirstIncompleteSlot(slot); + } + + @Override + public void onNewSidecar(final DataColumnSidecar sidecar) { + database.addSidecar(sidecar); + } + + @Override + public void onSidecarsAvailabilitySlot(final UInt64 earliestSlotRequired) { + database.pruneAllSidecars(earliestSlotRequired.minusMinZero(1)); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java index 49fdd7983e8..a834ba69147 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; @@ -31,6 +32,7 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.storage.api.CombinedStorageChannel; import tech.pegasys.teku.storage.api.OnDiskStoreData; @@ -244,4 +246,20 @@ public SafeFuture> getBlobSidecarKeys( final SlotAndBlockRoot slotAndBlockRoot) { return asyncRunner.runAsync(() -> queryDelegate.getBlobSidecarKeys(slotAndBlockRoot)); } + + @Override + public SafeFuture> getFirstIncompleteSlot() { + return asyncRunner.runAsync(queryDelegate::getFirstIncompleteSlot); + } + + @Override + public SafeFuture> getSidecar( + final ColumnSlotAndIdentifier identifier) { + return asyncRunner.runAsync(() -> queryDelegate.getSidecar(identifier)); + } + + @Override + public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { + return asyncRunner.runAsync(() -> queryDelegate.getDataColumnIdentifiers(slot)); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index 6a4137754aa..1067b263267 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -993,6 +993,10 @@ private UpdateResult doUpdate(final StorageUpdate update) { update.getEarliestBlobSidecarSlot(), update.getBlobSidecars().values().stream().flatMap(Collection::stream)); } + if (update.isSidecarsEnabled()) { + removeNonCanonicalSidecars( + update.getDeletedHotBlocks(), update.getFinalizedChildToParentMap()); + } long finalizedDataUpdatedTime = System.currentTimeMillis(); LOG.trace("Applying hot updates"); @@ -1157,8 +1161,6 @@ private void removeNonCanonicalBlobSidecars( } } - // TODO: link on storage update when sidecars are enabled - @SuppressWarnings("UnusedMethod") private void removeNonCanonicalSidecars( final Map deletedHotBlocks, final Map finalizedChildToParentMap) { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdates.java b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdates.java index ba6c478eb45..81de5fbc63a 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdates.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdates.java @@ -46,6 +46,7 @@ class StoreTransactionUpdates { private final boolean optimisticTransitionBlockRootSet; private final Optional optimisticTransitionBlockRoot; private final boolean blobSidecarsEnabled; + private final boolean sidecarsEnabled; StoreTransactionUpdates( final StoreTransaction tx, @@ -59,7 +60,8 @@ class StoreTransactionUpdates { final Map stateRoots, final boolean optimisticTransitionBlockRootSet, final Optional optimisticTransitionBlockRoot, - final boolean blobSidecarsEnabled) { + final boolean blobSidecarsEnabled, + final boolean sidecarsEnabled) { checkNotNull(tx, "Transaction is required"); checkNotNull(finalizedChainData, "Finalized data is required"); checkNotNull(hotBlocks, "Hot blocks are required"); @@ -82,6 +84,7 @@ class StoreTransactionUpdates { this.optimisticTransitionBlockRootSet = optimisticTransitionBlockRootSet; this.optimisticTransitionBlockRoot = optimisticTransitionBlockRoot; this.blobSidecarsEnabled = blobSidecarsEnabled; + this.sidecarsEnabled = sidecarsEnabled; } public StorageUpdate createStorageUpdate() { @@ -98,7 +101,8 @@ public StorageUpdate createStorageUpdate() { stateRoots, optimisticTransitionBlockRootSet, optimisticTransitionBlockRoot, - blobSidecarsEnabled); + blobSidecarsEnabled, + sidecarsEnabled); } public void applyToStore(final Store store, final UpdateResult updateResult) { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java index c1e8d90832f..ed5dcc16e61 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java @@ -257,6 +257,8 @@ private StoreTransactionUpdates createStoreTransactionUpdates( stateRoots, optimisticTransitionBlockRootSet, optimisticTransitionBlockRoot, - spec.isMilestoneSupported(SpecMilestone.DENEB)); + // FIXME: suboptimal criteria, doesn't fade out when blobs are over + spec.isMilestoneSupported(SpecMilestone.DENEB), + spec.isMilestoneSupported(SpecMilestone.ELECTRA)); } } diff --git a/storage/src/test/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainDataTest.java b/storage/src/test/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainDataTest.java index 779f7524e87..166c66ead33 100644 --- a/storage/src/test/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainDataTest.java +++ b/storage/src/test/java/tech/pegasys/teku/storage/client/StorageBackedRecentChainDataTest.java @@ -43,6 +43,7 @@ import tech.pegasys.teku.storage.api.ChainHeadChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; import tech.pegasys.teku.storage.api.OnDiskStoreData; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageQueryChannel; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.StubChainHeadChannel; @@ -61,6 +62,7 @@ public class StorageBackedRecentChainDataTest { private final StorageQueryChannel storageQueryChannel = mock(StorageQueryChannel.class); private final StorageUpdateChannel storageUpdateChannel = mock(StorageUpdateChannel.class); private final VoteUpdateChannel voteUpdateChannel = mock(VoteUpdateChannel.class); + private final SidecarUpdateChannel sidecarUpdateChannel = mock(SidecarUpdateChannel.class); private final FinalizedCheckpointChannel finalizedCheckpointChannel = new StubFinalizedCheckpointChannel(); private final ChainHeadChannel chainHeadChannel = new StubChainHeadChannel(); @@ -88,6 +90,7 @@ public void storageBackedClient_storeInitializeViaGetStoreRequest() storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -139,6 +142,7 @@ public void storageBackedClient_storeInitializeViaNewGenesisState() storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -193,6 +197,7 @@ public void storageBackedClient_storeInitializeViaGetStoreRequestAfterTimeout() storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -243,6 +248,7 @@ public void storageBackedClient_storeInitializeViaGetStoreRequestAfterIOExceptio storageQueryChannel, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java new file mode 100644 index 00000000000..2eeb808d976 --- /dev/null +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java @@ -0,0 +1,28 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.storage.api; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; + +public class StubSidecarUpdateChannel implements SidecarUpdateChannel { + @Override + public void onFirstIncompleteSlot(UInt64 slot) {} + + @Override + public void onNewSidecar(DataColumnSidecar sidecar) {} + + @Override + public void onSidecarsAvailabilitySlot(UInt64 earliestSlotRequired) {} +} diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java index 45bc5ba345b..8919c9ddd98 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java @@ -24,12 +24,14 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; public class StubStorageQueryChannel implements StorageQueryChannel { @@ -176,4 +178,20 @@ public SafeFuture> getBlobSidecarsBySlotAndBlockRoot( final SlotAndBlockRoot slotAndBlockRoot) { return SafeFuture.completedFuture(Collections.emptyList()); } + + @Override + public SafeFuture> getFirstIncompleteSlot() { + return SafeFuture.completedFuture(Optional.empty()); + } + + @Override + public SafeFuture> getSidecar( + final ColumnSlotAndIdentifier identifier) { + return SafeFuture.completedFuture(Optional.empty()); + } + + @Override + public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { + return SafeFuture.completedFuture(Collections.emptyList()); + } } diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/client/MemoryOnlyRecentChainData.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/client/MemoryOnlyRecentChainData.java index 0f429961b94..62d04c92500 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/client/MemoryOnlyRecentChainData.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/client/MemoryOnlyRecentChainData.java @@ -28,9 +28,11 @@ import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.storage.api.ChainHeadChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; +import tech.pegasys.teku.storage.api.SidecarUpdateChannel; import tech.pegasys.teku.storage.api.StorageUpdateChannel; import tech.pegasys.teku.storage.api.StubChainHeadChannel; import tech.pegasys.teku.storage.api.StubFinalizedCheckpointChannel; +import tech.pegasys.teku.storage.api.StubSidecarUpdateChannel; import tech.pegasys.teku.storage.api.StubStorageUpdateChannel; import tech.pegasys.teku.storage.api.VoteUpdateChannel; import tech.pegasys.teku.storage.store.StoreConfig; @@ -43,6 +45,7 @@ private MemoryOnlyRecentChainData( final StoreConfig storeConfig, final StorageUpdateChannel storageUpdateChannel, final VoteUpdateChannel voteUpdateChannel, + final SidecarUpdateChannel sidecarUpdateChannel, final FinalizedCheckpointChannel finalizedCheckpointChannel, final ChainHeadChannel chainHeadChannel, final ValidatorIsConnectedProvider validatorIsConnectedProvider, @@ -58,6 +61,7 @@ private MemoryOnlyRecentChainData( EarliestBlobSidecarSlotProvider.NOOP, storageUpdateChannel, voteUpdateChannel, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -84,6 +88,7 @@ public static class Builder { private StoreConfig storeConfig = StoreConfig.createDefault(); private Spec spec = TestSpecFactory.createMinimalPhase0(); private StorageUpdateChannel storageUpdateChannel = new StubStorageUpdateChannel(); + private SidecarUpdateChannel sidecarUpdateChannel = new StubSidecarUpdateChannel(); private FinalizedCheckpointChannel finalizedCheckpointChannel = new StubFinalizedCheckpointChannel(); private ChainHeadChannel chainHeadChannel = new StubChainHeadChannel(); @@ -98,6 +103,7 @@ public RecentChainData build() { storeConfig, storageUpdateChannel, votes -> {}, + sidecarUpdateChannel, finalizedCheckpointChannel, chainHeadChannel, validatorIsConnectedProvider, @@ -122,6 +128,12 @@ public Builder storageUpdateChannel(final StorageUpdateChannel storageUpdateChan return this; } + public Builder sidecarsUpdateChannel(final SidecarUpdateChannel sidecarUpdateChannel) { + checkNotNull(sidecarUpdateChannel); + this.sidecarUpdateChannel = sidecarUpdateChannel; + return this; + } + public Builder validatorIsConnectedProvider( final ValidatorIsConnectedProvider validatorIsConnectedProvider) { this.validatorIsConnectedProvider = validatorIsConnectedProvider; diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java index c71959bfb9a..3513b56f197 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/storageSystem/StorageSystem.java @@ -114,6 +114,7 @@ static StorageSystem create( chainStorageServer, chainStorageServer, chainStorageServer, + chainStorageServer, finalizedCheckpointChannel, chainHeadChannel, ValidatorIsConnectedProvider.NOOP, From ad67fca8bff1dc47e56a53a60213cdaa284a86e2 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 23 Apr 2024 17:42:19 +0400 Subject: [PATCH 31/70] Some more DAS core interfaces and some draft implementations (#21) * DataColumnReqResp to abstract over network Req/Resp implementation * DataColumnPeerSearcher to abstract over ConnectionManager + PeerSelectionStrategy * DataColumnManager which notifies on peer connect/disconnect can ban peers returning invalid req/resp responses (this could probably fit into existing peers scoring classes) * DataAvailabilitySampler: minimalistic for now * Add simple implementations ofDataColumnSidecarRetriever and DataAvailabilitySampler interfaces --- .../datacolumns/DataAvailabilitySampler.java | 23 +++ .../DataColumnSidecarCustodyImpl.java | 1 + .../SimpleDataAvailabilitySampler.java | 75 +++++++ .../retriever/DataColumnPeerManager.java | 30 +++ .../retriever/DataColumnPeerSearcher.java | 26 +++ .../retriever/DataColumnReqResp.java | 29 +++ .../retriever/SimpleSidecarRetriever.java | 195 ++++++++++++++++++ .../ValidatingDataColumnReqResp.java | 59 ++++++ .../p2p/discovery/DiscoveryPeer.java | 10 +- .../discovery/discv5/NodeRecordConverter.java | 12 +- 10 files changed, 458 insertions(+), 2 deletions(-) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataAvailabilitySampler.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataAvailabilitySampler.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataAvailabilitySampler.java new file mode 100644 index 00000000000..0e95b70e522 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataAvailabilitySampler.java @@ -0,0 +1,23 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public interface DataAvailabilitySampler { + + SafeFuture checkDataAvailability(UInt64 slot, Bytes32 blockRoot); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 7acf0d77cf1..6a310480fd6 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -83,6 +83,7 @@ public DataColumnSidecarCustodyImpl( Spec spec, CombinedChainDataClient combinedChainDataClient, DataColumnSidecarDB db, + BlockChainAccessor blockChainAccessor, UInt256 nodeId, int totalCustodySubnetCount) { this.spec = spec; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java new file mode 100644 index 00000000000..fac09bce0f4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java @@ -0,0 +1,75 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public class SimpleDataAvailabilitySampler implements DataAvailabilitySampler { + + private final DataColumnSidecarRetriever retriever; + private final Random random; + private final int samplesPerSlot; + private final int extBlobColumnCount; + + public SimpleDataAvailabilitySampler( + DataColumnSidecarRetriever retriever, + Random random, + int samplesPerSlot, + int extBlobColumnCount) { + this.retriever = retriever; + this.random = random; + this.samplesPerSlot = samplesPerSlot; + this.extBlobColumnCount = extBlobColumnCount; + } + + private Collection getColumnsForSampling() { + List allColumnIndexes = + Stream.iterate(0, (idx) -> idx < extBlobColumnCount, (idx) -> idx + 1) + .map(UInt64::valueOf) + .collect(Collectors.toList()); // Use Collectors to get a mutable List instance + + List selectedIndexes = new ArrayList<>(); + for (int i = 0; i < samplesPerSlot; i++) { + int idx = random.nextInt(allColumnIndexes.size()); + UInt64 columnIndex = allColumnIndexes.remove(idx); + selectedIndexes.add(columnIndex); + } + return selectedIndexes; + } + + @Override + public SafeFuture checkDataAvailability(UInt64 slot, Bytes32 blockRoot) { + + Collection columnsForSampling = getColumnsForSampling(); + + Stream> samplingPromises = + columnsForSampling.stream() + .map( + columnIndex -> + retriever.retrieve( + new ColumnSlotAndIdentifier( + slot, new DataColumnIdentifier(blockRoot, columnIndex)))); + + return SafeFuture.allOf(samplingPromises); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java new file mode 100644 index 00000000000..4c7bdaab833 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; + +public interface DataColumnPeerManager extends DataColumnPeerSearcher { + + void addPeerListener(PeerListener listener); + + void banNode(UInt256 node); + + interface PeerListener { + + void peerConnected(UInt256 nodeId, int extraCustodySubnetCount); + + void peerDisconnected(UInt256 nodeId); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java new file mode 100644 index 00000000000..75f366ca295 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java @@ -0,0 +1,26 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public interface DataColumnPeerSearcher { + + PeerSearchRequest requestPeers(UInt64 slot, UInt64 columnIndex); + + interface PeerSearchRequest { + + void dispose(); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java new file mode 100644 index 00000000000..707562394b6 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java @@ -0,0 +1,29 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public interface DataColumnReqResp { + + SafeFuture requestDataColumnSidecar( + UInt256 nodeId, DataColumnIdentifier columnIdentifier); + + void flush(); + + int getCurrentRequestLimit(UInt256 nodeId); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java new file mode 100644 index 00000000000..3abed2f041b --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -0,0 +1,195 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarRetriever; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; + +// TODO improve thread-safety: external calls are better to do outside of the synchronize block to +// prevent potential dead locks +public class SimpleSidecarRetriever + implements DataColumnSidecarRetriever, DataColumnPeerManager.PeerListener { + + private final Spec spec; + private final DataColumnPeerManager peerManager; + private final DataColumnReqResp reqResp; + + public SimpleSidecarRetriever( + Spec spec, + DataColumnPeerManager peerManager, + DataColumnReqResp reqResp, + DataColumnSidecarValidator validator) { + this.spec = spec; + this.peerManager = peerManager; + this.reqResp = new ValidatingDataColumnReqResp(peerManager, reqResp, validator); + peerManager.addPeerListener(this); + } + + private final Map pendingRequests = + new LinkedHashMap<>(); + private final Map connectedPeers = new HashMap<>(); + + @Override + public synchronized SafeFuture retrieve(ColumnSlotAndIdentifier columnId) { + DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest = + peerManager.requestPeers(columnId.slot(), columnId.identifier().getIndex()); + + synchronized (this) { + RetrieveRequest existingRequest = pendingRequests.get(columnId); + if (existingRequest == null) { + RetrieveRequest request = new RetrieveRequest(columnId, peerSearchRequest); + pendingRequests.put(columnId, request); + return request.result; + } else { + peerSearchRequest.dispose(); + return existingRequest.result; + } + } + } + + private synchronized List matchRequestsAndPeers() { + disposeCancelledRequests(); + return pendingRequests.entrySet().stream() + .filter(entry -> entry.getValue().activeRpcRequest == null) + .flatMap( + entry -> { + RetrieveRequest request = entry.getValue(); + return findBestMatchingPeer(request).stream() + .map(peer -> new RequestMatch(peer, request)); + }) + .toList(); + } + + private Optional findBestMatchingPeer(RetrieveRequest request) { + return findMatchingPeers(request).stream() + .max(Comparator.comparing(peer -> reqResp.getCurrentRequestLimit(peer.nodeId))); + } + + private Collection findMatchingPeers(RetrieveRequest request) { + return connectedPeers.values().stream() + .filter(peer -> peer.isCustodyFor(request.columnId)) + .filter(peer -> reqResp.getCurrentRequestLimit(peer.nodeId) > 0) + .toList(); + } + + private void disposeCancelledRequests() { + Iterator> pendingIterator = + pendingRequests.entrySet().iterator(); + while (pendingIterator.hasNext()) { + Map.Entry pendingEntry = pendingIterator.next(); + RetrieveRequest pendingRequest = pendingEntry.getValue(); + if (pendingRequest.result.isCancelled()) { + pendingIterator.remove(); + pendingRequest.peerSearchRequest.dispose(); + if (pendingRequest.activeRpcRequest != null) { + pendingRequest.activeRpcRequest.cancel(true); + } + } + } + } + + // TODO implement triggering of rounds or do it in a finer grained fashion + void nextRound() { + List matches = matchRequestsAndPeers(); + for (RequestMatch match : matches) { + SafeFuture reqRespPromise = + reqResp.requestDataColumnSidecar(match.peer.nodeId, match.request.columnId.identifier()); + match.request.activeRpcRequest = + reqRespPromise.whenComplete( + (sidecar, err) -> reqRespCompleted(match.request, sidecar, err)); + } + + reqResp.flush(); + } + + @SuppressWarnings("unused") + private void reqRespCompleted( + RetrieveRequest request, DataColumnSidecar maybeResult, Throwable maybeError) { + if (maybeResult != null) { + synchronized (this) { + pendingRequests.remove(request.columnId); + } + request.peerSearchRequest.dispose(); + } else { + request.activeRpcRequest = null; + } + } + + @Override + public synchronized void peerConnected(UInt256 nodeId, int extraCustodySubnetCount) { + connectedPeers.put(nodeId, new ConnectedPeer(nodeId, extraCustodySubnetCount)); + } + + @Override + public synchronized void peerDisconnected(UInt256 nodeId) { + connectedPeers.remove(nodeId); + } + + private static class RetrieveRequest { + final ColumnSlotAndIdentifier columnId; + final DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest; + final SafeFuture result = new SafeFuture<>(); + volatile SafeFuture activeRpcRequest = null; + + private RetrieveRequest( + ColumnSlotAndIdentifier columnId, DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest) { + this.columnId = columnId; + this.peerSearchRequest = peerSearchRequest; + } + } + + private class ConnectedPeer { + final UInt256 nodeId; + final int extraCustodySubnetCount; + + public ConnectedPeer(UInt256 nodeId, int extraCustodySubnetCount) { + this.nodeId = nodeId; + this.extraCustodySubnetCount = extraCustodySubnetCount; + } + + private Set getNodeCustodyIndexes(UInt64 slot) { + UInt64 epoch = spec.computeEpochAtSlot(slot); + SpecVersion specVersion = spec.atSlot(slot); + int minCustodyRequirement = + SpecConfigElectra.required(specVersion.getConfig()).getCustodyRequirement(); + return MiscHelpersElectra.required(specVersion.miscHelpers()) + .computeCustodyColumnIndexes( + nodeId, epoch, minCustodyRequirement + extraCustodySubnetCount); + } + + public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { + return getNodeCustodyIndexes(columnId.slot()).contains(columnId.identifier().getIndex()); + } + } + + private record RequestMatch(ConnectedPeer peer, RetrieveRequest request) {} +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java new file mode 100644 index 00000000000..087870b9ebd --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java @@ -0,0 +1,59 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; + +public class ValidatingDataColumnReqResp implements DataColumnReqResp { + + private final DataColumnPeerManager peerManager; + private final DataColumnReqResp reqResp; + private final DataColumnSidecarValidator validator; + + public ValidatingDataColumnReqResp( + DataColumnPeerManager peerManager, + DataColumnReqResp reqResp, + DataColumnSidecarValidator validator) { + this.peerManager = peerManager; + this.reqResp = reqResp; + this.validator = validator; + } + + @Override + public SafeFuture requestDataColumnSidecar( + UInt256 nodeId, DataColumnIdentifier columnIdentifier) { + return reqResp + .requestDataColumnSidecar(nodeId, columnIdentifier) + .thenCompose( + sidecar -> + validator + .validate(sidecar) + .thenApply(__ -> sidecar) + .catchAndRethrow(err -> peerManager.banNode(nodeId))); + } + + @Override + public int getCurrentRequestLimit(UInt256 nodeId) { + return reqResp.getCurrentRequestLimit(nodeId); + } + + @Override + public void flush() { + reqResp.flush(); + } +} diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java index e357b8af773..3e515a66d36 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java @@ -18,6 +18,7 @@ import java.net.InetSocketAddress; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.web3j.abi.datatypes.Int; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EnrForkId; @@ -27,18 +28,21 @@ public class DiscoveryPeer { private final Optional enrForkId; private final SszBitvector persistentAttestationSubnets; private final SszBitvector syncCommitteeSubnets; + private final int dasExtraCustodySubnetCount; public DiscoveryPeer( final Bytes publicKey, final InetSocketAddress nodeAddress, final Optional enrForkId, final SszBitvector persistentAttestationSubnets, - final SszBitvector syncCommitteeSubnets) { + final SszBitvector syncCommitteeSubnets, + final Optional dasExtraCustodySubnetCount) { this.publicKey = publicKey; this.nodeAddress = nodeAddress; this.enrForkId = enrForkId; this.persistentAttestationSubnets = persistentAttestationSubnets; this.syncCommitteeSubnets = syncCommitteeSubnets; + this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount.orElse(0); } public Bytes getPublicKey() { @@ -61,6 +65,10 @@ public SszBitvector getSyncCommitteeSubnets() { return syncCommitteeSubnets; } + public int getDasExtraCustodySubnetCount() { + return dasExtraCustodySubnetCount; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index 6889becad42..17a573ca101 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.networking.p2p.discovery.discv5; import static tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork.ATTESTATION_SUBNET_ENR_FIELD; +import static tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork.DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD; import static tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork.ETH2_ENR_FIELD; import static tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork.SYNC_COMMITTEE_SUBNET_ENR_FIELD; @@ -26,6 +27,8 @@ import org.ethereum.beacon.discovery.schema.EnrField; import org.ethereum.beacon.discovery.schema.NodeRecord; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EnrForkId; @@ -60,13 +63,20 @@ private static DiscoveryPeer socketAddressToDiscoveryPeer( final SszBitvector syncCommitteeSubnets = parseField(nodeRecord, SYNC_COMMITTEE_SUBNET_ENR_FIELD, syncnetsSchema::fromBytes) .orElse(syncnetsSchema.getDefault()); + final Optional dasExtraCustodySubnetCount = + parseField( + nodeRecord, + DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD, + SszPrimitiveSchemas.UINT64_SCHEMA::sszDeserialize) + .map(i -> i.get().intValue()); return new DiscoveryPeer( ((Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1)), address, enrForkId, persistentAttestationSubnets, - syncCommitteeSubnets); + syncCommitteeSubnets, + dasExtraCustodySubnetCount); } private static Optional parseField( From 67b4d56064e7096a7669fa6b3e356a1a9784087a Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 24 Apr 2024 11:16:22 +0400 Subject: [PATCH 32/70] DataColumnSidecars database tests (#22) * database integration tests * found a bug with missed commit on pruning, fixed * minor old interface clarification input name --- .../versions/electra/DataColumnSchema.java | 6 + .../versions/electra/DataColumnSidecar.java | 49 ++++--- .../electra/DataColumnSidecarSchema.java | 58 +++++--- .../util/ColumnSlotAndIdentifier.java | 8 ++ .../schemas/SchemaDefinitionsElectra.java | 11 ++ .../teku/spec/util/DataStructureUtil.java | 103 +++++++++++++++ .../datacolumns/DataColumnSidecarDBImpl.java | 5 +- .../storage/server/kvstore/DatabaseTest.java | 124 ++++++++++++++++++ .../pegasys/teku/storage/server/Database.java | 2 +- .../server/kvstore/KvStoreDatabase.java | 1 + .../storage/server/noop/NoOpDatabase.java | 2 +- 11 files changed, 323 insertions(+), 46 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java index 40a199d59c1..97464c8e4fb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +import java.util.List; import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.config.SpecConfigElectra; @@ -23,6 +24,11 @@ public DataColumnSchema(final SpecConfigElectra specConfig) { super(new CellSchema(specConfig), specConfig.getMaxBlobCommitmentsPerBlock()); } + public DataColumn create(final List cells) { + final TreeNode backingNode = this.createTreeFromElements(cells); + return createFromBackingNode(backingNode); + } + @Override public DataColumn createFromBackingNode(TreeNode node) { return new DataColumn(this, node); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java index 83e77be05bc..a9787ee97ad 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java @@ -13,14 +13,18 @@ package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +import java.util.List; import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.logging.LogFormatter; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszBytes32Vector; import tech.pegasys.teku.infrastructure.ssz.containers.Container6; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.kzg.KZGProof; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; @@ -41,27 +45,30 @@ public class DataColumnSidecar super(dataColumnSidecarSchema, backingTreeNode); } - // public DataColumnSidecar( - // final DataColumnSidecarSchema schema, - // final UInt64 index, - // final Blob blob, - // final SszKZGCommitment sszKzgCommitment, - // final SszKZGProof sszKzgProof, - // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentInclusionProof) { - // super( - // schema, - // SszUInt64.of(index), - // schema.getDataColumnSszSchema().create(blob.getBytes()), - // sszKzgCommitment, - // sszKzgProof, - // signedBeaconBlockHeader, - // schema - // .getKzgCommitmentInclusionProofSchema() - // - // .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); - // } - // + public DataColumnSidecar( + final DataColumnSidecarSchema schema, + final UInt64 index, + final DataColumn dataColumn, + final List kzgCommitments, + final List kzgProofs, + final SignedBeaconBlockHeader signedBeaconBlockHeader, + final List kzgCommitmentInclusionProof) { + super( + schema, + SszUInt64.of(index), + dataColumn, + schema + .getKzgCommitmentsSchema() + .createFromElements(kzgCommitments.stream().map(SszKZGCommitment::new).toList()), + schema + .getKzgProofsSchema() + .createFromElements(kzgProofs.stream().map(SszKZGProof::new).toList()), + signedBeaconBlockHeader, + schema + .getKzgCommitmentInclusionProofSchema() + .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); + } + // public DataColumnSidecar( // final DataColumnSidecarSchema schema, // final UInt64 index, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java index 6ae1512bac8..99d70b62681 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszBytes32Vector; import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema6; @@ -22,6 +24,9 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBytes32VectorSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.kzg.KZGProof; import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeaderSchema; @@ -41,6 +46,8 @@ public class DataColumnSidecarSchema SszBytes32Vector> { static final SszFieldName FIELD_BLOB = () -> "column"; + static final SszFieldName FIELD_KZG_COMMITMENTS = () -> "kzg_commitments"; + static final SszFieldName FIELD_KZG_PROOFS = () -> "kzg_proofs"; static final SszFieldName FIELD_SIGNED_BLOCK_HEADER = () -> "signed_block_header"; static final SszFieldName FIELD_KZG_COMMITMENT_INCLUSION_PROOF = () -> "kzg_commitment_inclusion_proof"; @@ -54,11 +61,11 @@ public class DataColumnSidecarSchema namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), namedSchema(FIELD_BLOB, dataColumnSchema), namedSchema( - "kzg_commitments", + FIELD_KZG_COMMITMENTS, SszListSchema.create( SszKZGCommitmentSchema.INSTANCE, specConfig.getMaxBlobCommitmentsPerBlock())), namedSchema( - "kzg_proofs", + FIELD_KZG_PROOFS, SszListSchema.create( SszKZGProofSchema.INSTANCE, specConfig.getMaxBlobCommitmentsPerBlock())), namedSchema(FIELD_SIGNED_BLOCK_HEADER, signedBeaconBlockHeaderSchema), @@ -81,6 +88,17 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENT_INCLUSION_PROOF)); } + @SuppressWarnings("unchecked") + public SszListSchema getKzgCommitmentsSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENTS)); + } + + @SuppressWarnings("unchecked") + public SszListSchema getKzgProofsSchema() { + return (SszListSchema) getChildSchema(getFieldIndex(FIELD_KZG_PROOFS)); + } + // public DataColumnSidecar create( // final UInt64 index, // final Blob blob, @@ -113,24 +131,24 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { // signedBeaconBlockHeader, // kzgCommitmentInclusionProof); // } - // - // public DataColumnSidecar create( - // final UInt64 index, - // final Blob blob, - // final KZGCommitment kzgCommitment, - // final KZGProof kzgProof, - // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentInclusionProof) { - // return new DataColumnSidecar( - // this, - // index, - // blob, - // kzgCommitment, - // kzgProof, - // signedBeaconBlockHeader, - // kzgCommitmentInclusionProof); - // } - // + + public DataColumnSidecar create( + final UInt64 index, + final DataColumn dataColumn, + final List kzgCommitments, + final List kzgProofs, + final SignedBeaconBlockHeader signedBeaconBlockHeader, + final List kzgCommitmentInclusionProof) { + return new DataColumnSidecar( + this, + index, + dataColumn, + kzgCommitments, + kzgProofs, + signedBeaconBlockHeader, + kzgCommitmentInclusionProof); + } + public static DataColumnSidecarSchema create( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, final DataColumnSchema dataColumnSchema, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java index cf320e92bf0..eb65a8079c1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java @@ -17,6 +17,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.jetbrains.annotations.NotNull; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public record ColumnSlotAndIdentifier(UInt64 slot, DataColumnIdentifier identifier) @@ -26,6 +27,13 @@ public ColumnSlotAndIdentifier( this(slot, new DataColumnIdentifier(blockRoot, columnIndex)); } + public static ColumnSlotAndIdentifier fromDataColumn(final DataColumnSidecar dataColumnSidecar) { + return new ColumnSlotAndIdentifier( + dataColumnSidecar.getSlot(), + dataColumnSidecar.getBlockRoot(), + dataColumnSidecar.getIndex()); + } + @Override public int compareTo(@NotNull final ColumnSlotAndIdentifier o) { return Comparator.comparing(ColumnSlotAndIdentifier::slot) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index d5dd4e2e27f..1ceba9dc48e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -17,6 +17,7 @@ import java.util.Optional; import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.CellSchema; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSchema; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSchema; @@ -88,6 +89,7 @@ public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { pendingPartialWithdrawalSchema; private final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema; + private final CellSchema cellSchema; private final DataColumnSchema dataColumnSchema; private final DataColumnSidecarSchema dataColumnSidecarSchema; @@ -146,6 +148,7 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); this.pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); + this.cellSchema = new CellSchema(specConfig); this.dataColumnSchema = new DataColumnSchema(specConfig); this.dataColumnSidecarSchema = DataColumnSidecarSchema.create( @@ -296,4 +299,12 @@ public PendingConsolidation.PendingConsolidationSchema getPendingConsolidationSc public DataColumnSidecarSchema getDataColumnSidecarSchema() { return dataColumnSidecarSchema; } + + public DataColumnSchema getDataColumnSchema() { + return dataColumnSchema; + } + + public CellSchema getCellSchema() { + return cellSchema; + } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 7d307ecd14c..222473aca6d 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -90,6 +90,12 @@ import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSchema; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecarSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.Cell; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.CellSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumn; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; @@ -2414,6 +2420,103 @@ public BlobSidecar build() { } } + public class RandomSidecarBuilder { + private Optional index = Optional.empty(); + private Optional dataColumn = Optional.empty(); + private Optional> kzgCommitments = Optional.empty(); + private Optional> kzgProofs = Optional.empty(); + private Optional signedBeaconBlockHeader = Optional.empty(); + private Optional> kzgCommitmentInclusionProof = Optional.empty(); + + public RandomSidecarBuilder index(final UInt64 index) { + this.index = Optional.of(index); + return this; + } + + public RandomSidecarBuilder dataColumn(final DataColumn dataColumn) { + this.dataColumn = Optional.of(dataColumn); + return this; + } + + public RandomSidecarBuilder kzgCommitments(final List kzgCommitments) { + this.kzgCommitments = Optional.of(kzgCommitments); + return this; + } + + public RandomSidecarBuilder kzgProofs(final List kzgProofs) { + this.kzgProofs = Optional.of(kzgProofs); + return this; + } + + public RandomSidecarBuilder signedBeaconBlockHeader( + final SignedBeaconBlockHeader signedBeaconBlockHeader) { + this.signedBeaconBlockHeader = Optional.of(signedBeaconBlockHeader); + return this; + } + + public RandomSidecarBuilder kzgCommitmentInclusionProof( + final List kzgCommitmentInclusionProof) { + this.kzgCommitmentInclusionProof = Optional.of(kzgCommitmentInclusionProof); + return this; + } + + public DataColumnSidecar build() { + final SignedBeaconBlockHeader signedBlockHeader = + signedBeaconBlockHeader.orElseGet(DataStructureUtil.this::randomSignedBeaconBlockHeader); + final DataColumnSidecarSchema dataColumnSidecarSchema = + getElectraSchemaDefinitions(signedBlockHeader.getMessage().getSlot()) + .getDataColumnSidecarSchema(); + final int numberOfProofs = + kzgProofs + .map(List::size) + .or(() -> kzgCommitments.map(List::size)) + .orElseGet(DataStructureUtil.this::randomNumberOfBlobsPerBlock); + + return dataColumnSidecarSchema.create( + index.orElseGet(DataStructureUtil.this::randomBlobSidecarIndex), + dataColumn.orElseGet(() -> randomDataColumn(signedBlockHeader.getMessage().getSlot())), + kzgCommitments.orElseGet( + () -> + IntStream.range(0, numberOfProofs) + .mapToObj(__ -> randomKZGCommitment()) + .toList()), + kzgProofs.orElseGet( + () -> IntStream.range(0, numberOfProofs).mapToObj(__ -> randomKZGProof()).toList()), + signedBlockHeader, + kzgCommitmentInclusionProof.orElseGet( + () -> + IntStream.range( + 0, + dataColumnSidecarSchema + .getKzgCommitmentInclusionProofSchema() + .getLength()) + .mapToObj(__ -> randomBytes32()) + .toList())); + } + } + + public DataColumn randomDataColumn(final UInt64 slot) { + final DataColumnSchema dataColumnSchema = + getElectraSchemaDefinitions(slot).getDataColumnSchema(); + List list = + IntStream.range(0, randomNumberOfBlobsPerBlock()).mapToObj(__ -> randomCell(slot)).toList(); + return dataColumnSchema.create(list); + } + + public Cell randomCell(final UInt64 slot) { + final CellSchema cellSchema = getElectraSchemaDefinitions(slot).getCellSchema(); + return cellSchema.create(randomBytes(cellSchema.getLength())); + } + + public DataColumnSidecar randomDataColumnSidecar() { + return new RandomSidecarBuilder().build(); + } + + public DataColumnSidecar randomDataColumnSidecar( + final SignedBeaconBlockHeader header, final UInt64 index) { + return new RandomSidecarBuilder().signedBeaconBlockHeader(header).index(index).build(); + } + public List randomKzgCommitmentInclusionProof() { final int depth = SpecConfigDeneb.required(spec.forMilestone(SpecMilestone.DENEB).getConfig()) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index 1309820d530..4d2be1212d9 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -60,9 +60,8 @@ public void addSidecar(final DataColumnSidecar sidecar) { sidecarUpdateChannel.onNewSidecar(sidecar); } - // TODO: clarify, is it inclusive? change input name @Override - public void pruneAllSidecars(final UInt64 tillSlot) { - sidecarUpdateChannel.onSidecarsAvailabilitySlot(tillSlot); + public void pruneAllSidecars(final UInt64 tillSlotExclusive) { + sidecarUpdateChannel.onSidecarsAvailabilitySlot(tillSlotExclusive); } } diff --git a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java index 6e24e53780e..170f6764f73 100644 --- a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java +++ b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java @@ -64,7 +64,9 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; @@ -73,6 +75,7 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.spec.datastructures.util.SlotAndBlockRootAndBlobIndex; import tech.pegasys.teku.spec.executionlayer.PayloadStatus; import tech.pegasys.teku.spec.generator.ChainBuilder; @@ -2171,6 +2174,127 @@ public void pruneFinalizedBlocks_shouldRemoveFinalizedBlocks(final DatabaseConte assertThat(lastPrunedSlot3).isEqualTo(UInt64.valueOf(4)); } + @TestTemplate + public void addSidecar_isOperative(final DatabaseContext context) throws IOException { + setupWithSpec(TestSpecFactory.createMinimalElectra()); + initialize(context); + final DataColumnSidecar dataColumnSidecar = dataStructureUtil.randomDataColumnSidecar(); + final ColumnSlotAndIdentifier columnSlotAndIdentifier = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar); + assertThat(database.getSidecar(columnSlotAndIdentifier).isEmpty()).isTrue(); + + database.addSidecar(dataColumnSidecar); + assertThat(database.getSidecar(columnSlotAndIdentifier)).contains(dataColumnSidecar); + } + + @TestTemplate + public void setFirstIncompleteSlot_isOperative(final DatabaseContext context) throws IOException { + setupWithSpec(TestSpecFactory.createMinimalElectra()); + initialize(context); + assertThat(database.getFirstIncompleteSlot().isEmpty()).isTrue(); + + final UInt64 incompleteSlot = UInt64.valueOf(123); + database.setFirstIncompleteSlot(UInt64.valueOf(123)); + assertThat(database.getFirstIncompleteSlot()).contains(incompleteSlot); + } + + @TestTemplate + @SuppressWarnings("JavaCase") + public void streamDataColumnIdentifiers_isOperative(final DatabaseContext context) + throws IOException { + setupWithSpec(TestSpecFactory.createMinimalElectra()); + initialize(context); + + final SignedBeaconBlockHeader blockHeader1 = dataStructureUtil.randomSignedBeaconBlockHeader(); + final DataColumnSidecar dataColumnSidecar1_0 = + dataStructureUtil.randomDataColumnSidecar(blockHeader1, ZERO); + final ColumnSlotAndIdentifier columnSlotAndIdentifier1_0 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar1_0); + final DataColumnSidecar dataColumnSidecar1_1 = + dataStructureUtil.randomDataColumnSidecar(blockHeader1, ONE); + final ColumnSlotAndIdentifier columnSlotAndIdentifier1_1 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar1_1); + + final SignedBeaconBlockHeader blockHeader2 = + dataStructureUtil.randomSignedBeaconBlockHeader( + blockHeader1.getMessage().getSlot().plus(100)); + final DataColumnSidecar dataColumnSidecar2_0 = + dataStructureUtil.randomDataColumnSidecar(blockHeader2, ZERO); + final ColumnSlotAndIdentifier columnSlotAndIdentifier2_0 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar2_0); + + database.addSidecar(dataColumnSidecar1_0); + database.addSidecar(dataColumnSidecar1_1); + database.addSidecar(dataColumnSidecar2_0); + + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(dataColumnSidecar1_0.getSlot().plus(1))) { + assertThat(dataColumnIdentifiersStream.toList()).isEmpty(); + } + + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(dataColumnSidecar1_0.getSlot())) { + assertThat(dataColumnIdentifiersStream.toList()) + .containsExactly(columnSlotAndIdentifier1_0, columnSlotAndIdentifier1_1); + } + + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers( + dataColumnSidecar1_0.getSlot().plus(1), dataColumnSidecar2_0.getSlot().plus(1))) { + assertThat(dataColumnIdentifiersStream.toList()).containsExactly(columnSlotAndIdentifier2_0); + } + } + + @TestTemplate + @SuppressWarnings("JavaCase") + public void pruneAllSidecars_isOperative(final DatabaseContext context) throws IOException { + setupWithSpec(TestSpecFactory.createMinimalElectra()); + initialize(context); + + final SignedBeaconBlockHeader blockHeader1 = + dataStructureUtil.randomSignedBeaconBlockHeader(ONE); + final DataColumnSidecar dataColumnSidecar1_0 = + dataStructureUtil.randomDataColumnSidecar(blockHeader1, ZERO); + final ColumnSlotAndIdentifier columnSlotAndIdentifier1_0 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar1_0); + final DataColumnSidecar dataColumnSidecar1_1 = + dataStructureUtil.randomDataColumnSidecar(blockHeader1, ONE); + final ColumnSlotAndIdentifier columnSlotAndIdentifier1_1 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar1_1); + + final SignedBeaconBlockHeader blockHeader2 = + dataStructureUtil.randomSignedBeaconBlockHeader( + blockHeader1.getMessage().getSlot().plus(100)); + final DataColumnSidecar dataColumnSidecar2_0 = + dataStructureUtil.randomDataColumnSidecar(blockHeader2, ZERO); + final ColumnSlotAndIdentifier columnSlotAndIdentifier2_0 = + ColumnSlotAndIdentifier.fromDataColumn(dataColumnSidecar2_0); + + database.addSidecar(dataColumnSidecar1_0); + database.addSidecar(dataColumnSidecar1_1); + database.addSidecar(dataColumnSidecar2_0); + + database.pruneAllSidecars(ZERO); + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(ZERO, dataColumnSidecar2_0.getSlot())) { + assertThat(dataColumnIdentifiersStream.toList()) + .containsExactly( + columnSlotAndIdentifier1_0, columnSlotAndIdentifier1_1, columnSlotAndIdentifier2_0); + } + + database.pruneAllSidecars(ONE); + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(ZERO, dataColumnSidecar2_0.getSlot())) { + assertThat(dataColumnIdentifiersStream.toList()).containsExactly(columnSlotAndIdentifier2_0); + } + + database.pruneAllSidecars(dataColumnSidecar2_0.getSlot()); + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(ZERO, dataColumnSidecar2_0.getSlot())) { + assertThat(dataColumnIdentifiersStream.toList()).isEmpty(); + } + } + private List> getFinalizedStateRootsList() { try (final Stream> roots = database.getFinalizedStateRoots()) { return roots.map(entry -> Map.entry(entry.getKey(), entry.getValue())).collect(toList()); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java index a973da9cd11..e9d018e5ae7 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java @@ -256,5 +256,5 @@ default Stream streamDataColumnIdentifiers(final UInt64 void addSidecar(DataColumnSidecar sidecar); - void pruneAllSidecars(UInt64 tillSlot); + void pruneAllSidecars(UInt64 tillSlotInclusive); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index 1067b263267..60bc456c2f5 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -964,6 +964,7 @@ public void pruneAllSidecars(final UInt64 tillSlotInclusive) { streamDataColumnIdentifiers(UInt64.ZERO, tillSlotInclusive); final FinalizedUpdater updater = finalizedUpdater()) { prunableIdentifiers.forEach(updater::removeSidecar); + updater.commit(); } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java index 6a6dbf849dc..c80629cf829 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java @@ -353,7 +353,7 @@ public void setFirstIncompleteSlot(UInt64 slot) {} public void addSidecar(DataColumnSidecar sidecar) {} @Override - public void pruneAllSidecars(UInt64 tillSlot) {} + public void pruneAllSidecars(UInt64 tillSlotInclusive) {} @Override public void close() {} From 1d1f478352ed84f0e1d8c9124c5319137b972032 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 25 Apr 2024 13:35:47 +0400 Subject: [PATCH 33/70] Integrate DAS column subnets to peer scoring (#23) --- .../retriever/SimpleSidecarRetriever.java | 3 +- .../networking/eth2/ActiveEth2P2PNetwork.java | 5 + .../eth2/Eth2P2PNetworkBuilder.java | 66 +++++++++- .../DataColumnSidecarSubnetTopicProvider.java | 40 ++++++ .../subnets/PeerSubnetSubscriptions.java | 119 ++++++++++++++---- .../eth2/gossip/subnets/SubnetScorer.java | 39 +++++- .../networking/eth2/peers/PeerScorer.java | 10 +- .../eth2/ActiveEth2P2PNetworkTest.java | 4 + .../subnets/PeerSubnetSubscriptionsTest.java | 14 ++- .../eth2/gossip/subnets/SubnetScorerTest.java | 37 ++++-- .../peers/Eth2PeerSelectionStrategyTest.java | 3 +- .../eth2/Eth2P2PNetworkFactory.java | 25 +++- .../networking/eth2/peers/StubPeerScorer.java | 29 ++++- .../p2p/discovery/DiscoveryPeer.java | 1 - .../p2p/discovery/discv5/DiscV5Service.java | 3 +- .../discovery/discv5/NodeRecordConverter.java | 1 - .../networking/p2p/libp2p/MultiaddrUtil.java | 2 +- .../p2p/connection/ConnectionManagerTest.java | 3 +- .../p2p/discovery/DiscoveryNetworkTest.java | 3 +- .../discv5/NodeRecordConverterTest.java | 50 ++++++-- .../p2p/libp2p/MultiaddrUtilTest.java | 12 +- 21 files changed, 397 insertions(+), 72 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetTopicProvider.java diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 3abed2f041b..fde2f3ecca8 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -161,7 +161,8 @@ private static class RetrieveRequest { volatile SafeFuture activeRpcRequest = null; private RetrieveRequest( - ColumnSlotAndIdentifier columnId, DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest) { + ColumnSlotAndIdentifier columnId, + DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest) { this.columnId = columnId; this.peerSearchRequest = peerSearchRequest; } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 9b2b35b2dd1..76195a90aef 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -69,6 +69,7 @@ public class ActiveEth2P2PNetwork extends DelegatingP2PNetwork impleme private final GossipConfigurator gossipConfigurator; private final SubnetSubscriptionService attestationSubnetService; private final SubnetSubscriptionService syncCommitteeSubnetService; + private final SubnetSubscriptionService dataColumnSidecarSubnetService; private final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; private final AtomicBoolean gossipStarted = new AtomicBoolean(false); private final Optional dasExtraCustodySubnetCount; @@ -92,6 +93,7 @@ public ActiveEth2P2PNetwork( final RecentChainData recentChainData, final SubnetSubscriptionService attestationSubnetService, final SubnetSubscriptionService syncCommitteeSubnetService, + final SubnetSubscriptionService dataColumnSidecarSubnetService, final GossipEncoding gossipEncoding, final GossipConfigurator gossipConfigurator, final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider, @@ -109,6 +111,7 @@ public ActiveEth2P2PNetwork( this.gossipConfigurator = gossipConfigurator; this.attestationSubnetService = attestationSubnetService; this.syncCommitteeSubnetService = syncCommitteeSubnetService; + this.dataColumnSidecarSubnetService = dataColumnSidecarSubnetService; this.processedAttestationSubscriptionProvider = processedAttestationSubscriptionProvider; this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount; this.allTopicsFilterEnabled = allTopicsFilterEnabled; @@ -340,11 +343,13 @@ public void unsubscribeFromSyncCommitteeSubnetId(final int subnetId) { @Override public void subscribeToDataColumnSidecarSubnetId(int subnetId) { gossipForkManager.subscribeToDataColumnSidecarSubnetId(subnetId); + dataColumnSidecarSubnetService.addSubscription(subnetId); } @Override public void unsubscribeFromDataColumnSidecarSubnetId(int subnetId) { gossipForkManager.unsubscribeFromDataColumnSidecarSubnetId(subnetId); + dataColumnSidecarSubnetService.removeSubscription(subnetId); } @Override diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 24d83b2798e..549d6574ac8 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -17,6 +17,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import com.google.common.base.Supplier; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -24,12 +25,16 @@ import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; import tech.pegasys.teku.networking.eth2.gossip.forks.GossipForkManager; @@ -41,6 +46,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; +import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.PeerSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.topics.Eth2GossipTopicFilter; @@ -67,7 +73,9 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationManager; import tech.pegasys.teku.networking.p2p.rpc.RpcMethod; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.Constants; +import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; @@ -139,6 +147,8 @@ public Eth2P2PNetwork build() { // Setup eth2 handlers final SubnetSubscriptionService attestationSubnetService = new SubnetSubscriptionService(); final SubnetSubscriptionService syncCommitteeSubnetService = new SubnetSubscriptionService(); + final SubnetSubscriptionService dataColumnSidecarSubnetService = + new SubnetSubscriptionService(); final RpcEncoding rpcEncoding = RpcEncoding.createSszSnappyEncoding(spec.getNetworkingConfig().getMaxChunkSize()); if (statusMessageFactory == null) { @@ -169,7 +179,8 @@ public Eth2P2PNetwork build() { final GossipEncoding gossipEncoding = config.getGossipEncoding(); // Build core network and inject eth2 handlers - final DiscoveryNetwork network = buildNetwork(gossipEncoding, syncCommitteeSubnetService); + final DiscoveryNetwork network = + buildNetwork(gossipEncoding, syncCommitteeSubnetService, dataColumnSidecarSubnetService); final GossipForkManager gossipForkManager = buildGossipForkManager(gossipEncoding, network); @@ -188,6 +199,7 @@ public Eth2P2PNetwork build() { combinedChainDataClient.getRecentChainData(), attestationSubnetService, syncCommitteeSubnetService, + dataColumnSidecarSubnetService, gossipEncoding, config.getGossipConfigurator(), processedAttestationSubscriptionProvider, @@ -319,7 +331,8 @@ private GossipForkSubscriptions createSubscriptions( protected DiscoveryNetwork buildNetwork( final GossipEncoding gossipEncoding, - final SubnetSubscriptionService syncCommitteeSubnetService) { + final SubnetSubscriptionService syncCommitteeSubnetService, + final SubnetSubscriptionService dataColumnSidecarSubnetService) { final PeerPools peerPools = new PeerPools(); final ReputationManager reputationManager = new DefaultReputationManager( @@ -356,14 +369,58 @@ protected DiscoveryNetwork buildNetwork( final SyncCommitteeSubnetTopicProvider syncCommitteeSubnetTopicProvider = new SyncCommitteeSubnetTopicProvider( combinedChainDataClient.getRecentChainData(), gossipEncoding); + final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = + new DataColumnSidecarSubnetTopicProvider( + combinedChainDataClient.getRecentChainData(), gossipEncoding); final TargetPeerRange targetPeerRange = new TargetPeerRange( discoConfig.getMinPeers(), discoConfig.getMaxPeers(), discoConfig.getMinRandomlySelectedPeers()); + final Supplier currentSpecVersionSupplier = + () -> combinedChainDataClient.getRecentChainData().getCurrentSpec(); final SchemaDefinitionsSupplier currentSchemaDefinitions = () -> combinedChainDataClient.getRecentChainData().getCurrentSpec().getSchemaDefinitions(); + final Supplier> currentSlotSupplier = + () -> combinedChainDataClient.getRecentChainData().getCurrentSlot(); + + final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator + nodeIdToDataColumnSidecarSubnetsCalculator = + (nodeId, extraSubnetCount) -> { + SpecVersion currentSpecVersion = currentSpecVersionSupplier.get(); + Integer custodyRequirement = + currentSpecVersion + .getConfig() + .toVersionElectra() + .map(NetworkingSpecConfigElectra::getCustodyRequirement) + .orElse(0); + Optional currentEpoch = + currentSlotSupplier + .get() + .map(slot -> currentSpecVersion.miscHelpers().computeEpochAtSlot(slot)); + + SszBitvectorSchema bitvectorSchema = + SszBitvectorSchema.create(config.getTargetSubnetSubscriberCount()); + Optional sszBits = + currentSpecVersion + .miscHelpers() + .toVersionElectra() + .flatMap( + electraHelpers -> + currentEpoch.map( + epoch -> { + List nodeSubnets = + electraHelpers.computeDataColumnSidecarBackboneSubnets( + UInt256.fromBytes(nodeId.toBytes()), + epoch, + custodyRequirement + extraSubnetCount); + return bitvectorSchema.ofBits( + nodeSubnets.stream().map(UInt64::intValue).toList()); + })); + return sszBits.orElseGet(bitvectorSchema::getDefault); + }; + final SettableLabelledGauge subnetPeerCountGauge = SettableLabelledGauge.create( metricsSystem, @@ -382,11 +439,14 @@ protected DiscoveryNetwork buildNetwork( targetPeerRange, network -> PeerSubnetSubscriptions.create( - currentSchemaDefinitions, + currentSpecVersionSupplier.get(), + nodeIdToDataColumnSidecarSubnetsCalculator, network, attestationSubnetTopicProvider, syncCommitteeSubnetTopicProvider, syncCommitteeSubnetService, + dataColumnSidecarSubnetTopicProvider, + dataColumnSidecarSubnetService, config.getTargetSubnetSubscriberCount(), subnetPeerCountGauge), reputationManager, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetTopicProvider.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetTopicProvider.java new file mode 100644 index 00000000000..bc55ea5f7ca --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetTopicProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip.subnets; + +import static tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopics.getDataColumnSidecarSubnetTopic; + +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.storage.client.RecentChainData; + +public class DataColumnSidecarSubnetTopicProvider { + private final Spec spec; + private final RecentChainData recentChainData; + private final GossipEncoding gossipEncoding; + + public DataColumnSidecarSubnetTopicProvider( + final RecentChainData recentChainData, final GossipEncoding gossipEncoding) { + this.spec = recentChainData.getSpec(); + this.recentChainData = recentChainData; + this.gossipEncoding = gossipEncoding; + } + + public String getTopicForSubnet(final int subnetId) { + final Bytes4 forkDigest = + recentChainData.getCurrentForkInfo().orElseThrow().getForkDigest(spec); + return getDataColumnSidecarSubnetTopic(forkDigest, subnetId, gossipEncoding); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index 8f79951ab88..1268be55061 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.OptionalInt; import java.util.function.Consumer; @@ -33,36 +34,63 @@ import tech.pegasys.teku.networking.eth2.peers.PeerScorer; import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; import tech.pegasys.teku.networking.p2p.peer.NodeId; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsSupplier; public class PeerSubnetSubscriptions { private final SubnetSubscriptions attestationSubnetSubscriptions; private final SubnetSubscriptions syncCommitteeSubnetSubscriptions; + private final SubnetSubscriptions dataColumnSidecarSubnetSubscriptions; + private final NodeIdToDataColumnSidecarSubnetsCalculator + nodeIdToDataColumnSidecarSubnetsCalculator; private final int targetSubnetSubscriberCount; private PeerSubnetSubscriptions( final SubnetSubscriptions attestationSubnetSubscriptions, final SubnetSubscriptions syncCommitteeSubnetSubscriptions, + final SubnetSubscriptions dataColumnSidecarSubnetSubscriptions, + final NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator, final int targetSubnetSubscriberCount) { this.attestationSubnetSubscriptions = attestationSubnetSubscriptions; this.syncCommitteeSubnetSubscriptions = syncCommitteeSubnetSubscriptions; + this.dataColumnSidecarSubnetSubscriptions = dataColumnSidecarSubnetSubscriptions; + this.nodeIdToDataColumnSidecarSubnetsCalculator = nodeIdToDataColumnSidecarSubnetsCalculator; this.targetSubnetSubscriberCount = targetSubnetSubscriberCount; } + @FunctionalInterface + public interface NodeIdToDataColumnSidecarSubnetsCalculator { + + SszBitvector calculateSubnets(NodeId nodeId, int extraSubnetCount); + } + public static PeerSubnetSubscriptions create( - final SchemaDefinitionsSupplier currentSchemaDefinitions, + final SpecVersion currentVersion, + final NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator, final GossipNetwork network, final AttestationSubnetTopicProvider attestationTopicProvider, final SyncCommitteeSubnetTopicProvider syncCommitteeSubnetTopicProvider, final SubnetSubscriptionService syncCommitteeSubnetService, + final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider, + final SubnetSubscriptionService dataColumnSidecarSubnetService, final int targetSubnetSubscriberCount, final SettableLabelledGauge subnetPeerCountGauge) { final Map> subscribersByTopic = network.getSubscribersByTopic(); + SchemaDefinitionsSupplier currentSchemaDefinitions = currentVersion::getSchemaDefinitions; + Integer dataColumnSidecarSubnetCount = + currentVersion + .getConfig() + .toVersionElectra() + .map(NetworkingSpecConfigElectra::getDataColumnSidecarSubnetCount) + .orElse(0); + final PeerSubnetSubscriptions subscriptions = - builder(currentSchemaDefinitions) + builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount) .targetSubnetSubscriberCount(targetSubnetSubscriberCount) + .nodeIdToDataColumnSidecarSubnetsCalculator(nodeIdToDataColumnSidecarSubnetsCalculator) .attestationSubnetSubscriptions( b -> // Track all attestation subnets @@ -94,6 +122,20 @@ public static PeerSubnetSubscriptions create( subscriber -> b.addSubscriber(syncCommitteeSubnet, subscriber)); })) + .dataColumnSidecarSubnetSubscriptions( + b -> + dataColumnSidecarSubnetService + .getSubnets() + .forEach( + columnSubnet -> { + b.addRelevantSubnet(columnSubnet); + subscribersByTopic + .getOrDefault( + dataColumnSidecarSubnetTopicProvider.getTopicForSubnet( + columnSubnet), + Collections.emptySet()) + .forEach(subscriber -> b.addSubscriber(columnSubnet, subscriber)); + })) .build(); updateMetrics(currentSchemaDefinitions, subnetPeerCountGauge, subscriptions); return subscriptions; @@ -129,14 +171,17 @@ private static IntStream streamAllSyncCommitteeSubnetIds( return IntStream.range(0, currentSchemaDefinitions.getSyncnetsENRFieldSchema().getLength()); } - static Builder builder(final SchemaDefinitionsSupplier currentSchemaDefinitions) { - return new Builder(currentSchemaDefinitions); + static Builder builder( + final SchemaDefinitionsSupplier currentSchemaDefinitions, + Integer dataColumnSidecarSubnetCount) { + return new Builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount); } @VisibleForTesting static PeerSubnetSubscriptions createEmpty( - final SchemaDefinitionsSupplier currentSchemaDefinitions) { - return builder(currentSchemaDefinitions).build(); + final SchemaDefinitionsSupplier currentSchemaDefinitions, + Integer dataColumnSidecarSubnetCount) { + return builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount).build(); } public int getSubscriberCountForAttestationSubnet(final int subnetId) { @@ -147,6 +192,10 @@ public int getSubscriberCountForSyncCommitteeSubnet(final int subnetId) { return syncCommitteeSubnetSubscriptions.getSubscriberCountForSubnet(subnetId); } + public int getSubscriberCountForDataColumnSidecarSubnet(final int subnetId) { + return dataColumnSidecarSubnetSubscriptions.getSubscriberCountForSubnet(subnetId); + } + public SszBitvector getAttestationSubnetSubscriptions(final NodeId peerId) { return attestationSubnetSubscriptions.getSubnetSubscriptions(peerId); } @@ -155,6 +204,15 @@ public SszBitvector getSyncCommitteeSubscriptions(final NodeId peerId) { return syncCommitteeSubnetSubscriptions.getSubnetSubscriptions(peerId); } + public SszBitvector getDataColumnSidecarSubnetSubscriptions(final NodeId peerId) { + return dataColumnSidecarSubnetSubscriptions.getSubnetSubscriptions(peerId); + } + + public SszBitvector getDataColumnSidecarSubnetSubscriptionsByNodeId( + final NodeId peerId, final int extraSubnetCount) { + return nodeIdToDataColumnSidecarSubnetsCalculator.calculateSubnets(peerId, extraSubnetCount); + } + public boolean isSyncCommitteeSubnetRelevant(final int subnetId) { return syncCommitteeSubnetSubscriptions.isSubnetRelevant(subnetId); } @@ -163,6 +221,10 @@ public boolean isAttestationSubnetRelevant(final int subnetId) { return attestationSubnetSubscriptions.isSubnetRelevant(subnetId); } + public boolean isDataColumnSidecarSubnetRelevant(final int subnetId) { + return dataColumnSidecarSubnetSubscriptions.isSubnetRelevant(subnetId); + } + public PeerScorer createScorer() { return SubnetScorer.create(this); } @@ -177,20 +239,15 @@ public int getSubscribersRequired() { } private OptionalInt getMinSubscriberCount() { - final OptionalInt minAttestationSubscribers = - attestationSubnetSubscriptions.getMinSubscriberCount(); - final OptionalInt minSyncnetSubscribers = - syncCommitteeSubnetSubscriptions.getMinSubscriberCount(); - if (minAttestationSubscribers.isPresent() && minSyncnetSubscribers.isPresent()) { - return OptionalInt.of( - Math.min(minAttestationSubscribers.getAsInt(), minSyncnetSubscribers.getAsInt())); - } else { - if (minAttestationSubscribers.isPresent()) { - return minAttestationSubscribers; - } else { - return minSyncnetSubscribers; - } - } + return optionalMin( + List.of( + attestationSubnetSubscriptions.getMinSubscriberCount(), + syncCommitteeSubnetSubscriptions.getMinSubscriberCount(), + dataColumnSidecarSubnetSubscriptions.getMinSubscriberCount())); + } + + private static OptionalInt optionalMin(List optionalInts) { + return optionalInts.stream().flatMapToInt(OptionalInt::stream).min(); } public interface Factory { @@ -288,19 +345,27 @@ public SubnetSubscriptions build() { public static class Builder { private final SubnetSubscriptions.Builder attestationSubnetSubscriptions; private final SubnetSubscriptions.Builder syncCommitteeSubnetSubscriptions; + private final SubnetSubscriptions.Builder dataColumnSidecarSubnetSubscriptions; + private NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator; private int targetSubnetSubscriberCount = 2; - private Builder(final SchemaDefinitionsSupplier currentSchemaDefinitions) { + private Builder( + final SchemaDefinitionsSupplier currentSchemaDefinitions, + Integer dataColumnSidecarSubnetCount) { attestationSubnetSubscriptions = SubnetSubscriptions.builder(currentSchemaDefinitions.getAttnetsENRFieldSchema()); syncCommitteeSubnetSubscriptions = SubnetSubscriptions.builder(currentSchemaDefinitions.getSyncnetsENRFieldSchema()); + dataColumnSidecarSubnetSubscriptions = + SubnetSubscriptions.builder(SszBitvectorSchema.create(dataColumnSidecarSubnetCount)); } public PeerSubnetSubscriptions build() { return new PeerSubnetSubscriptions( attestationSubnetSubscriptions.build(), syncCommitteeSubnetSubscriptions.build(), + dataColumnSidecarSubnetSubscriptions.build(), + nodeIdToDataColumnSidecarSubnetsCalculator, targetSubnetSubscriberCount); } @@ -324,5 +389,17 @@ public Builder syncCommitteeSubnetSubscriptions( consumer.accept(syncCommitteeSubnetSubscriptions); return this; } + + public Builder dataColumnSidecarSubnetSubscriptions( + final Consumer consumer) { + consumer.accept(dataColumnSidecarSubnetSubscriptions); + return this; + } + + public Builder nodeIdToDataColumnSidecarSubnetsCalculator( + NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator) { + this.nodeIdToDataColumnSidecarSubnetsCalculator = nodeIdToDataColumnSidecarSubnetsCalculator; + return this; + } } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java index 895fbb5e2c3..0d1329fe584 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java @@ -16,6 +16,8 @@ import java.util.function.IntUnaryOperator; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.networking.eth2.peers.PeerScorer; +import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; +import tech.pegasys.teku.networking.p2p.libp2p.MultiaddrUtil; import tech.pegasys.teku.networking.p2p.peer.NodeId; /** Scores peers higher if they are tracking subnets that are not tracked by other peers. */ @@ -37,22 +39,40 @@ public int scoreExistingPeer(final NodeId peerId) { peerSubnetSubscriptions.getAttestationSubnetSubscriptions(peerId); final SszBitvector syncCommitteeSubscriptions = peerSubnetSubscriptions.getSyncCommitteeSubscriptions(peerId); - return score(attSubscriptions, syncCommitteeSubscriptions, this::scoreSubnetForExistingPeer); + final SszBitvector dataColumnSidecarSubscriptions = + peerSubnetSubscriptions.getDataColumnSidecarSubnetSubscriptions(peerId); + return score( + attSubscriptions, + syncCommitteeSubscriptions, + dataColumnSidecarSubscriptions, + this::scoreSubnetForExistingPeer); } @Override + public int scoreCandidatePeer(DiscoveryPeer candidate) { + return scoreCandidatePeer( + candidate.getPersistentAttestationSubnets(), + candidate.getSyncCommitteeSubnets(), + peerSubnetSubscriptions.getDataColumnSidecarSubnetSubscriptionsByNodeId( + MultiaddrUtil.getNodeId(candidate), candidate.getDasExtraCustodySubnetCount())); + } + + // @Override public int scoreCandidatePeer( final SszBitvector attSubnetSubscriptions, - final SszBitvector syncCommitteeSubnetSubscriptions) { + final SszBitvector syncCommitteeSubnetSubscriptions, + final SszBitvector dataColumnSidecarSubscriptions) { return score( attSubnetSubscriptions, syncCommitteeSubnetSubscriptions, + dataColumnSidecarSubscriptions, this::scoreSubnetForCandidatePeer); } private int score( final SszBitvector attestationSubnetSubscriptions, final SszBitvector syncCommitteeSubnetSubscriptions, + final SszBitvector dataColumnSidecarSubnetSubscriptions, final IntUnaryOperator subscriberCountToScore) { final int attestationSubnetScore = attestationSubnetSubscriptions @@ -78,7 +98,20 @@ private int score( }) .sum(); - return attestationSubnetScore + syncCommitteeSubnetScore; + final int dataColumnSidecarSubnetScore = + dataColumnSidecarSubnetSubscriptions + .streamAllSetBits() + .filter(peerSubnetSubscriptions::isDataColumnSidecarSubnetRelevant) + .map( + subnetId -> { + int subscriberCount = + peerSubnetSubscriptions.getSubscriberCountForDataColumnSidecarSubnet( + subnetId); + return subscriberCountToScore.applyAsInt(subscriberCount); + }) + .sum(); + + return attestationSubnetScore + syncCommitteeSubnetScore + dataColumnSidecarSubnetScore; } private int scoreSubnetForExistingPeer(final int subscriberCount) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/PeerScorer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/PeerScorer.java index ee7f37e903c..d074b775640 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/PeerScorer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/PeerScorer.java @@ -13,7 +13,6 @@ package tech.pegasys.teku.networking.eth2.peers; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.networking.p2p.peer.Peer; @@ -26,12 +25,5 @@ default int scoreExistingPeer(final Peer peer) { return scoreExistingPeer(peer.getId()); } - int scoreCandidatePeer( - final SszBitvector attSubnetSubscriptions, - final SszBitvector syncCommitteeSubnetSubscriptions); - - default int scoreCandidatePeer(final DiscoveryPeer candidate) { - return scoreCandidatePeer( - candidate.getPersistentAttestationSubnets(), candidate.getSyncCommitteeSubnets()); - } + int scoreCandidatePeer(final DiscoveryPeer candidate); } diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java index d63b6dc26e8..d479dad96b4 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java @@ -68,6 +68,8 @@ public class ActiveEth2P2PNetworkTest { new SubnetSubscriptionService(); private final SubnetSubscriptionService syncCommitteeSubnetService = new SubnetSubscriptionService(); + private final SubnetSubscriptionService dataColumnSidecarCommitteeSubnetService = + new SubnetSubscriptionService(); private final RecentChainData recentChainData = storageSystem.recentChainData(); private final GossipEncoding gossipEncoding = GossipEncoding.SSZ_SNAPPY; private final GossipConfigurator gossipConfigurator = GossipConfigurator.NOOP; @@ -286,6 +288,7 @@ void isCloseToInSync_shouldReturnFalseWhenEmptyCurrentEpoch() { recentChainData, attestationSubnetService, syncCommitteeSubnetService, + dataColumnSidecarCommitteeSubnetService, gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, @@ -323,6 +326,7 @@ ActiveEth2P2PNetwork createNetwork() { recentChainData, attestationSubnetService, syncCommitteeSubnetService, + dataColumnSidecarCommitteeSubnetService, gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java index 20e5778cded..475b2ea49fc 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import com.google.common.base.Supplier; import com.google.common.collect.ImmutableMap; import it.unimi.dsi.fastutil.ints.IntList; import java.util.ArrayList; @@ -25,17 +26,20 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.SubnetSubscriptionService; import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; import tech.pegasys.teku.networking.p2p.mock.MockNodeId; import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsSupplier; @@ -47,6 +51,8 @@ class PeerSubnetSubscriptionsTest { private final Spec spec = TestSpecFactory.createMinimalAltair(); private final SettableLabelledGauge subnetPeerCountGauge = mock(SettableLabelledGauge.class); + final Supplier currentSpecVersionSupplier = spec::getGenesisSpec; + final Supplier> currentSlotSupplier = Optional::empty; private final SchemaDefinitionsSupplier currentSchemaDefinitions = spec::getGenesisSchemaDefinitions; private final GossipNetwork gossipNetwork = mock(GossipNetwork.class); @@ -54,7 +60,10 @@ class PeerSubnetSubscriptionsTest { mock(AttestationSubnetTopicProvider.class); private final SyncCommitteeSubnetTopicProvider syncCommitteeTopicProvider = mock(SyncCommitteeSubnetTopicProvider.class); + private final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = + mock(DataColumnSidecarSubnetTopicProvider.class); private final SubnetSubscriptionService syncnetSubscriptions = new SubnetSubscriptionService(); + private final SubnetSubscriptionService dataColumnSubscriptions = new SubnetSubscriptionService(); @BeforeEach public void setUp() { @@ -197,11 +206,14 @@ public void isAttestationSubnetRelevant() { private PeerSubnetSubscriptions createPeerSubnetSubscriptions() { return PeerSubnetSubscriptions.create( - currentSchemaDefinitions, + currentSpecVersionSupplier.get(), + currentSlotSupplier.get(), gossipNetwork, attestationTopicProvider, syncCommitteeTopicProvider, syncnetSubscriptions, + dataColumnSidecarSubnetTopicProvider, + dataColumnSubscriptions, TARGET_SUBSCRIBER_COUNT, subnetPeerCountGauge); } diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java index 370e5dd7c99..24c78fea78a 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java @@ -18,15 +18,21 @@ import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; +import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.networking.eth2.peers.PeerScorer; +import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; import tech.pegasys.teku.networking.p2p.mock.MockNodeId; import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.spec.Spec; @@ -40,18 +46,19 @@ class SubnetScorerTest { @Test void shouldScoreCandidatePeerWithNoSubnetsAsZero() { final SubnetScorer scorer = - SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions)); + SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions, 128)); assertThat( scorer.scoreCandidatePeer( - schemaDefinitions.getAttnetsENRFieldSchema().getDefault(), - schemaDefinitions.getSyncnetsENRFieldSchema().getDefault())) + createDiscoveryPeer( + schemaDefinitions.getAttnetsENRFieldSchema().getDefault(), + schemaDefinitions.getSyncnetsENRFieldSchema().getDefault()))) .isZero(); } @Test void shouldScoreExistingPeerWithNoSubnetsAsZero() { final SubnetScorer scorer = - SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions)); + SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions, 128)); assertThat(scorer.scoreExistingPeer(new MockNodeId(1))).isZero(); } @@ -64,7 +71,7 @@ void shouldScoreExistingPeersOnSubnetsWithFewPeersMoreHighly() { final MockNodeId node5 = new MockNodeId(4); final SubnetScorer scorer = SubnetScorer.create( - PeerSubnetSubscriptions.builder(() -> schemaDefinitions) + PeerSubnetSubscriptions.builder(() -> schemaDefinitions, 0) .attestationSubnetSubscriptions( b -> b.addRelevantSubnet(1) @@ -113,7 +120,7 @@ void shouldScoreCandidatePeersOnSubnetsWithFewPeersMoreHighly() { final MockNodeId node3 = new MockNodeId(2); final SubnetScorer scorer = SubnetScorer.create( - PeerSubnetSubscriptions.builder(() -> schemaDefinitions) + PeerSubnetSubscriptions.builder(() -> schemaDefinitions, 128) .attestationSubnetSubscriptions( b -> b.addRelevantSubnet(1) @@ -176,10 +183,26 @@ private void assertCandidatePeerScores( Function.identity(), (subscriptions) -> scorer.scoreCandidatePeer( - subscriptions.getLeft(), subscriptions.getRight()))); + createDiscoveryPeer( + subscriptions.getLeft(), subscriptions.getRight())))); assertThat(actual).contains(expected); } + private DiscoveryPeer createDiscoveryPeer(SszBitvector attSubnets, SszBitvector syncSubnets) { + try { + return new DiscoveryPeer( + Bytes.fromHexString( + "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"), + new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), + Optional.empty(), + attSubnets, + syncSubnets, + Optional.empty()); + } catch (UnknownHostException e) { + throw new RuntimeException(e); + } + } + private Pair candidateWithSubnets( final List attnets, List syncnets) { return Pair.of( diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java index 8eaae60dccf..c302f614a55 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java @@ -339,6 +339,7 @@ private static DiscoveryPeer createDiscoveryPeer(final Bytes peerId, final int.. new InetSocketAddress(InetAddress.getLoopbackAddress(), peerId.trimLeadingZeros().toInt()), ENR_FORK_ID, SCHEMA_DEFINITIONS.getAttnetsENRFieldSchema().ofBits(attnets), - SCHEMA_DEFINITIONS.getSyncnetsENRFieldSchema().getDefault()); + SCHEMA_DEFINITIONS.getSyncnetsENRFieldSchema().getDefault(), + Optional.empty()); } } diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index 315f974e461..94b2aa2efdc 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -18,6 +18,7 @@ import static java.util.stream.Collectors.toList; import static org.assertj.core.api.Assertions.assertThat; +import com.google.common.base.Supplier; import java.net.BindException; import java.time.Duration; import java.util.ArrayList; @@ -44,6 +45,7 @@ import tech.pegasys.teku.infrastructure.subscribers.Subscribers; import tech.pegasys.teku.infrastructure.time.StubTimeProvider; import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.network.p2p.jvmlibp2p.PrivateKeyGenerator; import tech.pegasys.teku.networking.eth2.gossip.config.GossipConfigurator; @@ -57,6 +59,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; +import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.PeerSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.topics.Eth2GossipTopicFilter; @@ -80,6 +83,7 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationManager; import tech.pegasys.teku.networking.p2p.rpc.RpcMethod; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.config.Constants; import tech.pegasys.teku.spec.datastructures.attestation.ProcessedAttestationListener; @@ -200,12 +204,17 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { final SubnetSubscriptionService attestationSubnetService = new SubnetSubscriptionService(); final SubnetSubscriptionService syncCommitteeSubnetService = new SubnetSubscriptionService(); + final SubnetSubscriptionService dataColumnSidecarSubnetService = + new SubnetSubscriptionService(); final EarliestAvailableBlockSlot earliestAvailableBlockSlot = new EarliestAvailableBlockSlot( historicalChainData, timeProvider, earliestAvailableBlockSlotFrequency); final CombinedChainDataClient combinedChainDataClient = new CombinedChainDataClient( recentChainData, historicalChainData, spec, earliestAvailableBlockSlot); + final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = + new DataColumnSidecarSubnetTopicProvider( + combinedChainDataClient.getRecentChainData(), gossipEncoding); if (rpcEncoding == null) { rpcEncoding = @@ -258,8 +267,16 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { discoConfig.getMinPeers(), discoConfig.getMaxPeers(), discoConfig.getMinRandomlySelectedPeers()); + final Supplier currentSpecVersionSupplier = + () -> combinedChainDataClient.getRecentChainData().getCurrentSpec(); final SchemaDefinitionsSupplier currentSchemaDefinitions = - () -> config.getSpec().getGenesisSchemaDefinitions(); + () -> + combinedChainDataClient + .getRecentChainData() + .getCurrentSpec() + .getSchemaDefinitions(); + final Supplier> currentSlotSupplier = + () -> combinedChainDataClient.getRecentChainData().getCurrentSlot(); final SettableLabelledGauge subnetPeerCountGauge = SettableLabelledGauge.create( metricsSystem, @@ -294,11 +311,14 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { targetPeerRange, gossipNetwork -> PeerSubnetSubscriptions.create( - currentSchemaDefinitions, + currentSpecVersionSupplier.get(), + currentSlotSupplier.get(), gossipNetwork, attestationSubnetTopicProvider, syncCommitteeTopicProvider, syncCommitteeSubnetService, + dataColumnSidecarSubnetTopicProvider, + dataColumnSidecarSubnetService, config.getTargetSubnetSubscriberCount(), subnetPeerCountGauge), reputationManager, @@ -331,6 +351,7 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { recentChainData, attestationSubnetService, syncCommitteeSubnetService, + dataColumnSidecarSubnetService, gossipEncoding, GossipConfigurator.NOOP, processedAttestationSubscriptionProvider, diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubPeerScorer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubPeerScorer.java index 0675b3bc81c..9e16e1c9cb1 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubPeerScorer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubPeerScorer.java @@ -15,13 +15,20 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import org.apache.commons.lang3.tuple.Pair; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; +import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; import tech.pegasys.teku.networking.p2p.peer.NodeId; public class StubPeerScorer implements PeerScorer { + + record SubnetSubscriptionsKey( + SszBitvector attestationSubscriptions, + SszBitvector syncCommitteeSubnetSubscriptions, + SszBitvector dataColumnSidecarSubnetSubscriptions) {} + private final Object2IntMap peerScores = new Object2IntOpenHashMap<>(); - private final Object2IntMap> candidateScores = + private final Object2IntMap candidateScores = new Object2IntOpenHashMap<>(); public void setScore(final NodeId peerId, final int score) { @@ -32,7 +39,12 @@ public void setScore( final SszBitvector attestationSubscriptions, final SszBitvector syncCommitteeSubnetSubscriptions, final int score) { - candidateScores.put(Pair.of(attestationSubscriptions, syncCommitteeSubnetSubscriptions), score); + candidateScores.put( + new SubnetSubscriptionsKey( + attestationSubscriptions, + syncCommitteeSubnetSubscriptions, + SszBitvectorSchema.create(128).getDefault()), + score); } @Override @@ -41,10 +53,19 @@ public int scoreExistingPeer(final NodeId peerId) { } @Override + public int scoreCandidatePeer(DiscoveryPeer candidate) { + return scoreCandidatePeer( + candidate.getPersistentAttestationSubnets(), candidate.getSyncCommitteeSubnets()); + } + public int scoreCandidatePeer( final SszBitvector attestationSubscriptions, final SszBitvector syncCommitteeSubnetSubscriptions) { return candidateScores.getOrDefault( - Pair.of(attestationSubscriptions, syncCommitteeSubnetSubscriptions), 0); + new SubnetSubscriptionsKey( + attestationSubscriptions, + syncCommitteeSubnetSubscriptions, + SszBitvectorSchema.create(128).getDefault()), + 0); } } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java index 3e515a66d36..1b182847f9a 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java @@ -18,7 +18,6 @@ import java.net.InetSocketAddress; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; -import org.web3j.abi.datatypes.Int; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EnrForkId; diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java index 5845fcd6ddc..6e999a41ac0 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java @@ -216,7 +216,8 @@ public Optional getDiscoveryAddress() { nodeRecord.getUdpAddress().get(), Optional.empty(), currentSchemaDefinitionsSupplier.getAttnetsENRFieldSchema().getDefault(), - currentSchemaDefinitionsSupplier.getSyncnetsENRFieldSchema().getDefault()); + currentSchemaDefinitionsSupplier.getSyncnetsENRFieldSchema().getDefault(), + Optional.empty()); return Optional.of(MultiaddrUtil.fromDiscoveryPeerAsUdp(discoveryPeer).toString()); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index 17a573ca101..0947429c886 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -27,7 +27,6 @@ import org.ethereum.beacon.discovery.schema.EnrField; import org.ethereum.beacon.discovery.schema.NodeRecord; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java index 2342a0cfb2f..6cce9b8fa42 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java @@ -58,7 +58,7 @@ private static Multiaddr addPeerId(final Multiaddr addr, final NodeId nodeId) { return addr.withP2P(PeerId.fromBase58(nodeId.toBase58())); } - private static LibP2PNodeId getNodeId(final DiscoveryPeer peer) { + public static NodeId getNodeId(final DiscoveryPeer peer) { final PubKey pubKey = unmarshalSecp256k1PublicKey(peer.getPublicKey().toArrayUnsafe()); return new LibP2PNodeId(PeerId.fromPubKey(pubKey)); } diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java index 84157aaf726..ccf2ac2fc49 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java @@ -473,6 +473,7 @@ private static DiscoveryPeer createDiscoveryPeer(final Bytes peerId, final int.. new InetSocketAddress(InetAddress.getLoopbackAddress(), peerId.trimLeadingZeros().toInt()), ENR_FORK_ID, SCHEMA_DEFINITIONS_SUPPLIER.getAttnetsENRFieldSchema().ofBits(subnetIds), - SCHEMA_DEFINITIONS_SUPPLIER.getSyncnetsENRFieldSchema().getDefault()); + SCHEMA_DEFINITIONS_SUPPLIER.getSyncnetsENRFieldSchema().getDefault(), + Optional.empty()); } } diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java index 7737c6c32c3..854b3a7704f 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java @@ -299,7 +299,8 @@ public DiscoveryPeer createDiscoveryPeer(Optional maybeForkId) { maybeForkId, SszBitvectorSchema.create(spec.getNetworkingConfig().getAttestationSubnetCount()) .getDefault(), - syncCommitteeSubnets); + syncCommitteeSubnets, + Optional.empty()); } public static Stream provideNodeIds() { diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java index 63eca4e78ae..64db7b7ddf4 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java @@ -71,7 +71,8 @@ public void shouldConvertRealEnrToDiscoveryPeer() throws Exception { new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), Optional.empty(), ATTNETS, - SYNCNETS); + SYNCNETS, + Optional.empty()); assertThat(CONVERTER.convertToDiscoveryPeer(nodeRecord, SCHEMA_DEFINITIONS)) .contains(expectedPeer); } @@ -105,7 +106,12 @@ public void shouldUseV4PortIfV6PortSpecifiedWithNoV6Ip() { new EnrField(EnrField.IP_V6, IPV6_LOCALHOST), new EnrField(EnrField.TCP, 30303))) .contains( new DiscoveryPeer( - PUB_KEY, new InetSocketAddress("::1", 30303), ENR_FORK_ID, ATTNETS, SYNCNETS)); + PUB_KEY, + new InetSocketAddress("::1", 30303), + ENR_FORK_ID, + ATTNETS, + SYNCNETS, + Optional.empty())); } @Test @@ -135,7 +141,8 @@ public void shouldConvertIpV4Record() { new InetSocketAddress("129.24.31.22", 1234), ENR_FORK_ID, ATTNETS, - SYNCNETS)); + SYNCNETS, + Optional.empty())); } @Test @@ -146,7 +153,12 @@ public void shouldConvertIpV6Record() { assertThat(result) .contains( new DiscoveryPeer( - PUB_KEY, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, SYNCNETS)); + PUB_KEY, + new InetSocketAddress("::1", 1234), + ENR_FORK_ID, + ATTNETS, + SYNCNETS, + Optional.empty())); } @Test @@ -165,7 +177,8 @@ public void shouldConvertAttnets() { new InetSocketAddress("::1", 1234), ENR_FORK_ID, persistentSubnets, - SYNCNETS)); + SYNCNETS, + Optional.empty())); } @Test @@ -184,7 +197,8 @@ public void shouldUseEmptyAttnetsWhenFieldValueIsInvalid() { new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATT_SUBNET_SCHEMA.getDefault(), - SYNCNETS)); + SYNCNETS, + Optional.empty())); } @Test @@ -199,7 +213,12 @@ public void shouldConvertSyncnets() { assertThat(result) .contains( new DiscoveryPeer( - PUB_KEY, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, syncnets)); + PUB_KEY, + new InetSocketAddress("::1", 1234), + ENR_FORK_ID, + ATTNETS, + syncnets, + Optional.empty())); } @Test @@ -216,7 +235,12 @@ public void shouldUseEmptySyncnetsFieldValueIsInvalid() { assertThat(result) .contains( new DiscoveryPeer( - PUB_KEY, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, SYNCNETS)); + PUB_KEY, + new InetSocketAddress("::1", 1234), + ENR_FORK_ID, + ATTNETS, + SYNCNETS, + Optional.empty())); } @Test @@ -235,7 +259,8 @@ public void shouldConvertEnrForkId() { new InetSocketAddress("::1", 1234), Optional.of(enrForkId), ATTNETS, - SYNCNETS)); + SYNCNETS, + Optional.empty())); } @Test @@ -249,7 +274,12 @@ public void shouldNotHaveEnrForkIdWhenValueIsInvalid() { assertThat(result) .contains( new DiscoveryPeer( - PUB_KEY, new InetSocketAddress("::1", 1234), Optional.empty(), ATTNETS, SYNCNETS)); + PUB_KEY, + new InetSocketAddress("::1", 1234), + Optional.empty(), + ATTNETS, + SYNCNETS, + Optional.empty())); } private Optional convertNodeRecordWithFields(final EnrField... fields) { diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java index d6b901946b1..323a2959933 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java @@ -82,7 +82,8 @@ public void fromDiscoveryPeer_shouldConvertIpV4Peer() throws Exception { new InetSocketAddress(InetAddress.getByAddress(ipAddress), port), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, - SYNC_COMMITTEE_SUBNETS); + SYNC_COMMITTEE_SUBNETS, + Optional.empty()); final Multiaddr result = MultiaddrUtil.fromDiscoveryPeer(peer); assertThat(result).isEqualTo(Multiaddr.fromString("/ip4/123.34.58.22/tcp/5883/p2p/" + PEER_ID)); assertThatComponent(result, Protocol.IP4).isEqualTo(ipAddress); @@ -100,7 +101,8 @@ public void fromDiscoveryPeer_shouldConvertIpV6Peer() throws Exception { new InetSocketAddress(InetAddress.getByAddress(ipAddress), port), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, - SYNC_COMMITTEE_SUBNETS); + SYNC_COMMITTEE_SUBNETS, + Optional.empty()); final Multiaddr result = MultiaddrUtil.fromDiscoveryPeer(peer); assertThat(result) .isEqualTo(Multiaddr.fromString("/ip6/3300:4:5000:780:0:12:0:1/tcp/5883/p2p/" + PEER_ID)); @@ -118,7 +120,8 @@ public void fromDiscoveryPeer_shouldConvertRealPeer() throws Exception { new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, - SYNC_COMMITTEE_SUBNETS); + SYNC_COMMITTEE_SUBNETS, + Optional.empty()); final Multiaddr expectedMultiAddr = Multiaddr.fromString( "/ip4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmR4wQRGWgCNy5uzx7HfuV59Q6X1MVzBRmvreuHgEQcCnF"); @@ -134,7 +137,8 @@ public void fromDiscoveryPeerAsUdp_shouldConvertDiscoveryPeer() throws Exception new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, - SYNC_COMMITTEE_SUBNETS); + SYNC_COMMITTEE_SUBNETS, + Optional.empty()); final Multiaddr expectedMultiAddr = Multiaddr.fromString( "/ip4/127.0.0.1/udp/9000/p2p/16Uiu2HAmR4wQRGWgCNy5uzx7HfuV59Q6X1MVzBRmvreuHgEQcCnF"); From a5b32f30b67686930e6dbb828b58bab51b28ef21 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Fri, 26 Apr 2024 15:34:10 +0400 Subject: [PATCH 34/70] Add rpc byRoot method for DataColumnSidecar (#24) --- .../java/tech/pegasys/teku/spec/Spec.java | 25 +++ .../config/NetworkingSpecConfigElectra.java | 2 + .../spec/config/SpecConfigElectraImpl.java | 10 +- .../spec/config/builder/ElectraBuilder.java | 11 +- ...ataColumnSidecarsByRootRequestMessage.java | 44 ++++ ...umnSidecarsByRootRequestMessageSchema.java | 31 +++ .../logic/common/helpers/MiscHelpers.java | 6 + .../electra/helpers/MiscHelpersElectra.java | 20 ++ .../schemas/SchemaDefinitionsElectra.java | 10 + .../teku/spec/config/configs/mainnet.yaml | 3 +- .../teku/spec/config/configs/minimal.yaml | 3 +- .../spec/config/SpecConfigElectraTest.java | 3 +- .../tech/pegasys/teku/kzg/KZGCellWithID.java | 2 +- .../eth2/peers/DefaultEth2Peer.java | 54 +++++ .../teku/networking/eth2/peers/Eth2Peer.java | 14 ++ .../eth2/peers/Eth2PeerFactory.java | 5 + .../rpc/beaconchain/BeaconChainMethodIds.java | 8 + .../rpc/beaconchain/BeaconChainMethods.java | 63 ++++++ ...SidecarsByRootListenerValidatingProxy.java | 48 +++++ ...ataColumnSidecarsByRootMessageHandler.java | 204 ++++++++++++++++++ .../DataColumnSidecarsByRootValidator.java | 81 +++++++ ...ecarsResponseInvalidResponseException.java | 52 +++++ .../context/ForkDigestPayloadContext.java | 16 ++ .../subnets/PeerSubnetSubscriptionsTest.java | 5 +- .../networking/eth2/peers/Eth2PeerTest.java | 2 + .../eth2/Eth2P2PNetworkFactory.java | 14 +- .../eth2/peers/RespondingEth2Peer.java | 22 ++ 27 files changed, 747 insertions(+), 11 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessage.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 3bb6252cba2..ae4ed60aa92 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -957,6 +957,22 @@ public UInt64 computeSubnetForBlobSidecar(final BlobSidecar blobSidecar) { return blobSidecar.getIndex().mod(specConfigDeneb.getBlobSidecarSubnetCount()); } + public Optional getNumberOfDataColumns() { + return getSpecConfigElectra().map(SpecConfigElectra::getNumberOfColumns); + } + + public boolean isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( + final ReadOnlyStore store, final UInt64 epoch) { + if (!forkSchedule.getSpecMilestoneAtEpoch(epoch).isGreaterThanOrEqualTo(ELECTRA)) { + return false; + } + final SpecConfig config = atEpoch(epoch).getConfig(); + final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(config); + return getCurrentEpoch(store) + .minusMinZero(epoch) + .isLessThanOrEqualTo(specConfigElectra.getMinEpochsForDataColumnSidecarsRequests()); + } + public UInt64 computeSubnetForDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { final SpecConfig config = atSlot(dataColumnSidecar.getSlot()).getConfig(); final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(config); @@ -987,6 +1003,15 @@ private Optional getSpecConfigDeneb(final UInt64 slot) { return atSlot(slot).getConfig().toVersionDeneb(); } + // Electra private helpers + private Optional getSpecConfigElectra() { + final SpecMilestone highestSupportedMilestone = + getForkSchedule().getHighestSupportedMilestone(); + return Optional.ofNullable(forMilestone(highestSupportedMilestone)) + .map(SpecVersion::getConfig) + .flatMap(SpecConfig::toVersionElectra); + } + // Private helpers private SpecVersion atState(final BeaconState state) { return atSlot(state.getSlot()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java index 3990e592353..f0bcac5e86c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java @@ -26,4 +26,6 @@ public interface NetworkingSpecConfigElectra extends NetworkingSpecConfig { int getCustodyRequirement(); int getMinEpochsForDataColumnSidecarsRequests(); + + int getMaxRequestDataColumnSidecars(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java index aa1b554c9d8..96bad6b3835 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java @@ -42,6 +42,7 @@ public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements private final int custodyRequirement; private final UInt64 fieldElementsPerCell; private final int minEpochsForDataColumnSidecarsRequests; + private final int maxRequestDataColumnSidecars; public SpecConfigElectraImpl( final SpecConfigDeneb specConfig, @@ -65,7 +66,8 @@ public SpecConfigElectraImpl( final UInt64 fieldElementsPerCell, final int dataColumnSidecarSubnetCount, final int custodyRequirement, - int minEpochsForDataColumnSidecarsRequests) { + final int minEpochsForDataColumnSidecarsRequests, + final int maxRequestDataColumnSidecars) { super(specConfig); this.electraForkVersion = electraForkVersion; this.electraForkEpoch = electraForkEpoch; @@ -88,6 +90,7 @@ public SpecConfigElectraImpl( this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; this.custodyRequirement = custodyRequirement; this.minEpochsForDataColumnSidecarsRequests = minEpochsForDataColumnSidecarsRequests; + this.maxRequestDataColumnSidecars = maxRequestDataColumnSidecars; } @Override @@ -195,6 +198,11 @@ public int getMinEpochsForDataColumnSidecarsRequests() { return minEpochsForDataColumnSidecarsRequests; } + @Override + public int getMaxRequestDataColumnSidecars() { + return maxRequestDataColumnSidecars; + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java index 2f6fcf28deb..7d11426c159 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java @@ -52,6 +52,7 @@ public class ElectraBuilder implements ForkConfigBuilder getValidationMap() { constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); constants.put("custodyRequirement", custodyRequirement); constants.put("minEpochsForDataColumnSidecarsRequests", minEpochsForDataColumnSidecarsRequests); + constants.put("maxRequestDataColumnSidecars", maxRequestDataColumnSidecars); return constants; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessage.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessage.java new file mode 100644 index 00000000000..fff1a4df8da --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessage.java @@ -0,0 +1,44 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc; + +import java.util.List; +import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.impl.SszListImpl; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; + +public class DataColumnSidecarsByRootRequestMessage extends SszListImpl + implements SszList, RpcRequest { + + public DataColumnSidecarsByRootRequestMessage( + final DataColumnSidecarsByRootRequestMessageSchema schema, + final List dataColumnIdentifiers) { + super(schema, schema.createTreeFromElements(dataColumnIdentifiers)); + } + + DataColumnSidecarsByRootRequestMessage( + final DataColumnSidecarsByRootRequestMessageSchema schema, final TreeNode node) { + super(schema, node); + } + + @Override + public int getMaximumResponseChunks() { + return size(); + } + + @Override + public String toString() { + return "DataColumnSidecarsByRootRequestMessage{" + super.toString() + "}"; + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java new file mode 100644 index 00000000000..3b8dce49f51 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc; + +import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfigElectra; + +public class DataColumnSidecarsByRootRequestMessageSchema + extends AbstractSszListSchema { + + public DataColumnSidecarsByRootRequestMessageSchema(final SpecConfigElectra specConfigElectra) { + super(DataColumnIdentifier.SSZ_SCHEMA, specConfigElectra.getMaxRequestDataColumnSidecars()); + } + + @Override + public DataColumnSidecarsByRootRequestMessage createFromBackingNode(final TreeNode node) { + return new DataColumnSidecarsByRootRequestMessage(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 57ed6dc36b3..89a4602b6be 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -39,6 +39,7 @@ import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.constants.NetworkConstants; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.state.ForkData; import tech.pegasys.teku.spec.datastructures.state.SigningData; @@ -349,6 +350,11 @@ public boolean verifyBlobKzgProof(final KZG kzg, final BlobSidecar blobSidecar) return false; } + public boolean verifyDataColumnSidecarKzgProof( + final KZG kzg, final DataColumnSidecar dataColumnSidecar) { + return false; + } + public boolean verifyBlobKzgProofBatch(final KZG kzg, final List blobSidecars) { return false; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java index d4a27a1b764..ecb01f021e6 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java @@ -22,7 +22,11 @@ import java.util.stream.Stream; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellWithID; import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; @@ -85,6 +89,22 @@ public List computeDataColumnSidecarBackboneSubnets( .toList(); } + @Override + public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataColumnSidecar) { + return IntStream.range(0, dataColumnSidecar.getSszKZGProofs().size()) + .mapToObj( + index -> + kzg.verifyCellProof( + dataColumnSidecar.getSszKZGCommitments().get(index).getKZGCommitment(), + KZGCellWithID.fromCellAndColumn( + new KZGCell(dataColumnSidecar.getDataColumn().get(index).getBytes()), + dataColumnSidecar.getIndex().intValue()), + dataColumnSidecar.getSszKZGProofs().get(index).getKZGProof())) + .filter(verificationResult -> !verificationResult) + .findFirst() + .orElse(true); + } + @Override public Optional toVersionElectra() { return Optional.of(this); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java index 1ceba9dc48e..abeaf602c08 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java @@ -48,6 +48,7 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderSchemaElectra; import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadSchemaElectra; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; @@ -92,6 +93,8 @@ public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { private final CellSchema cellSchema; private final DataColumnSchema dataColumnSchema; private final DataColumnSidecarSchema dataColumnSidecarSchema; + private final DataColumnSidecarsByRootRequestMessageSchema + dataColumnSidecarsByRootRequestMessageSchema; public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { super(specConfig); @@ -153,6 +156,8 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { this.dataColumnSidecarSchema = DataColumnSidecarSchema.create( SignedBeaconBlockHeader.SSZ_SCHEMA, dataColumnSchema, specConfig); + this.dataColumnSidecarsByRootRequestMessageSchema = + new DataColumnSidecarsByRootRequestMessageSchema(specConfig); } public static SchemaDefinitionsElectra required(final SchemaDefinitions schemaDefinitions) { @@ -307,4 +312,9 @@ public DataColumnSchema getDataColumnSchema() { public CellSchema getCellSchema() { return cellSchema; } + + public DataColumnSidecarsByRootRequestMessageSchema + getDataColumnSidecarsByRootRequestMessageSchema() { + return dataColumnSidecarsByRootRequestMessageSchema; + } } diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 0cb5b6b5b82..afa67a490c5 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -148,4 +148,5 @@ MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,00 MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) # [New in Electra:EIP7594] -DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 \ No newline at end of file +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index 2d7b0722aaa..f276d2637a1 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -148,4 +148,5 @@ MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) # [New in Electra:EIP7594] -DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 \ No newline at end of file +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java index 6d8d3ae2fe1..e2f356dad41 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java @@ -100,6 +100,7 @@ private SpecConfigElectra createRandomElectraConfig( dataStructureUtil.randomUInt64(64), dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(64), - dataStructureUtil.randomPositiveInt(4096)) {}; + dataStructureUtil.randomPositiveInt(4096), + dataStructureUtil.randomPositiveInt(16384)) {}; } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java index 96bb30a2272..3f1ccc5c841 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java @@ -15,7 +15,7 @@ public record KZGCellWithID(KZGCell cell, KZGCellID id) { - static KZGCellWithID fromCellAndColumn(KZGCell cell, int index) { + public static KZGCellWithID fromCellAndColumn(KZGCell cell, int index) { return new KZGCellWithID(cell, KZGCellID.fromCellColumnIndex(index)); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index 1bae45cc1e3..e710ec6bd6e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -38,6 +38,7 @@ import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootValidator; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlocksByRangeListenerWrapper; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRootListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.StatusMessageFactory; import tech.pegasys.teku.networking.eth2.rpc.core.Eth2RpcResponseHandler; @@ -52,6 +53,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage; @@ -59,6 +61,9 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.GoodbyeMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.PingMessage; @@ -67,6 +72,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private static final Logger LOG = LogManager.getLogger(); @@ -85,11 +91,14 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private final AtomicInteger unansweredPings = new AtomicInteger(); private final RateTracker blockRequestTracker; private final RateTracker blobSidecarsRequestTracker; + private final RateTracker dataColumnSidecarsRequestTracker; private final RateTracker requestTracker; private final KZG kzg; private final Supplier firstSlotSupportingBlobSidecarsByRange; private final Supplier blobSidecarsByRootRequestMessageSchema; + private final Supplier + dataColumnSidecarsByRootRequestMessageSchema; private final Supplier maxBlobsPerBlock; DefaultEth2Peer( @@ -101,6 +110,7 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { final PeerChainValidator peerChainValidator, final RateTracker blockRequestTracker, final RateTracker blobSidecarsRequestTracker, + final RateTracker dataColumnSidecarsRequestTracker, final RateTracker requestTracker, final KZG kzg) { super(peer); @@ -111,6 +121,7 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { this.peerChainValidator = peerChainValidator; this.blockRequestTracker = blockRequestTracker; this.blobSidecarsRequestTracker = blobSidecarsRequestTracker; + this.dataColumnSidecarsRequestTracker = dataColumnSidecarsRequestTracker; this.requestTracker = requestTracker; this.kzg = kzg; this.firstSlotSupportingBlobSidecarsByRange = @@ -125,6 +136,13 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { SchemaDefinitionsDeneb.required( spec.forMilestone(SpecMilestone.DENEB).getSchemaDefinitions()) .getBlobSidecarsByRootRequestMessageSchema()); + this.dataColumnSidecarsByRootRequestMessageSchema = + Suppliers.memoize( + () -> + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getDataColumnSidecarsByRootRequestMessageSchema()); + this.maxBlobsPerBlock = Suppliers.memoize(() -> getSpecConfigDeneb().getMaxBlobsPerBlock()); } @@ -253,6 +271,23 @@ public SafeFuture requestBlobSidecarsByRoot( .orElse(failWithUnsupportedMethodException("BlobSidecarsByRoot")); } + @Override + public SafeFuture requestDataColumnSidecarsByRoot( + final List dataColumnIdentifiers, + final RpcResponseListener listener) { + return rpcMethods + .dataColumnSidecarsByRoot() + .map( + method -> + requestStream( + method, + new DataColumnSidecarsByRootRequestMessage( + dataColumnSidecarsByRootRequestMessageSchema.get(), dataColumnIdentifiers), + new DataColumnSidecarsByRootListenerValidatingProxy( + this, spec, listener, kzg, dataColumnIdentifiers))) + .orElse(failWithUnsupportedMethodException("DataColumnSidecarsByRoot")); + } + @Override public SafeFuture> requestBlockBySlot(final UInt64 slot) { final Eth2RpcMethod blocksByRange = @@ -385,6 +420,25 @@ public void adjustBlobSidecarsRequest( blobSidecarsRequestTracker, blobSidecarsRequest, returnedBlobSidecarsCount); } + @Override + public Optional approveDataColumnSidecarsRequest( + final ResponseCallback callback, long dataColumnSidecarsCount) { + return approveObjectsRequest( + "data column sidecars", + dataColumnSidecarsRequestTracker, + dataColumnSidecarsCount, + callback); + } + + @Override + public void adjustDataColumnSidecarsRequest( + final RequestApproval dataColumnSidecarsRequest, final long returnedDataColumnSidecarsCount) { + adjustObjectsRequest( + dataColumnSidecarsRequestTracker, + dataColumnSidecarsRequest, + returnedDataColumnSidecarsCount); + } + @Override public boolean approveRequest() { if (requestTracker.approveObjectsRequest(1L).isEmpty()) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java index 5480b26df89..867dc81cee3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java @@ -31,8 +31,10 @@ import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.RpcRequest; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; @@ -47,6 +49,7 @@ static Eth2Peer create( final PeerChainValidator peerChainValidator, final RateTracker blockRequestTracker, final RateTracker blobSidecarsRequestTracker, + final RateTracker dataColumnSidecarsRequestTracker, final RateTracker requestTracker, final KZG kzg) { return new DefaultEth2Peer( @@ -58,6 +61,7 @@ static Eth2Peer create( peerChainValidator, blockRequestTracker, blobSidecarsRequestTracker, + dataColumnSidecarsRequestTracker, requestTracker, kzg); } @@ -93,6 +97,10 @@ SafeFuture requestBlocksByRoot( SafeFuture requestBlobSidecarsByRoot( List blobIdentifiers, RpcResponseListener listener); + SafeFuture requestDataColumnSidecarsByRoot( + List dataColumnIdentifiers, + RpcResponseListener listener); + SafeFuture> requestBlockBySlot(UInt64 slot); SafeFuture> requestBlockByRoot(Bytes32 blockRoot); @@ -115,6 +123,12 @@ Optional approveBlobSidecarsRequest( void adjustBlobSidecarsRequest( RequestApproval blobSidecarsRequest, long returnedBlobSidecarsCount); + Optional approveDataColumnSidecarsRequest( + ResponseCallback callback, long dataColumnSidecarsCount); + + void adjustDataColumnSidecarsRequest( + RequestApproval dataColumnSidecarsRequest, long returnedDataColumnSidecarsCount); + boolean approveRequest(); SafeFuture sendPing(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java index dd996a4c7de..e28e2ed7181 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java @@ -16,6 +16,7 @@ import java.util.Optional; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.BeaconChainMethods; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; @@ -73,6 +74,10 @@ public Eth2Peer create(final Peer peer, final BeaconChainMethods rpcMethods) { RateTracker.create(peerRateLimit, TIME_OUT, timeProvider), RateTracker.create( peerRateLimit * spec.getMaxBlobsPerBlock().orElse(1), TIME_OUT, timeProvider), + RateTracker.create( + peerRateLimit * spec.getNumberOfDataColumns().orElse(UInt64.ONE).intValue(), + TIME_OUT, + timeProvider), RateTracker.create(peerRequestLimit, TIME_OUT, timeProvider), kzg); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java index ec2da65a97e..192d854e593 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java @@ -26,6 +26,9 @@ public class BeaconChainMethodIds { static final String BLOB_SIDECARS_BY_ROOT = "/eth2/beacon_chain/req/blob_sidecars_by_root"; static final String BLOB_SIDECARS_BY_RANGE = "/eth2/beacon_chain/req/blob_sidecars_by_range"; + static final String DATA_COLUMN_SIDECARS_BY_ROOT = + "/eth2/beacon_chain/req/data_column_sidecars_by_root"; + static final String GET_METADATA = "/eth2/beacon_chain/req/metadata"; static final String PING = "/eth2/beacon_chain/req/ping"; @@ -52,6 +55,11 @@ public static String getBlobSidecarsByRangeMethodId( return getMethodId(BLOB_SIDECARS_BY_RANGE, version, encoding); } + public static String getDataColumnSidecarsByRootMethodId( + final int version, final RpcEncoding encoding) { + return getMethodId(DATA_COLUMN_SIDECARS_BY_ROOT, version, encoding); + } + public static String getStatusMethodId(final int version, final RpcEncoding encoding) { return getMethodId(STATUS, version, encoding); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java index 631730f8ef9..116c3f1ec33 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BeaconBlocksByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.GoodbyeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; @@ -44,6 +45,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage.BeaconBlocksByRangeRequestMessageSchema; @@ -52,6 +54,8 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage.EmptyMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.GoodbyeMessage; @@ -59,6 +63,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.StatusMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.client.RecentChainData; @@ -74,6 +79,8 @@ public class BeaconChainMethods { blobSidecarsByRoot; private final Optional> blobSidecarsByRange; + private final Optional> + dataColumnSidecarsByRoot; private final Eth2RpcMethod getMetadata; private final Eth2RpcMethod ping; @@ -88,6 +95,8 @@ private BeaconChainMethods( blobSidecarsByRoot, final Optional> blobSidecarsByRange, + final Optional> + dataColumnSidecarsByRoot, final Eth2RpcMethod getMetadata, final Eth2RpcMethod ping) { this.status = status; @@ -96,6 +105,7 @@ private BeaconChainMethods( this.beaconBlocksByRange = beaconBlocksByRange; this.blobSidecarsByRoot = blobSidecarsByRoot; this.blobSidecarsByRange = blobSidecarsByRange; + this.dataColumnSidecarsByRoot = dataColumnSidecarsByRoot; this.getMetadata = getMetadata; this.ping = ping; this.allMethods = @@ -103,6 +113,7 @@ private BeaconChainMethods( List.of(status, goodBye, beaconBlocksByRoot, beaconBlocksByRange, getMetadata, ping)); blobSidecarsByRoot.ifPresent(allMethods::add); blobSidecarsByRange.ifPresent(allMethods::add); + dataColumnSidecarsByRoot.ifPresent(allMethods::add); } public static BeaconChainMethods create( @@ -144,6 +155,14 @@ public static BeaconChainMethods create( peerLookup, rpcEncoding, recentChainData), + createDataColumnSidecarsByRoot( + spec, + metricsSystem, + asyncRunner, + combinedChainDataClient, + peerLookup, + rpcEncoding, + recentChainData), createMetadata(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding), createPing(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding)); } @@ -343,6 +362,45 @@ private static Eth2RpcMethod createGoodBye( spec.getNetworkingConfig())); } + private static Optional> + createDataColumnSidecarsByRoot( + final Spec spec, + final MetricsSystem metricsSystem, + final AsyncRunner asyncRunner, + final CombinedChainDataClient combinedChainDataClient, + final PeerLookup peerLookup, + final RpcEncoding rpcEncoding, + final RecentChainData recentChainData) { + if (!spec.isMilestoneSupported(SpecMilestone.ELECTRA)) { + return Optional.empty(); + } + + final RpcContextCodec forkDigestContextCodec = + RpcContextCodec.forkDigest( + spec, recentChainData, ForkDigestPayloadContext.DATA_COLUMN_SIDECAR); + + final DataColumnSidecarsByRootMessageHandler dataColumnSidecarsByRootMessageHandler = + new DataColumnSidecarsByRootMessageHandler(spec, metricsSystem, combinedChainDataClient); + final DataColumnSidecarsByRootRequestMessageSchema + dataColumnSidecarsByRootRequestMessageSchema = + SchemaDefinitionsElectra.required( + spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + .getDataColumnSidecarsByRootRequestMessageSchema(); + + return Optional.of( + new SingleProtocolEth2RpcMethod<>( + asyncRunner, + BeaconChainMethodIds.DATA_COLUMN_SIDECARS_BY_ROOT, + 1, + rpcEncoding, + dataColumnSidecarsByRootRequestMessageSchema, + true, + forkDigestContextCodec, + dataColumnSidecarsByRootMessageHandler, + peerLookup, + spec.getNetworkingConfig())); + } + private static Eth2RpcMethod createMetadata( final Spec spec, final AsyncRunner asyncRunner, @@ -455,6 +513,11 @@ public Eth2RpcMethod beaco return blobSidecarsByRoot; } + public Optional> + dataColumnSidecarsByRoot() { + return dataColumnSidecarsByRoot; + } + public Optional> blobSidecarsByRange() { return blobSidecarsByRange; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java new file mode 100644 index 00000000000..e135788742d --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java @@ -0,0 +1,48 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import java.util.List; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.p2p.peer.Peer; +import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public class DataColumnSidecarsByRootListenerValidatingProxy + extends DataColumnSidecarsByRootValidator implements RpcResponseListener { + + private final RpcResponseListener listener; + + public DataColumnSidecarsByRootListenerValidatingProxy( + final Peer peer, + final Spec spec, + final RpcResponseListener listener, + final KZG kzg, + final List expectedDataColumnIdentifiers) { + super(peer, spec, kzg, expectedDataColumnIdentifiers); + this.listener = listener; + } + + @Override + public SafeFuture onResponse(final DataColumnSidecar dataColumnSidecar) { + return SafeFuture.of( + () -> { + validate(dataColumnSidecar); + return listener.onResponse(dataColumnSidecar); + }); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java new file mode 100644 index 00000000000..d092f81fec6 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -0,0 +1,204 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.core.RpcResponseStatus.INVALID_REQUEST_CODE; + +import com.google.common.base.Throwables; +import java.nio.channels.ClosedChannelException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.peers.Eth2Peer; +import tech.pegasys.teku.networking.eth2.peers.RequestApproval; +import tech.pegasys.teku.networking.eth2.rpc.core.PeerRequiredLocalMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.core.ResponseCallback; +import tech.pegasys.teku.networking.eth2.rpc.core.RpcException; +import tech.pegasys.teku.networking.p2p.rpc.StreamClosedException; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; +import tech.pegasys.teku.storage.client.CombinedChainDataClient; + +/** + * DataColumnSidecarsByRoot + * v1 + */ +public class DataColumnSidecarsByRootMessageHandler + extends PeerRequiredLocalMessageHandler< + DataColumnSidecarsByRootRequestMessage, DataColumnSidecar> { + + private static final Logger LOG = LogManager.getLogger(); + + private final Spec spec; + private final CombinedChainDataClient combinedChainDataClient; + + private final LabelledMetric requestCounter; + private final Counter totalDataColumnSidecarsRequestedCounter; + + public DataColumnSidecarsByRootMessageHandler( + final Spec spec, + final MetricsSystem metricsSystem, + final CombinedChainDataClient combinedChainDataClient) { + this.spec = spec; + this.combinedChainDataClient = combinedChainDataClient; + requestCounter = + metricsSystem.createLabelledCounter( + TekuMetricCategory.NETWORK, + "rpc_data_column_sidecars_by_root_requests_total", + "Total number of data column sidecars by root requests received", + "status"); + totalDataColumnSidecarsRequestedCounter = + metricsSystem.createCounter( + TekuMetricCategory.NETWORK, + "rpc_data_column_sidecars_by_root_requested_blob_sidecars_total", + "Total number of data column sidecars requested in accepted data column sidecars by root requests from peers"); + } + + @Override + public void onIncomingMessage( + final String protocolId, + final Eth2Peer peer, + final DataColumnSidecarsByRootRequestMessage message, + final ResponseCallback callback) { + + LOG.trace( + "Peer {} requested {} data column sidecars with identifiers: {}", + peer.getId(), + message.size(), + message); + + final Optional dataColumnSidecarsRequestApproval = + peer.approveDataColumnSidecarsRequest(callback, message.size()); + + if (!peer.approveRequest() || dataColumnSidecarsRequestApproval.isEmpty()) { + requestCounter.labels("rate_limited").inc(); + return; + } + + requestCounter.labels("ok").inc(); + totalDataColumnSidecarsRequestedCounter.inc(message.size()); + + SafeFuture future = SafeFuture.COMPLETE; + final AtomicInteger sentDataColumnSidecars = new AtomicInteger(0); + final UInt64 finalizedEpoch = getFinalizedEpoch(); + + for (final DataColumnIdentifier identifier : message) { + future = + future + .thenCompose(__ -> retrieveDataColumnSidecar(identifier)) + .thenCompose( + maybeSidecar -> + validateMinimumRequestEpoch(identifier, maybeSidecar, finalizedEpoch) + .thenApply(__ -> maybeSidecar)) + .thenComposeChecked( + maybeSidecar -> + maybeSidecar + .map( + dataColumnSidecar -> + callback + .respond(dataColumnSidecar) + .thenRun(sentDataColumnSidecars::incrementAndGet)) + .orElse(SafeFuture.COMPLETE)); + } + + future.finish( + () -> { + if (sentDataColumnSidecars.get() != message.size()) { + peer.adjustDataColumnSidecarsRequest( + dataColumnSidecarsRequestApproval.get(), sentDataColumnSidecars.get()); + } + callback.completeSuccessfully(); + }, + err -> { + peer.adjustDataColumnSidecarsRequest(dataColumnSidecarsRequestApproval.get(), 0); + handleError(callback, err); + }); + } + + private UInt64 getFinalizedEpoch() { + return combinedChainDataClient + .getFinalizedBlock() + .map(SignedBeaconBlock::getSlot) + .map(spec::computeEpochAtSlot) + .orElse(UInt64.ZERO); + } + + /** + * Validations: + * + *

    + *
  • The block root references a block greater than or equal to the minimum_request_epoch + *
+ */ + private SafeFuture validateMinimumRequestEpoch( + final DataColumnIdentifier identifier, + final Optional maybeSidecar, + final UInt64 finalizedEpoch) { + return maybeSidecar + .map(sidecar -> SafeFuture.completedFuture(Optional.of(sidecar.getSlot()))) + .orElse( + combinedChainDataClient + .getBlockByBlockRoot(identifier.getBlockRoot()) + .thenApply(maybeBlock -> maybeBlock.map(SignedBeaconBlock::getSlot))) + .thenAcceptChecked( + maybeSlot -> { + if (maybeSlot.isEmpty()) { + return; + } + final UInt64 requestedEpoch = spec.computeEpochAtSlot(maybeSlot.get()); + if (!spec.isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( + combinedChainDataClient.getStore(), requestedEpoch) + || requestedEpoch.isLessThan(finalizedEpoch)) { + throw new RpcException( + INVALID_REQUEST_CODE, + String.format( + "Block root (%s) references a block earlier than the minimum_request_epoch", + identifier.getBlockRoot())); + } + }); + } + + private SafeFuture> retrieveDataColumnSidecar( + final DataColumnIdentifier identifier) { + return combinedChainDataClient.getSidecar(identifier); + } + + private void handleError( + final ResponseCallback callback, final Throwable error) { + final Throwable rootCause = Throwables.getRootCause(error); + if (rootCause instanceof RpcException) { + LOG.trace("Rejecting data column sidecars by root request", error); + callback.completeWithErrorResponse((RpcException) rootCause); + } else { + if (rootCause instanceof StreamClosedException + || rootCause instanceof ClosedChannelException) { + LOG.trace("Stream closed while sending requested data column sidecars", error); + } else { + LOG.error("Failed to process data column sidecars by root request", error); + } + callback.completeWithUnexpectedError(error); + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java new file mode 100644 index 00000000000..d16e69b8ffc --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java @@ -0,0 +1,81 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsResponseInvalidResponseException.InvalidResponseType; + +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.p2p.peer.Peer; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public class DataColumnSidecarsByRootValidator { + + private static final Logger LOG = LogManager.getLogger(); + + protected final Peer peer; + protected final Spec spec; + protected final KZG kzg; + + private final Set expectedDataColumnIdentifiers; + + public DataColumnSidecarsByRootValidator( + final Peer peer, + final Spec spec, + final KZG kzg, + final List expectedDataColumnIdentifiers) { + this.peer = peer; + this.spec = spec; + this.kzg = kzg; + this.expectedDataColumnIdentifiers = ConcurrentHashMap.newKeySet(); + this.expectedDataColumnIdentifiers.addAll(expectedDataColumnIdentifiers); + } + + public void validate(final DataColumnSidecar dataColumnSidecar) { + final DataColumnIdentifier dataColumnIdentifier = + new DataColumnIdentifier(dataColumnSidecar.getBlockRoot(), dataColumnSidecar.getIndex()); + if (!expectedDataColumnIdentifiers.remove(dataColumnIdentifier)) { + throw new DataColumnSidecarsResponseInvalidResponseException( + peer, InvalidResponseType.DATA_COLUMN_SIDECAR_UNEXPECTED_IDENTIFIER); + } + + verifyKzg(dataColumnSidecar); + } + + private void verifyKzg(final DataColumnSidecar dataColumnSidecar) { + if (!verifyDataColumnSidecarKzgProof(dataColumnSidecar)) { + throw new DataColumnSidecarsResponseInvalidResponseException( + peer, InvalidResponseType.DATA_COLUMN_SIDECAR_KZG_VERIFICATION_FAILED); + } + } + + private boolean verifyDataColumnSidecarKzgProof(final DataColumnSidecar dataColumnSidecar) { + try { + return spec.atSlot(dataColumnSidecar.getSlot()) + .miscHelpers() + .verifyDataColumnSidecarKzgProof(kzg, dataColumnSidecar); + } catch (final Exception ex) { + LOG.debug( + "KZG verification failed for DataColumnSidecar {}", dataColumnSidecar.toLogString()); + throw new DataColumnSidecarsResponseInvalidResponseException( + peer, InvalidResponseType.DATA_COLUMN_SIDECAR_KZG_VERIFICATION_FAILED, ex); + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java new file mode 100644 index 00000000000..863fa0803c5 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java @@ -0,0 +1,52 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import tech.pegasys.teku.networking.eth2.rpc.core.InvalidResponseException; +import tech.pegasys.teku.networking.p2p.peer.Peer; + +public class DataColumnSidecarsResponseInvalidResponseException extends InvalidResponseException { + + public DataColumnSidecarsResponseInvalidResponseException( + final Peer peer, final InvalidResponseType invalidResponseType) { + super( + String.format( + "Received invalid response from peer %s: %s", peer, invalidResponseType.describe())); + } + + public DataColumnSidecarsResponseInvalidResponseException( + final Peer peer, final InvalidResponseType invalidResponseType, final Exception cause) { + super( + String.format( + "Received invalid response from peer %s: %s", peer, invalidResponseType.describe()), + cause); + } + + public enum InvalidResponseType { + DATA_COLUMN_SIDECAR_KZG_VERIFICATION_FAILED( + "KZG verification for DataColumnSidecar has failed"), + DATA_COLUMN_SIDECAR_UNEXPECTED_IDENTIFIER( + "DataColumnSidecar is not within requested identifiers"); + + private final String description; + + InvalidResponseType(String description) { + this.description = description; + } + + public String describe() { + return description; + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java index f755cb0314e..a3f6e31f5a3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java @@ -17,8 +17,10 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; public interface ForkDigestPayloadContext { @@ -50,6 +52,20 @@ public SszSchema getSchemaFromSchemaDefinitions( } }; + ForkDigestPayloadContext DATA_COLUMN_SIDECAR = + new ForkDigestPayloadContext<>() { + @Override + public UInt64 getSlotFromPayload(final DataColumnSidecar responsePayload) { + return responsePayload.getSlot(); + } + + @Override + public SszSchema getSchemaFromSchemaDefinitions( + final SchemaDefinitions schemaDefinitions) { + return SchemaDefinitionsElectra.required(schemaDefinitions).getDataColumnSidecarSchema(); + } + }; + UInt64 getSlotFromPayload(final TPayload responsePayload); SszSchema getSchemaFromSchemaDefinitions(final SchemaDefinitions schemaDefinitions); diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java index 475b2ea49fc..a7d2a13c6e5 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java @@ -62,6 +62,9 @@ class PeerSubnetSubscriptionsTest { mock(SyncCommitteeSubnetTopicProvider.class); private final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = mock(DataColumnSidecarSubnetTopicProvider.class); + private final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator + nodeIdToDataColumnSidecarSubnetsCalculator = + mock(PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator.class); private final SubnetSubscriptionService syncnetSubscriptions = new SubnetSubscriptionService(); private final SubnetSubscriptionService dataColumnSubscriptions = new SubnetSubscriptionService(); @@ -207,7 +210,7 @@ public void isAttestationSubnetRelevant() { private PeerSubnetSubscriptions createPeerSubnetSubscriptions() { return PeerSubnetSubscriptions.create( currentSpecVersionSupplier.get(), - currentSlotSupplier.get(), + nodeIdToDataColumnSidecarSubnetsCalculator, gossipNetwork, attestationTopicProvider, syncCommitteeTopicProvider, diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java index c36b64fdcd2..f16c0f69379 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java @@ -62,6 +62,7 @@ class Eth2PeerTest { private final PeerChainValidator peerChainValidator = mock(PeerChainValidator.class); private final RateTracker blockRateTracker = mock(RateTracker.class); private final RateTracker blobSidecarsRateTracker = mock(RateTracker.class); + private final RateTracker dataColumnSidecarsRateTracker = mock(RateTracker.class); private final RateTracker rateTracker = mock(RateTracker.class); private final KZG kzg = mock(KZG.class); @@ -77,6 +78,7 @@ class Eth2PeerTest { peerChainValidator, blockRateTracker, blobSidecarsRateTracker, + dataColumnSidecarsRateTracker, rateTracker, kzg); diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index 94b2aa2efdc..233875fd9b7 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -42,10 +42,11 @@ import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.infrastructure.subscribers.Subscribers; import tech.pegasys.teku.infrastructure.time.StubTimeProvider; import tech.pegasys.teku.infrastructure.time.TimeProvider; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.network.p2p.jvmlibp2p.PrivateKeyGenerator; import tech.pegasys.teku.networking.eth2.gossip.config.GossipConfigurator; @@ -215,6 +216,13 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = new DataColumnSidecarSubnetTopicProvider( combinedChainDataClient.getRecentChainData(), gossipEncoding); + final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator + nodeIdToDataColumnSidecarSubnetsCalculator = + (nodeId, extraSubnetCount) -> { + SszBitvectorSchema bitvectorSchema = + SszBitvectorSchema.create(config.getTargetSubnetSubscriberCount()); + return bitvectorSchema.getDefault(); + }; if (rpcEncoding == null) { rpcEncoding = @@ -275,8 +283,6 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { .getRecentChainData() .getCurrentSpec() .getSchemaDefinitions(); - final Supplier> currentSlotSupplier = - () -> combinedChainDataClient.getRecentChainData().getCurrentSlot(); final SettableLabelledGauge subnetPeerCountGauge = SettableLabelledGauge.create( metricsSystem, @@ -312,7 +318,7 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { gossipNetwork -> PeerSubnetSubscriptions.create( currentSpecVersionSupplier.get(), - currentSlotSupplier.get(), + nodeIdToDataColumnSidecarSubnetsCalculator, gossipNetwork, attestationSubnetTopicProvider, syncCommitteeTopicProvider, diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java index 8eebb5c3df2..a47b6983fa7 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java @@ -49,10 +49,12 @@ import tech.pegasys.teku.networking.p2p.rpc.RpcStreamController; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.RpcRequest; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; @@ -257,6 +259,14 @@ public SafeFuture requestBlobSidecarsByRoot( return createPendingBlobSidecarRequest(handler); } + @Override + public SafeFuture requestDataColumnSidecarsByRoot( + final List dataColumnIdentifiers, + final RpcResponseListener listener) { + // TODO + return SafeFuture.COMPLETE; + } + @Override public SafeFuture> requestBlockBySlot(final UInt64 slot) { final PendingRequestHandler, SignedBeaconBlock> handler = @@ -340,6 +350,18 @@ public Optional approveBlobSidecarsRequest( public void adjustBlobSidecarsRequest( final RequestApproval blobSidecarRequests, final long returnedBlobSidecarsCount) {} + @Override + public Optional approveDataColumnSidecarsRequest( + final ResponseCallback callback, final long dataColumnSidecarsCount) { + return Optional.of( + new RequestApproval.RequestApprovalBuilder().timeSeconds(ZERO).objectsCount(0).build()); + } + + @Override + public void adjustDataColumnSidecarsRequest( + final RequestApproval dataColumnSidecarRequests, + final long returnedDataColumnSidecarsCount) {} + @Override public boolean approveRequest() { return true; From 68861737cda5b466f1b248f99244e6bca867a61a Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 26 Apr 2024 16:16:49 +0400 Subject: [PATCH 35/70] Extract NodeIdToDataColumnSidecarSubnetsCalculator (#25) --- .../eth2/Eth2P2PNetworkBuilder.java | 54 ++----------- ...dToDataColumnSidecarSubnetsCalculator.java | 77 +++++++++++++++++++ .../subnets/PeerSubnetSubscriptions.java | 28 +++---- .../subnets/PeerSubnetSubscriptionsTest.java | 5 +- .../eth2/gossip/subnets/SubnetScorerTest.java | 20 ++++- .../eth2/Eth2P2PNetworkFactory.java | 16 ++-- 6 files changed, 120 insertions(+), 80 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 549d6574ac8..0590be01388 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -17,7 +17,6 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; -import com.google.common.base.Supplier; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; @@ -25,16 +24,12 @@ import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.infrastructure.time.TimeProvider; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; import tech.pegasys.teku.networking.eth2.gossip.forks.GossipForkManager; @@ -47,6 +42,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; +import tech.pegasys.teku.networking.eth2.gossip.subnets.NodeIdToDataColumnSidecarSubnetsCalculator; import tech.pegasys.teku.networking.eth2.gossip.subnets.PeerSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.topics.Eth2GossipTopicFilter; @@ -73,9 +69,7 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationManager; import tech.pegasys.teku.networking.p2p.rpc.RpcMethod; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.Constants; -import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; @@ -378,48 +372,12 @@ protected DiscoveryNetwork buildNetwork( discoConfig.getMinPeers(), discoConfig.getMaxPeers(), discoConfig.getMinRandomlySelectedPeers()); - final Supplier currentSpecVersionSupplier = - () -> combinedChainDataClient.getRecentChainData().getCurrentSpec(); final SchemaDefinitionsSupplier currentSchemaDefinitions = () -> combinedChainDataClient.getRecentChainData().getCurrentSpec().getSchemaDefinitions(); - final Supplier> currentSlotSupplier = - () -> combinedChainDataClient.getRecentChainData().getCurrentSlot(); - - final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator - nodeIdToDataColumnSidecarSubnetsCalculator = - (nodeId, extraSubnetCount) -> { - SpecVersion currentSpecVersion = currentSpecVersionSupplier.get(); - Integer custodyRequirement = - currentSpecVersion - .getConfig() - .toVersionElectra() - .map(NetworkingSpecConfigElectra::getCustodyRequirement) - .orElse(0); - Optional currentEpoch = - currentSlotSupplier - .get() - .map(slot -> currentSpecVersion.miscHelpers().computeEpochAtSlot(slot)); - - SszBitvectorSchema bitvectorSchema = - SszBitvectorSchema.create(config.getTargetSubnetSubscriberCount()); - Optional sszBits = - currentSpecVersion - .miscHelpers() - .toVersionElectra() - .flatMap( - electraHelpers -> - currentEpoch.map( - epoch -> { - List nodeSubnets = - electraHelpers.computeDataColumnSidecarBackboneSubnets( - UInt256.fromBytes(nodeId.toBytes()), - epoch, - custodyRequirement + extraSubnetCount); - return bitvectorSchema.ofBits( - nodeSubnets.stream().map(UInt64::intValue).toList()); - })); - return sszBits.orElseGet(bitvectorSchema::getDefault); - }; + + final NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator = + NodeIdToDataColumnSidecarSubnetsCalculator.create( + spec, () -> combinedChainDataClient.getRecentChainData().getCurrentSlot()); final SettableLabelledGauge subnetPeerCountGauge = SettableLabelledGauge.create( @@ -439,7 +397,7 @@ protected DiscoveryNetwork buildNetwork( targetPeerRange, network -> PeerSubnetSubscriptions.create( - currentSpecVersionSupplier.get(), + combinedChainDataClient.getRecentChainData().getCurrentSpec(), nodeIdToDataColumnSidecarSubnetsCalculator, network, attestationSubnetTopicProvider, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java new file mode 100644 index 00000000000..f0e938afced --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java @@ -0,0 +1,77 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.gossip.subnets; + +import com.google.common.base.Supplier; +import java.util.List; +import java.util.Optional; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.p2p.peer.NodeId; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; + +@FunctionalInterface +public interface NodeIdToDataColumnSidecarSubnetsCalculator { + + Optional calculateSubnets(NodeId nodeId, int extraSubnetCount); + + NodeIdToDataColumnSidecarSubnetsCalculator NOOP = (nodeId, extraSubnetCount) -> Optional.empty(); + + /** Creates a calculator instance for the specific slot */ + private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( + SpecConfigElectra config, MiscHelpersElectra miscHelpers, UInt64 currentSlot) { + UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(currentSlot); + SszBitvectorSchema bitvectorSchema = + SszBitvectorSchema.create(config.getDataColumnSidecarSubnetCount()); + return (nodeId, extraSubnetCount) -> { + List nodeSubnets = + miscHelpers.computeDataColumnSidecarBackboneSubnets( + UInt256.fromBytes(nodeId.toBytes()), + currentEpoch, + config.getCustodyRequirement() + extraSubnetCount); + return Optional.of( + bitvectorSchema.ofBits(nodeSubnets.stream().map(UInt64::intValue).toList())); + }; + } + + /** Create an instance base on the current slot */ + static NodeIdToDataColumnSidecarSubnetsCalculator create( + Spec spec, Supplier> currentSlotSupplier) { + + return (nodeId, extraSubnetCount) -> + currentSlotSupplier + .get() + .flatMap( + slot -> { + SpecVersion specVersion = spec.atSlot(slot); + final NodeIdToDataColumnSidecarSubnetsCalculator calculatorAtSlot; + if (specVersion.getMilestone().isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)) { + calculatorAtSlot = + createAtSlot( + SpecConfigElectra.required(specVersion.getConfig()), + MiscHelpersElectra.required(specVersion.miscHelpers()), + slot); + } else { + calculatorAtSlot = NOOP; + } + return calculatorAtSlot.calculateSubnets(nodeId, extraSubnetCount); + }); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index 1268be55061..a9864457933 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -60,12 +60,6 @@ private PeerSubnetSubscriptions( this.targetSubnetSubscriberCount = targetSubnetSubscriberCount; } - @FunctionalInterface - public interface NodeIdToDataColumnSidecarSubnetsCalculator { - - SszBitvector calculateSubnets(NodeId nodeId, int extraSubnetCount); - } - public static PeerSubnetSubscriptions create( final SpecVersion currentVersion, final NodeIdToDataColumnSidecarSubnetsCalculator nodeIdToDataColumnSidecarSubnetsCalculator, @@ -88,7 +82,7 @@ public static PeerSubnetSubscriptions create( .orElse(0); final PeerSubnetSubscriptions subscriptions = - builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount) + builder(currentSchemaDefinitions, SszBitvectorSchema.create(dataColumnSidecarSubnetCount)) .targetSubnetSubscriberCount(targetSubnetSubscriberCount) .nodeIdToDataColumnSidecarSubnetsCalculator(nodeIdToDataColumnSidecarSubnetsCalculator) .attestationSubnetSubscriptions( @@ -173,15 +167,15 @@ private static IntStream streamAllSyncCommitteeSubnetIds( static Builder builder( final SchemaDefinitionsSupplier currentSchemaDefinitions, - Integer dataColumnSidecarSubnetCount) { - return new Builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount); + final SszBitvectorSchema dataColumnSidecarSubnetBitmaskSchema) { + return new Builder(currentSchemaDefinitions, dataColumnSidecarSubnetBitmaskSchema); } @VisibleForTesting static PeerSubnetSubscriptions createEmpty( final SchemaDefinitionsSupplier currentSchemaDefinitions, - Integer dataColumnSidecarSubnetCount) { - return builder(currentSchemaDefinitions, dataColumnSidecarSubnetCount).build(); + final SszBitvectorSchema dataColumnSidecarSubnetBitmaskSchema) { + return builder(currentSchemaDefinitions, dataColumnSidecarSubnetBitmaskSchema).build(); } public int getSubscriberCountForAttestationSubnet(final int subnetId) { @@ -210,7 +204,9 @@ public SszBitvector getDataColumnSidecarSubnetSubscriptions(final NodeId peerId) public SszBitvector getDataColumnSidecarSubnetSubscriptionsByNodeId( final NodeId peerId, final int extraSubnetCount) { - return nodeIdToDataColumnSidecarSubnetsCalculator.calculateSubnets(peerId, extraSubnetCount); + return nodeIdToDataColumnSidecarSubnetsCalculator + .calculateSubnets(peerId, extraSubnetCount) + .orElse(dataColumnSidecarSubnetSubscriptions.getSubscriptionSchema().getDefault()); } public boolean isSyncCommitteeSubnetRelevant(final int subnetId) { @@ -307,6 +303,10 @@ public SszBitvector getSubnetSubscriptions(final NodeId peerId) { return subscriptionsByPeer.getOrDefault(peerId, subscriptionSchema.getDefault()); } + public SszBitvectorSchema getSubscriptionSchema() { + return subscriptionSchema; + } + public static class Builder { private final SszBitvectorSchema subscriptionSchema; @@ -351,13 +351,13 @@ public static class Builder { private Builder( final SchemaDefinitionsSupplier currentSchemaDefinitions, - Integer dataColumnSidecarSubnetCount) { + final SszBitvectorSchema dataColumnSidecarSubnetBitmaskSchema) { attestationSubnetSubscriptions = SubnetSubscriptions.builder(currentSchemaDefinitions.getAttnetsENRFieldSchema()); syncCommitteeSubnetSubscriptions = SubnetSubscriptions.builder(currentSchemaDefinitions.getSyncnetsENRFieldSchema()); dataColumnSidecarSubnetSubscriptions = - SubnetSubscriptions.builder(SszBitvectorSchema.create(dataColumnSidecarSubnetCount)); + SubnetSubscriptions.builder(dataColumnSidecarSubnetBitmaskSchema); } public PeerSubnetSubscriptions build() { diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java index a7d2a13c6e5..f5d7e9746da 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptionsTest.java @@ -62,9 +62,6 @@ class PeerSubnetSubscriptionsTest { mock(SyncCommitteeSubnetTopicProvider.class); private final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = mock(DataColumnSidecarSubnetTopicProvider.class); - private final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator - nodeIdToDataColumnSidecarSubnetsCalculator = - mock(PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator.class); private final SubnetSubscriptionService syncnetSubscriptions = new SubnetSubscriptionService(); private final SubnetSubscriptionService dataColumnSubscriptions = new SubnetSubscriptionService(); @@ -210,7 +207,7 @@ public void isAttestationSubnetRelevant() { private PeerSubnetSubscriptions createPeerSubnetSubscriptions() { return PeerSubnetSubscriptions.create( currentSpecVersionSupplier.get(), - nodeIdToDataColumnSidecarSubnetsCalculator, + NodeIdToDataColumnSidecarSubnetsCalculator.NOOP, gossipNetwork, attestationTopicProvider, syncCommitteeTopicProvider, diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java index 24c78fea78a..1e213b598a3 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java @@ -31,6 +31,7 @@ import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.networking.eth2.peers.PeerScorer; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; import tech.pegasys.teku.networking.p2p.mock.MockNodeId; @@ -42,11 +43,15 @@ class SubnetScorerTest { private final Spec spec = TestSpecFactory.createMinimalAltair(); private final SchemaDefinitions schemaDefinitions = spec.getGenesisSchemaDefinitions(); + private static final int DATA_COLUMN_SIDECAR_SUBNET_COUNT = 128; @Test void shouldScoreCandidatePeerWithNoSubnetsAsZero() { final SubnetScorer scorer = - SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions, 128)); + SubnetScorer.create( + PeerSubnetSubscriptions.createEmpty( + () -> schemaDefinitions, + SszBitvectorSchema.create(DATA_COLUMN_SIDECAR_SUBNET_COUNT))); assertThat( scorer.scoreCandidatePeer( createDiscoveryPeer( @@ -58,7 +63,10 @@ void shouldScoreCandidatePeerWithNoSubnetsAsZero() { @Test void shouldScoreExistingPeerWithNoSubnetsAsZero() { final SubnetScorer scorer = - SubnetScorer.create(PeerSubnetSubscriptions.createEmpty(() -> schemaDefinitions, 128)); + SubnetScorer.create( + PeerSubnetSubscriptions.createEmpty( + () -> schemaDefinitions, + SszBitvectorSchema.create(DATA_COLUMN_SIDECAR_SUBNET_COUNT))); assertThat(scorer.scoreExistingPeer(new MockNodeId(1))).isZero(); } @@ -71,7 +79,9 @@ void shouldScoreExistingPeersOnSubnetsWithFewPeersMoreHighly() { final MockNodeId node5 = new MockNodeId(4); final SubnetScorer scorer = SubnetScorer.create( - PeerSubnetSubscriptions.builder(() -> schemaDefinitions, 0) + PeerSubnetSubscriptions.builder( + () -> schemaDefinitions, + SszBitvectorSchema.create(DATA_COLUMN_SIDECAR_SUBNET_COUNT)) .attestationSubnetSubscriptions( b -> b.addRelevantSubnet(1) @@ -120,7 +130,9 @@ void shouldScoreCandidatePeersOnSubnetsWithFewPeersMoreHighly() { final MockNodeId node3 = new MockNodeId(2); final SubnetScorer scorer = SubnetScorer.create( - PeerSubnetSubscriptions.builder(() -> schemaDefinitions, 128) + PeerSubnetSubscriptions.builder( + () -> schemaDefinitions, + SszBitvectorSchema.create(DATA_COLUMN_SIDECAR_SUBNET_COUNT)) .attestationSubnetSubscriptions( b -> b.addRelevantSubnet(1) diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index 233875fd9b7..53a319bd307 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -42,11 +42,10 @@ import tech.pegasys.teku.infrastructure.events.EventChannels; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.infrastructure.subscribers.Subscribers; import tech.pegasys.teku.infrastructure.time.StubTimeProvider; import tech.pegasys.teku.infrastructure.time.TimeProvider; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.network.p2p.jvmlibp2p.PrivateKeyGenerator; import tech.pegasys.teku.networking.eth2.gossip.config.GossipConfigurator; @@ -61,6 +60,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; +import tech.pegasys.teku.networking.eth2.gossip.subnets.NodeIdToDataColumnSidecarSubnetsCalculator; import tech.pegasys.teku.networking.eth2.gossip.subnets.PeerSubnetSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.topics.Eth2GossipTopicFilter; @@ -216,13 +216,6 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { final DataColumnSidecarSubnetTopicProvider dataColumnSidecarSubnetTopicProvider = new DataColumnSidecarSubnetTopicProvider( combinedChainDataClient.getRecentChainData(), gossipEncoding); - final PeerSubnetSubscriptions.NodeIdToDataColumnSidecarSubnetsCalculator - nodeIdToDataColumnSidecarSubnetsCalculator = - (nodeId, extraSubnetCount) -> { - SszBitvectorSchema bitvectorSchema = - SszBitvectorSchema.create(config.getTargetSubnetSubscriberCount()); - return bitvectorSchema.getDefault(); - }; if (rpcEncoding == null) { rpcEncoding = @@ -283,6 +276,8 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { .getRecentChainData() .getCurrentSpec() .getSchemaDefinitions(); + final Supplier> currentSlotSupplier = + () -> combinedChainDataClient.getRecentChainData().getCurrentSlot(); final SettableLabelledGauge subnetPeerCountGauge = SettableLabelledGauge.create( metricsSystem, @@ -318,7 +313,8 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { gossipNetwork -> PeerSubnetSubscriptions.create( currentSpecVersionSupplier.get(), - nodeIdToDataColumnSidecarSubnetsCalculator, + NodeIdToDataColumnSidecarSubnetsCalculator.create( + spec, currentSlotSupplier), gossipNetwork, attestationSubnetTopicProvider, syncCommitteeTopicProvider, From 6444f9ccc2b3bb360514c6e729600627b853f9e8 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 30 Apr 2024 08:33:12 +0400 Subject: [PATCH 36/70] Remove Electra stuff (#26) * removed all Electra features * renamed from Electra to EIP7594, compatible with spec config * configs updated to be close to those in spec * fixed bug with kzg_commitment_inclusion_proof size * fixed all tests * modified acceptance test BlockProposalAcceptanceTest to ensure we could run up to EIP7594 fork --- .../BlockProposalAcceptanceTest.java | 24 +- .../test/acceptance/dsl/GenesisGenerator.java | 18 + .../acceptance/dsl/TekuNodeConfigBuilder.java | 13 + .../coordinator/DepositProviderBenchmark.java | 2 +- .../coordinator/DepositProvider.java | 22 +- .../validator/coordinator/Eth1DataCache.java | 14 +- .../coordinator/DepositProviderTest.java | 55 --- .../coordinator/Eth1DataCacheTest.java | 31 +- .../coordinator/Eth1DataProviderTest.java | 2 +- .../paths/_eth_v1_beacon_blinded_blocks.json | 4 +- .../beacon/paths/_eth_v1_beacon_blocks.json | 4 +- .../_eth_v1_beacon_light_client_updates.json | 2 +- .../paths/_eth_v2_beacon_blinded_blocks.json | 4 +- .../beacon/paths/_eth_v2_beacon_blocks.json | 4 +- ...ectra.json => BeaconBlockBodyEip7594.json} | 4 +- ...ckElectra.json => BeaconBlockEip7594.json} | 4 +- ...teElectra.json => BeaconStateEip7594.json} | 60 +-- ...ctra.json => BlindedBlockBodyEip7594.json} | 4 +- ...kElectra.json => BlindedBlockEip7594.json} | 4 +- ...Electra.json => BlockContentsEip7594.json} | 4 +- .../beacon/schema/DepositReceipt.json | 37 -- .../beacon/schema/ExecutionLayerExit.json | 19 - ...ctra.json => ExecutionPayloadEip7594.json} | 16 +- ...son => ExecutionPayloadHeaderEip7594.json} | 16 +- .../schema/GetAllBlocksAtSlotResponse.json | 4 +- .../schema/GetBlindedBlockResponse.json | 4 +- .../beacon/schema/GetBlockV2Response.json | 4 +- .../GetLightClientBootstrapResponse.json | 2 +- .../schema/GetNewBlindedBlockResponse.json | 4 +- .../beacon/schema/GetStateV2Response.json | 4 +- .../beacon/schema/PendingBalanceDeposit.json | 19 - .../beacon/schema/PendingConsolidation.json | 19 - .../schema/PendingPartialWithdrawal.json | 25 - .../beacon/schema/ProduceBlockV2Response.json | 4 +- .../beacon/schema/ProduceBlockV3Response.json | 6 +- ...tra.json => SignedBeaconBlockEip7594.json} | 4 +- ...ra.json => SignedBlindedBlockEip7594.json} | 4 +- ...a.json => SignedBlockContentsEip7594.json} | 4 +- .../v3/newBlindedBlockEIP7594.json | 1 + .../v3/newBlindedBlockELECTRA.json | 358 -------------- .../v3/newBlockContentsEIP7594.json | 1 + .../v3/newBlockContentsELECTRA.json | 419 ---------------- .../teku/api/SchemaObjectProvider.java | 36 +- .../teku/api/ValidatorDataProvider.java | 8 +- .../teku/api/ValidatorDataProviderTest.java | 6 +- .../v2/beacon/GetBlockResponseV2.java | 8 +- .../teku/api/schema/ExecutionPayload.java | 4 +- .../api/schema/ExecutionPayloadHeader.java | 4 +- .../teku/api/schema/SignedBeaconBlock.java | 12 +- .../tech/pegasys/teku/api/schema/Version.java | 4 +- .../BeaconBlockBodyEip7594.java} | 34 +- .../BeaconBlockEip7594.java} | 20 +- .../schema/eip7594/BeaconStateEip7594.java | 178 +++++++ .../BlindedBeaconBlockBodyEip7594.java} | 30 +- .../BlindedBlockEip7594.java} | 18 +- .../ExecutionPayloadEip7594.java} | 55 +-- .../ExecutionPayloadHeaderEip7594.java} | 53 +-- .../SignedBeaconBlockEip7594.java} | 16 +- .../SignedBlindedBeaconBlockEip7594.java} | 16 +- .../schema/electra/BeaconStateElectra.java | 284 ----------- .../api/schema/electra/DepositReceipt.java | 72 --- .../schema/electra/ExecutionLayerExit.java | 48 -- .../schema/electra/PendingBalanceDeposit.java | 57 --- .../schema/electra/PendingConsolidation.java | 59 --- .../electra/PendingPartialWithdrawal.java | 66 --- .../provider/JsonProviderPropertyTest.java | 12 +- .../teku/api/schema/BeaconStateTest.java | 4 +- .../operations/DefaultOperationProcessor.java | 36 -- .../common/operations/OperationProcessor.java | 8 - .../operations/OperationsTestExecutor.java | 24 - .../tech/pegasys/teku/ethtests/TestFork.java | 2 +- .../ethtests/finder/ReferenceTestFinder.java | 2 +- .../teku/ethtests/finder/TestDefinition.java | 2 +- .../web3j/Web3JExecutionEngineClientTest.java | 66 +-- .../ExecutionEngineClient.java | 9 - .../ThrottlingExecutionEngineClient.java | 16 - .../methods/EngineGetPayloadV4.java | 101 ---- .../methods/EngineNewPayloadV4.java | 77 --- .../MetricRecordingExecutionEngineClient.java | 19 - .../schema/ExecutionPayloadV4.java | 184 ------- .../schema/GetPayloadV4Response.java | 44 -- .../web3j/Web3JExecutionEngineClient.java | 33 -- .../methods/EngineGetPayloadV4Test.java | 155 ------ .../methods/EngineNewPayloadV4Test.java | 136 ------ ...toneBasedEngineJsonRpcMethodsResolver.java | 15 +- .../ElectraExecutionClientHandlerTest.java | 143 ------ ...BasedEngineJsonRpcMethodsResolverTest.java | 44 +- .../networks/Eth2NetworkConfiguration.java | 26 +- .../java/tech/pegasys/teku/spec/Spec.java | 46 +- .../tech/pegasys/teku/spec/SpecFactory.java | 14 +- .../tech/pegasys/teku/spec/SpecMilestone.java | 8 +- .../tech/pegasys/teku/spec/SpecVersion.java | 16 +- ....java => NetworkingSpecConfigEip7594.java} | 2 +- .../pegasys/teku/spec/config/SpecConfig.java | 2 +- .../teku/spec/config/SpecConfigDeneb.java | 1 + .../teku/spec/config/SpecConfigEip7594.java | 49 ++ .../spec/config/SpecConfigEip7594Impl.java | 142 ++++++ .../teku/spec/config/SpecConfigElectra.java | 80 ---- .../spec/config/SpecConfigElectraImpl.java | 236 --------- .../teku/spec/config/SpecConfigLoader.java | 2 +- .../teku/spec/config/SpecConfigReader.java | 8 +- .../spec/config/builder/Eip7594Builder.java | 149 ++++++ .../spec/config/builder/ElectraBuilder.java | 270 ----------- .../config/builder/SpecConfigBuilder.java | 10 +- .../versions/{electra => eip7594}/Cell.java | 2 +- .../{electra => eip7594}/CellSchema.java | 6 +- .../{electra => eip7594}/DataColumn.java | 2 +- .../DataColumnSchema.java | 6 +- .../DataColumnSidecar.java | 2 +- .../DataColumnSidecarSchema.java | 11 +- .../blocks/blockbody/BeaconBlockBody.java | 8 +- .../blockbody/BeaconBlockBodySchema.java | 4 +- .../BeaconBlockBodyBuilderEip7594.java} | 32 +- .../BeaconBlockBodyEip7594.java} | 18 +- .../BeaconBlockBodyEip7594Impl.java} | 40 +- .../BeaconBlockBodySchemaEip7594.java} | 14 +- .../BeaconBlockBodySchemaEip7594Impl.java} | 36 +- .../BlindedBeaconBlockBodyEip7594.java} | 14 +- .../BlindedBeaconBlockBodyEip7594Impl.java} | 36 +- .../BlindedBeaconBlockBodySchemaEip7594.java} | 12 +- ...ndedBeaconBlockBodySchemaEip7594Impl.java} | 42 +- .../execution/ExecutionPayload.java | 4 +- .../execution/ExecutionPayloadBuilder.java | 6 - .../execution/ExecutionPayloadFields.java | 6 +- .../execution/ExecutionPayloadHeader.java | 4 +- .../ExecutionPayloadHeaderBuilder.java | 4 - .../execution/ExecutionPayloadSchema.java | 14 - .../ExecutionPayloadBuilderBellatrix.java | 13 - ...xecutionPayloadHeaderBuilderBellatrix.java | 11 - .../ExecutionPayloadSchemaBellatrix.java | 27 -- .../ExecutionPayloadSchemaCapella.java | 26 - .../deneb/ExecutionPayloadSchemaDeneb.java | 26 - .../ExecutionPayloadBuilderEip7594.java} | 40 +- .../ExecutionPayloadEip7594.java} | 19 +- .../ExecutionPayloadEip7594Impl.java} | 60 +-- ...ExecutionPayloadHeaderBuilderEip7594.java} | 42 +- .../ExecutionPayloadHeaderEip7594.java} | 11 +- .../ExecutionPayloadHeaderEip7594Impl.java} | 54 +-- .../ExecutionPayloadHeaderSchemaEip7594.java} | 62 +-- .../ExecutionPayloadSchemaEip7594.java} | 90 +--- .../versions/electra/DepositReceipt.java | 77 --- .../electra/DepositReceiptSchema.java | 57 --- .../versions/electra/ExecutionLayerExit.java | 54 --- .../electra/ExecutionLayerExitSchema.java | 44 -- ...umnSidecarsByRootRequestMessageSchema.java | 6 +- .../state/beaconstate/BeaconState.java | 4 +- .../state/beaconstate/MutableBeaconState.java | 4 +- .../beaconstate/common/BeaconStateFields.java | 13 +- .../versions/eip7594/BeaconStateEip7594.java | 51 ++ .../BeaconStateEip7594Impl.java} | 24 +- .../eip7594/BeaconStateSchemaEip7594.java | 150 ++++++ .../eip7594/MutableBeaconStateEip7594.java | 37 ++ .../MutableBeaconStateEip7594Impl.java} | 28 +- .../versions/electra/BeaconStateElectra.java | 113 ----- .../electra/BeaconStateSchemaElectra.java | 248 ---------- .../electra/MutableBeaconStateElectra.java | 93 ---- .../electra/PendingBalanceDeposit.java | 69 --- .../electra/PendingConsolidation.java | 65 --- .../electra/PendingPartialWithdrawal.java | 78 --- .../util/ColumnSlotAndIdentifier.java | 2 +- .../util/DepositReceiptsUtil.java | 73 --- .../ExecutionLayerChannelStub.java | 24 +- .../common/block/AbstractBlockProcessor.java | 29 -- .../logic/common/block/BlockProcessor.java | 12 - .../logic/common/helpers/MiscHelpers.java | 10 +- .../SpecLogicEip7594.java} | 36 +- .../eip7594/block/BlockProcessorEip7594.java | 60 +++ .../forktransition/Eip7594StateUpgrade.java} | 33 +- .../helpers/MiscHelpersEip7594.java} | 44 +- .../electra/block/BlockProcessorElectra.java | 236 --------- .../teku/spec/schemas/SchemaDefinitions.java | 2 +- ...tra.java => SchemaDefinitionsEip7594.java} | 149 ++---- .../teku/spec/config/configs/holesky.yaml | 6 +- .../teku/spec/config/configs/less-swift.yaml | 6 +- .../teku/spec/config/configs/mainnet.yaml | 16 +- .../teku/spec/config/configs/minimal.yaml | 15 +- .../teku/spec/config/configs/sepolia.yaml | 6 +- .../teku/spec/config/configs/swift.yaml | 15 +- .../spec/config/presets/mainnet/eip7594.yaml | 14 + .../spec/config/presets/mainnet/electra.yaml | 42 -- .../spec/config/presets/minimal/eip7594.yaml | 14 + .../spec/config/presets/minimal/electra.yaml | 44 -- .../spec/config/presets/swift/eip7594.yaml | 14 + .../spec/config/presets/swift/electra.yaml | 44 -- .../electra/DepositReceiptPropertyTest.java | 39 -- .../ExecutionLayerExitPropertyTest.java | 41 -- .../pegasys/teku/spec/SpecMilestoneTest.java | 44 +- .../pegasys/teku/spec/SpecVersionTest.java | 12 +- .../spec/config/SpecConfigBuilderTest.java | 4 +- ...raTest.java => SpecConfigEip7594Test.java} | 43 +- .../spec/config/SpecConfigLoaderTest.java | 2 +- .../operations/ExecutionLayerExitTest.java | 64 --- .../logic/StateUpgradeTransitionTest.java | 10 +- .../logic/common/helpers/MiscHelpersTest.java | 16 - .../logic/common/util/BlindBlockUtilTest.java | 2 +- .../block/BlockProcessorElectraTest.java | 450 ------------------ .../helpers/MiscHelpersElectraTest.java | 76 --- .../pegasys/teku/spec/TestSpecFactory.java | 70 +-- .../spec/generator/BlockProposalTestUtil.java | 4 +- .../electra/DepositReceiptSupplier.java | 26 - .../electra/ExecutionLayerExitSupplier.java | 26 - ...ra.java => BeaconStateBuilderEip7594.java} | 91 +--- .../teku/spec/util/DataStructureUtil.java | 102 +--- .../datacolumns/CustodySync.java | 2 +- .../datacolumns/DataColumnSidecarCustody.java | 2 +- .../DataColumnSidecarCustodyImpl.java | 14 +- .../datacolumns/DataColumnSidecarDB.java | 2 +- .../datacolumns/DataColumnSidecarDBImpl.java | 2 +- .../datacolumns/DataColumnSidecarManager.java | 2 +- .../DataColumnSidecarRetriever.java | 2 +- .../retriever/DataColumnReqResp.java | 2 +- .../retriever/SimpleSidecarRetriever.java | 10 +- .../ValidatingDataColumnReqResp.java | 2 +- .../DataColumnSidecarGossipValidator.java | 2 +- .../DataColumnSidecarValidator.java | 2 +- .../BlobSidecarGossipValidatorTest.java | 2 +- .../validation/BlockGossipValidatorTest.java | 2 +- .../AbstractRpcMethodIntegrationTest.java | 10 +- .../eth2/GetMetadataIntegrationTest.java | 2 +- .../eth2/Eth2P2PNetworkBuilder.java | 6 +- .../DataColumnSidecarGossipChannel.java | 2 +- .../DataColumnSidecarGossipManager.java | 2 +- .../eth2/gossip/forks/GossipForkManager.java | 2 +- .../gossip/forks/GossipForkSubscriptions.java | 8 +- ...va => GossipForkSubscriptionsEip7594.java} | 6 +- ...ColumnSidecarSubnetBackboneSubscriber.java | 10 +- .../DataColumnSidecarSubnetSubscriptions.java | 14 +- ...dToDataColumnSidecarSubnetsCalculator.java | 12 +- .../subnets/PeerSubnetSubscriptions.java | 10 +- .../eth2/gossip/topics/GossipTopics.java | 6 +- .../eth2/peers/DefaultEth2Peer.java | 8 +- .../teku/networking/eth2/peers/Eth2Peer.java | 2 +- .../rpc/beaconchain/BeaconChainMethods.java | 10 +- ...SidecarsByRootListenerValidatingProxy.java | 2 +- ...ataColumnSidecarsByRootMessageHandler.java | 2 +- .../DataColumnSidecarsByRootValidator.java | 2 +- .../context/ForkDigestPayloadContext.java | 6 +- .../eth2/gossip/subnets/SubnetScorerTest.java | 2 + .../eth2/Eth2P2PNetworkFactory.java | 6 +- .../eth2/peers/RespondingEth2Peer.java | 2 +- .../beaconchain/BeaconChainController.java | 4 +- .../services/powchain/PowchainService.java | 31 +- .../powchain/PowchainServiceTest.java | 49 +- .../storage/api/SidecarUpdateChannel.java | 2 +- .../teku/storage/api/StorageQueryChannel.java | 2 +- .../storage/server/kvstore/DatabaseTest.java | 10 +- .../client/CombinedChainDataClient.java | 2 +- .../teku/storage/server/ChainStorage.java | 2 +- .../CombinedStorageChannelSplitter.java | 2 +- .../pegasys/teku/storage/server/Database.java | 2 +- .../server/kvstore/KvStoreDatabase.java | 2 +- .../dataaccess/CombinedKvStoreDao.java | 2 +- .../dataaccess/KvStoreCombinedDao.java | 2 +- .../dataaccess/KvStoreCombinedDaoAdapter.java | 2 +- .../dataaccess/V4FinalizedKvStoreDao.java | 2 +- .../storage/server/noop/NoOpDatabase.java | 2 +- .../store/StoreTransactionUpdatesFactory.java | 2 +- .../storage/api/StubSidecarUpdateChannel.java | 2 +- .../storage/api/StubStorageQueryChannel.java | 2 +- .../teku/cli/options/Eth2NetworkOptions.java | 10 +- .../services/BeaconNodeServiceController.java | 25 +- ...ECTRA.json => newBlindedBlockEIP7594.json} | 2 +- .../newBlockContentsEIP7594.json | 1 + .../newBlockContentsELECTRA.json | 1 - 264 files changed, 1841 insertions(+), 7123 deletions(-) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BeaconBlockBodyElectra.json => BeaconBlockBodyEip7594.json} (95%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BeaconBlockElectra.json => BeaconBlockEip7594.json} (90%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BeaconStateElectra.json => BeaconStateEip7594.json} (73%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BlindedBlockBodyElectra.json => BlindedBlockBodyEip7594.json} (94%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BlindedBlockElectra.json => BlindedBlockEip7594.json} (89%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{BlockContentsElectra.json => BlockContentsEip7594.json} (85%) delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/DepositReceipt.json delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionLayerExit.json rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{ExecutionPayloadElectra.json => ExecutionPayloadEip7594.json} (90%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{ExecutionPayloadHeaderElectra.json => ExecutionPayloadHeaderEip7594.json} (85%) delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{SignedBeaconBlockElectra.json => SignedBeaconBlockEip7594.json} (73%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{SignedBlindedBlockElectra.json => SignedBlindedBlockEip7594.json} (73%) rename data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/{SignedBlockContentsElectra.json => SignedBlockContentsEip7594.json} (84%) create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockEIP7594.json delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockELECTRA.json create mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsEIP7594.json delete mode 100644 data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsELECTRA.json rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/BeaconBlockBodyElectra.java => eip7594/BeaconBlockBodyEip7594.java} (88%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/BeaconBlockElectra.java => eip7594/BeaconBlockEip7594.java} (76%) create mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconStateEip7594.java rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/BlindedBeaconBlockBodyElectra.java => eip7594/BlindedBeaconBlockBodyEip7594.java} (88%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/BlindedBlockElectra.java => eip7594/BlindedBlockEip7594.java} (81%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/ExecutionPayloadElectra.java => eip7594/ExecutionPayloadEip7594.java} (58%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/ExecutionPayloadHeaderElectra.java => eip7594/ExecutionPayloadHeaderEip7594.java} (63%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/SignedBeaconBlockElectra.java => eip7594/SignedBeaconBlockEip7594.java} (75%) rename data/serializer/src/main/java/tech/pegasys/teku/api/schema/{electra/SignedBlindedBeaconBlockElectra.java => eip7594/SignedBlindedBeaconBlockEip7594.java} (77%) delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/DepositReceipt.java delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionLayerExit.java delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java delete mode 100644 data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java delete mode 100644 ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java delete mode 100644 ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4.java delete mode 100644 ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/ExecutionPayloadV4.java delete mode 100644 ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java delete mode 100644 ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java delete mode 100644 ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4Test.java delete mode 100644 ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/{NetworkingSpecConfigElectra.java => NetworkingSpecConfigEip7594.java} (93%) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/Cell.java (94%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/CellSchema.java (87%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/DataColumn.java (93%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/DataColumnSchema.java (86%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/DataColumnSidecar.java (98%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/{electra => eip7594}/DataColumnSidecarSchema.java (94%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BeaconBlockBodyBuilderElectra.java => eip7594/BeaconBlockBodyBuilderEip7594.java} (73%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BeaconBlockBodyElectra.java => eip7594/BeaconBlockBodyEip7594.java} (72%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BeaconBlockBodyElectraImpl.java => eip7594/BeaconBlockBodyEip7594Impl.java} (83%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BeaconBlockBodySchemaElectra.java => eip7594/BeaconBlockBodySchemaEip7594.java} (73%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BeaconBlockBodySchemaElectraImpl.java => eip7594/BeaconBlockBodySchemaEip7594Impl.java} (91%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BlindedBeaconBlockBodyElectra.java => eip7594/BlindedBeaconBlockBodyEip7594.java} (74%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BlindedBeaconBlockBodyElectraImpl.java => eip7594/BlindedBeaconBlockBodyEip7594Impl.java} (83%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BlindedBeaconBlockBodySchemaElectra.java => eip7594/BlindedBeaconBlockBodySchemaEip7594.java} (74%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/{electra/BlindedBeaconBlockBodySchemaElectraImpl.java => eip7594/BlindedBeaconBlockBodySchemaEip7594Impl.java} (88%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadBuilderElectra.java => eip7594/ExecutionPayloadBuilderEip7594.java} (63%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadElectra.java => eip7594/ExecutionPayloadEip7594.java} (72%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadElectraImpl.java => eip7594/ExecutionPayloadEip7594Impl.java} (80%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadHeaderBuilderElectra.java => eip7594/ExecutionPayloadHeaderBuilderEip7594.java} (62%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadHeaderElectra.java => eip7594/ExecutionPayloadHeaderEip7594.java} (77%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadHeaderElectraImpl.java => eip7594/ExecutionPayloadHeaderEip7594Impl.java} (82%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadHeaderSchemaElectra.java => eip7594/ExecutionPayloadHeaderSchemaEip7594.java} (78%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/{electra/ExecutionPayloadSchemaElectra.java => eip7594/ExecutionPayloadSchemaEip7594.java} (71%) delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceipt.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptSchema.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExit.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitSchema.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/{electra/BeaconStateElectraImpl.java => eip7594/BeaconStateEip7594Impl.java} (77%) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateSchemaEip7594.java create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/{electra/MutableBeaconStateElectraImpl.java => eip7594/MutableBeaconStateEip7594Impl.java} (71%) delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectra.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/{electra/SpecLogicElectra.java => eip7594/SpecLogicEip7594.java} (88%) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/block/BlockProcessorEip7594.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/{electra/forktransition/ElectraStateUpgrade.java => eip7594/forktransition/Eip7594StateUpgrade.java} (82%) rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/{electra/helpers/MiscHelpersElectra.java => eip7594/helpers/MiscHelpersEip7594.java} (67%) delete mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java rename ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/{SchemaDefinitionsElectra.java => SchemaDefinitionsEip7594.java} (64%) create mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7594.yaml delete mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml create mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7594.yaml delete mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml create mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/eip7594.yaml delete mode 100644 ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml delete mode 100644 ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptPropertyTest.java delete mode 100644 ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitPropertyTest.java rename ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/{SpecConfigElectraTest.java => SpecConfigEip7594Test.java} (65%) delete mode 100644 ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/ExecutionLayerExitTest.java delete mode 100644 ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java delete mode 100644 ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java delete mode 100644 ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/DepositReceiptSupplier.java delete mode 100644 ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/ExecutionLayerExitSupplier.java rename ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/{BeaconStateBuilderElectra.java => BeaconStateBuilderEip7594.java} (56%) rename networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/{GossipForkSubscriptionsElectra.java => GossipForkSubscriptionsEip7594.java} (96%) rename validator/remote/src/integration-test/resources/responses/produce_block_responses/{newBlindedBlockELECTRA.json => newBlindedBlockEIP7594.json} (60%) create mode 100644 validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsEIP7594.json delete mode 100644 validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsELECTRA.json diff --git a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java index 5d579c84008..653381860dc 100644 --- a/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java +++ b/acceptance-tests/src/acceptance-test/java/tech/pegasys/teku/test/acceptance/BlockProposalAcceptanceTest.java @@ -22,7 +22,7 @@ import java.util.Locale; import org.apache.tuweni.bytes.Bytes32; import org.junit.jupiter.api.Test; -import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.test.acceptance.dsl.AcceptanceTestBase; import tech.pegasys.teku.test.acceptance.dsl.GenesisGenerator.InitialStateData; @@ -45,7 +45,10 @@ void shouldHaveCorrectFeeRecipientAndGraffiti() throws Exception { createGenesisGenerator() .network(networkName) .withAltairEpoch(UInt64.ZERO) - .withBellatrixEpoch(UInt64.ZERO) + .withBellatrixEpoch(UInt64.ONE) + .withCapellaEpoch(UInt64.valueOf(2)) + .withDenebEpoch(UInt64.valueOf(3)) + .withEip7594Epoch(UInt64.valueOf(4)) .validatorKeys(validatorKeystores, validatorKeystores) .generate(); @@ -58,8 +61,14 @@ void shouldHaveCorrectFeeRecipientAndGraffiti() throws Exception { .withJwtSecretFile(JWT_FILE) .withNetwork(networkName) .withInitialState(genesis) + .withRealNetwork() .withAltairEpoch(UInt64.ZERO) - .withBellatrixEpoch(UInt64.ZERO) + .withBellatrixEpoch(UInt64.ONE) + .withCapellaEpoch(UInt64.valueOf(2)) + .withDenebEpoch(UInt64.valueOf(3)) + .withEip7594Epoch(UInt64.valueOf(4)) + .withTotalTerminalDifficulty(0) + .withTrustedSetupFromClasspath("mainnet-trusted-setup.txt") .withValidatorProposerDefaultFeeRecipient(defaultFeeRecipient) .build()); final TekuValidatorNode validatorClient = @@ -76,14 +85,15 @@ void shouldHaveCorrectFeeRecipientAndGraffiti() throws Exception { beaconNode.start(); validatorClient.start(); + beaconNode.waitForEpochAtOrAbove(4); beaconNode.waitForBlockSatisfying( block -> { - assertThat(block).isInstanceOf(SignedBeaconBlockBellatrix.class); - final SignedBeaconBlockBellatrix bellatrixBlock = (SignedBeaconBlockBellatrix) block; + assertThat(block).isInstanceOf(SignedBeaconBlockEip7594.class); + final SignedBeaconBlockEip7594 eip7594Block = (SignedBeaconBlockEip7594) block; assertThat( - bellatrixBlock.getMessage().getBody().executionPayload.feeRecipient.toHexString()) + eip7594Block.getMessage().getBody().executionPayload.feeRecipient.toHexString()) .isEqualTo(defaultFeeRecipient.toLowerCase(Locale.ROOT)); - final Bytes32 graffiti = bellatrixBlock.getMessage().getBody().graffiti; + final Bytes32 graffiti = eip7594Block.getMessage().getBody().graffiti; final String graffitiMessage = new String( Arrays.copyOfRange( diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/GenesisGenerator.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/GenesisGenerator.java index d38b538c5fc..5a196b5fc85 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/GenesisGenerator.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/GenesisGenerator.java @@ -71,6 +71,24 @@ public GenesisGenerator withCapellaEpoch(final UInt64 capellaForkEpoch) { return this; } + public GenesisGenerator withDenebEpoch(final UInt64 denebForkEpoch) { + specConfigModifier = + specConfigModifier.andThen( + specConfigBuilder -> + specConfigBuilder.denebBuilder( + denebBuilder -> denebBuilder.denebForkEpoch(denebForkEpoch))); + return this; + } + + public GenesisGenerator withEip7594Epoch(final UInt64 eip7594ForkEpoch) { + specConfigModifier = + specConfigModifier.andThen( + specConfigBuilder -> + specConfigBuilder.eip7594Builder( + eip7594Builder -> eip7594Builder.eip7594ForkEpoch(eip7594ForkEpoch))); + return this; + } + public GenesisGenerator withTotalTerminalDifficulty(final long totalTerminalDifficulty) { return withTotalTerminalDifficulty(UInt256.valueOf(totalTerminalDifficulty)); } diff --git a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java index 8f9c84f3bcd..42a826c3801 100644 --- a/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java +++ b/acceptance-tests/src/testFixtures/java/tech/pegasys/teku/test/acceptance/dsl/TekuNodeConfigBuilder.java @@ -184,6 +184,19 @@ public TekuNodeConfigBuilder withDenebEpoch(final UInt64 denebForkEpoch) { return this; } + public TekuNodeConfigBuilder withEip7594Epoch(final UInt64 eip7594ForkEpoch) { + + mustBe(NodeType.BEACON_NODE); + LOG.debug("Xnetwork-das-fork-epoch={}", eip7594ForkEpoch); + configMap.put("Xnetwork-das-fork-epoch", eip7594ForkEpoch.toString()); + specConfigModifier = + specConfigModifier.andThen( + specConfigBuilder -> + specConfigBuilder.eip7594Builder( + eip7594Builder -> eip7594Builder.eip7594ForkEpoch(eip7594ForkEpoch))); + return this; + } + public TekuNodeConfigBuilder withTotalTerminalDifficulty(final long totalTerminalDifficulty) { return withTotalTerminalDifficulty(UInt256.valueOf(totalTerminalDifficulty)); } diff --git a/beacon/validator/src/jmh/java/tech/pegasys/teku/validator/coordinator/DepositProviderBenchmark.java b/beacon/validator/src/jmh/java/tech/pegasys/teku/validator/coordinator/DepositProviderBenchmark.java index b3f3c4c83e7..ae392ea42e9 100644 --- a/beacon/validator/src/jmh/java/tech/pegasys/teku/validator/coordinator/DepositProviderBenchmark.java +++ b/beacon/validator/src/jmh/java/tech/pegasys/teku/validator/coordinator/DepositProviderBenchmark.java @@ -68,7 +68,7 @@ public class DepositProviderBenchmark { new DepositProvider( metricsSystem, mock(RecentChainData.class), - new Eth1DataCache(spec, metricsSystem, new Eth1VotingPeriod(spec)), + new Eth1DataCache(metricsSystem, new Eth1VotingPeriod(spec)), mock(StorageUpdateChannel.class), mock(Eth1DepositStorageChannel.class), spec, diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/DepositProvider.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/DepositProvider.java index 3a519af6808..db98ba13296 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/DepositProvider.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/DepositProvider.java @@ -184,9 +184,6 @@ public void onSlot(final UInt64 slot) { .get() .thenAccept( state -> { - if (spec.isFormerDepositMechanismDisabled(state)) { - return; - } // We want to verify our Beacon Node view of the eth1 deposits. // So we want to check if it has the necessary deposit data to propose a block final UInt64 eth1DepositCount = state.getEth1Data().getDepositCount(); @@ -211,10 +208,6 @@ public synchronized SszList getDeposits( final BeaconState state, final Eth1Data eth1Data) { final long maxDeposits = spec.getMaxDeposits(state); final SszListSchema depositsSchema = depositsSchemaCache.get(maxDeposits); - // no Eth1 deposits needed if already transitioned to the EIP-6110 mechanism - if (spec.isFormerDepositMechanismDisabled(state)) { - return depositsSchema.createFromElements(emptyList()); - } final UInt64 eth1DepositCount; if (spec.isEnoughVotesToUpdateEth1Data(state, eth1Data, 1)) { eth1DepositCount = eth1Data.getDepositCount(); @@ -224,20 +217,7 @@ public synchronized SszList getDeposits( final UInt64 eth1DepositIndex = state.getEth1DepositIndex(); final UInt64 eth1PendingDepositCount = - state - .toVersionElectra() - .map( - stateElectra -> { - // EIP-6110 - final UInt64 eth1DepositIndexLimit = - eth1DepositCount.min(stateElectra.getDepositReceiptsStartIndex()); - return eth1DepositIndexLimit.minusMinZero(eth1DepositIndex).min(maxDeposits); - }) - .orElseGet( - () -> { - // Phase0 - return eth1DepositCount.minusMinZero(eth1DepositIndex).min(maxDeposits); - }); + eth1DepositCount.minusMinZero(eth1DepositIndex).min(maxDeposits); // No deposits to include if (eth1PendingDepositCount.isZero()) { diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/Eth1DataCache.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/Eth1DataCache.java index d688d0e057e..81e0977eac0 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/Eth1DataCache.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/Eth1DataCache.java @@ -28,7 +28,6 @@ import tech.pegasys.teku.infrastructure.metrics.SettableGauge; import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; @@ -40,7 +39,6 @@ public class Eth1DataCache { static final String VOTES_CURRENT_METRIC_NAME = "eth1_current_period_votes_current"; static final String VOTES_BEST_METRIC_NAME = "eth1_current_period_votes_best"; - private final Spec spec; private final Eth1VotingPeriod eth1VotingPeriod; private final UInt64 cacheDuration; @@ -52,9 +50,7 @@ public class Eth1DataCache { private final SettableGauge currentPeriodVotesBest; private final SettableGauge currentPeriodVotesMax; - public Eth1DataCache( - final Spec spec, final MetricsSystem metricsSystem, final Eth1VotingPeriod eth1VotingPeriod) { - this.spec = spec; + public Eth1DataCache(final MetricsSystem metricsSystem, final Eth1VotingPeriod eth1VotingPeriod) { this.eth1VotingPeriod = eth1VotingPeriod; cacheDuration = eth1VotingPeriod.getCacheDurationInSeconds(); metricsSystem.createIntegerGauge( @@ -115,10 +111,6 @@ public void onEth1Block( } public Eth1Data getEth1Vote(final BeaconState state) { - if (spec.isFormerDepositMechanismDisabled(state)) { - // no need for a real vote when Eth1 polling has been disabled - return state.getEth1Data(); - } final NavigableMap votesToConsider = getVotesToConsider(state.getSlot(), state.getGenesisTime(), state.getEth1Data()); // Avoid using .values() directly as it has O(n) lookup which gets expensive fast @@ -142,10 +134,6 @@ public Collection getAllEth1Blocks() { } public void updateMetrics(final BeaconState state) { - if (spec.isFormerDepositMechanismDisabled(state)) { - // no need to update metrics when Eth1 polling has been disabled - return; - } final Eth1Data currentEth1Data = state.getEth1Data(); // Avoid using .values() directly as it has O(n) lookup which gets expensive fast final Set knownBlocks = diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java index 47d27a2c759..a26e0cf3b34 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/DepositProviderTest.java @@ -51,7 +51,6 @@ import tech.pegasys.teku.spec.datastructures.state.AnchorPoint; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; import tech.pegasys.teku.spec.datastructures.util.DepositUtil; import tech.pegasys.teku.spec.datastructures.util.MerkleTree; import tech.pegasys.teku.spec.networks.Eth2Network; @@ -174,37 +173,6 @@ void numberOfDepositsThatCanBeIncludedMoreThanMaxDeposits() { checkThatDepositProofIsValid(deposits); } - @Test - void noDepositsIncludedIfFormerDepositMechanismHasBeenDisabled() { - setup(16, SpecMilestone.ELECTRA); - updateStateEth1DepositIndex(5); - updateStateDepositReceiptsStartIndex(5); - - final SszList deposits = depositProvider.getDeposits(state, randomEth1Data); - - assertThat(deposits).isEmpty(); - } - - @Test - void getsRemainingEth1PendingDepositsIfElectraIsEnabled() { - setup(16, SpecMilestone.ELECTRA); - updateStateEth1DepositIndex(5); - updateStateEth1DataDepositCount(20); - // 16th deposit is using the new mechanism - updateStateDepositReceiptsStartIndex(16); - - mockDepositsFromEth1Block(0, 10); - mockDepositsFromEth1Block(10, 20); - - final SszList deposits = depositProvider.getDeposits(state, randomEth1Data); - - // the pending Eth1 deposits (deposit_receipt_start_index - eth1_deposit_index) - // we need to process eth1_deposit_index deposit (5) up to 16 (exclusive) so 11 is the - // expected size - assertThat(deposits).hasSize(11); - checkThatDepositProofIsValid(deposits); - } - @Test void depositsWithFinalizedIndicesGetPrunedFromMap() { setup(16); @@ -305,21 +273,6 @@ void shouldLogAnEventOnSlotWhenAllDepositsRequiredForStateNotAvailable() { verify(eventLogger).eth1DepositDataNotAvailable(UInt64.valueOf(9), UInt64.valueOf(10)); } - @Test - void - shouldNotLogAnEventOnSlotIfFormerDepositMechanismIsDisabled_EvenIfAllDepositsRequiredForStateNotAvailable() { - setup(1, SpecMilestone.ELECTRA); - mockDepositsFromEth1Block(0, 8); - updateStateEth1DepositIndex(5); - updateStateDepositReceiptsStartIndex(5); - updateStateEth1DataDepositCount(10); - when(recentChainData.getBestState()).thenReturn(Optional.of(SafeFuture.completedFuture(state))); - - depositProvider.onSlot(UInt64.ONE); - - verifyNoInteractions(eventLogger); - } - @Test void shouldNotLogAnEventOnSlotWhenAllDepositsRequiredForStateAvailable() { setup(1); @@ -536,12 +489,4 @@ private void updateStateEth1DataDepositCount(int n) { private void updateStateEth1DepositIndex(int n) { state = state.updated(mutableState -> mutableState.setEth1DepositIndex(UInt64.valueOf(n))); } - - private void updateStateDepositReceiptsStartIndex(int n) { - state = - state.updated( - mutableState -> - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex(UInt64.valueOf(n))); - } } diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataCacheTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataCacheTest.java index ba6e5027341..0419e6ce86d 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataCacheTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataCacheTest.java @@ -67,7 +67,7 @@ void setUp() { when(eth1VotingPeriod.getSpecRangeLowerBound(SLOT, GENESIS_TIME)) .thenReturn(VOTING_PERIOD_START); when(eth1VotingPeriod.getSpecRangeUpperBound(SLOT, GENESIS_TIME)).thenReturn(VOTING_PERIOD_END); - eth1DataCache = new Eth1DataCache(spec, metricsSystem, eth1VotingPeriod); + eth1DataCache = new Eth1DataCache(metricsSystem, eth1VotingPeriod); } // Add tests for eth1 block with no votes @@ -215,17 +215,6 @@ void noValidVotesInThisPeriod_eth1ChainNotLive() { assertThat(eth1DataCache.getEth1Vote(beaconState)).isEqualTo(stateEth1Data); } - @Test - void shouldReturnStateEth1Data_ifFormerDepositMechanismHasBeenDisabled() { - final Spec spec = mock(Spec.class); - eth1DataCache = new Eth1DataCache(spec, new StubMetricsSystem(), eth1VotingPeriod); - final BeaconState beaconState = createBeaconStateWithVotes(createEth1Data(STATE_DEPOSIT_COUNT)); - - when(spec.isFormerDepositMechanismDisabled(beaconState)).thenReturn(true); - - assertThat(eth1DataCache.getEth1Vote(beaconState)).isEqualTo(beaconState.getEth1Data()); - } - @Test void shouldPruneOldBlocksWhenNewerOnesReceived() { final UInt64 olderBlockTimestamp = ZERO; @@ -265,24 +254,6 @@ void shouldUpdateMetrics() { assertGaugeValue(Eth1DataCache.VOTES_BEST_METRIC_NAME, 4); } - @Test - void shouldNotUpdateMetrics_ifFormerDepositMechanismHasBeenDisabled() { - final Spec spec = mock(Spec.class); - eth1DataCache = new Eth1DataCache(spec, new StubMetricsSystem(), eth1VotingPeriod); - final BeaconState beaconState = getStateForMetricsAssertions(); - - when(spec.isFormerDepositMechanismDisabled(beaconState)).thenReturn(true); - - eth1DataCache.updateMetrics(beaconState); - - // all gauge values are 0 - assertGaugeValue(Eth1DataCache.VOTES_TOTAL_METRIC_NAME, 0); - assertGaugeValue(Eth1DataCache.VOTES_MAX_METRIC_NAME, 0); - assertGaugeValue(Eth1DataCache.VOTES_UNKNOWN_METRIC_NAME, 0); - assertGaugeValue(Eth1DataCache.VOTES_CURRENT_METRIC_NAME, 0); - assertGaugeValue(Eth1DataCache.VOTES_BEST_METRIC_NAME, 0); - } - @Test void shouldIncludeUnknownBlocksWhenCalculatingVoteBestMetric() { // Metric needs to indicate when a block will be voted in which happens even when unknown diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataProviderTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataProviderTest.java index cc667ba40ab..6cfe1bd6130 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataProviderTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/Eth1DataProviderTest.java @@ -70,7 +70,7 @@ public void setup() { assertThat(slotsInVotingPeriod).isEqualTo(SLOTS_IN_VOTING_PERIOD_ASSERTION); final Eth1VotingPeriod eth1VotingPeriod = new Eth1VotingPeriod(spec); - final Eth1DataCache eth1DataCache = new Eth1DataCache(spec, metricsSystem, eth1VotingPeriod); + final Eth1DataCache eth1DataCache = new Eth1DataCache(metricsSystem, eth1VotingPeriod); final DepositProvider depositProvider = new DepositProvider( new StubMetricsSystem(), diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blinded_blocks.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blinded_blocks.json index 3a257f0a796..d6a2e1a3f47 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blinded_blocks.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blinded_blocks.json @@ -9,7 +9,7 @@ "in" : "header", "schema" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ], "description" : "Version of the block being submitted, if using SSZ encoding." } } ], @@ -36,7 +36,7 @@ }, { "$ref" : "#/components/schemas/SignedBlindedBlockDeneb" }, { - "$ref" : "#/components/schemas/SignedBlindedBlockElectra" + "$ref" : "#/components/schemas/SignedBlindedBlockEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blocks.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blocks.json index 85e3ef12d04..d1f0d93c593 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blocks.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_blocks.json @@ -9,7 +9,7 @@ "in" : "header", "schema" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ], "description" : "Version of the block being submitted, if using SSZ encoding." } } ], @@ -36,7 +36,7 @@ }, { "$ref" : "#/components/schemas/SignedBlockContentsDeneb" }, { - "$ref" : "#/components/schemas/SignedBlockContentsElectra" + "$ref" : "#/components/schemas/SignedBlockContentsEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json index 56fb7296377..7b0a5b2cf60 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v1_beacon_light_client_updates.json @@ -44,7 +44,7 @@ "bellatrix", "capella", "deneb", - "electra" + "eip7594" ] }, "data": { diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blinded_blocks.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blinded_blocks.json index f0a98e76d1f..6b4120efcba 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blinded_blocks.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blinded_blocks.json @@ -19,7 +19,7 @@ "in" : "header", "schema" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ], "description" : "Version of the block being submitted." } } ], @@ -46,7 +46,7 @@ }, { "$ref" : "#/components/schemas/SignedBlindedBlockDeneb" }, { - "$ref" : "#/components/schemas/SignedBlindedBlockElectra" + "$ref" : "#/components/schemas/SignedBlindedBlockEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blocks.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blocks.json index c0e383d8aa1..f968fef7455 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blocks.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/paths/_eth_v2_beacon_blocks.json @@ -19,7 +19,7 @@ "in" : "header", "schema" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ], + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ], "description" : "Version of the block being submitted." } } ], @@ -46,7 +46,7 @@ }, { "$ref" : "#/components/schemas/SignedBlockContentsDeneb" }, { - "$ref" : "#/components/schemas/SignedBlockContentsElectra" + "$ref" : "#/components/schemas/SignedBlockContentsEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyEip7594.json similarity index 95% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyEip7594.json index 20a5d211897..5548476b61b 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockBodyEip7594.json @@ -1,5 +1,5 @@ { - "title" : "BeaconBlockBodyElectra", + "title" : "BeaconBlockBodyEip7594", "type" : "object", "required" : [ "randao_reveal", "eth1_data", "graffiti", "proposer_slashings", "attester_slashings", "attestations", "deposits", "voluntary_exits", "sync_aggregate", "execution_payload", "bls_to_execution_changes", "blob_kzg_commitments" ], "properties" : { @@ -52,7 +52,7 @@ "$ref" : "#/components/schemas/SyncAggregate" }, "execution_payload" : { - "$ref" : "#/components/schemas/ExecutionPayloadElectra" + "$ref" : "#/components/schemas/ExecutionPayloadEip7594" }, "bls_to_execution_changes" : { "type" : "array", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockEip7594.json similarity index 90% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockEip7594.json index 227ae04b0f5..99775d037e8 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconBlockEip7594.json @@ -1,5 +1,5 @@ { - "title" : "BeaconBlockElectra", + "title" : "BeaconBlockEip7594", "type" : "object", "required" : [ "slot", "proposer_index", "parent_root", "state_root", "body" ], "properties" : { @@ -28,7 +28,7 @@ "format" : "byte" }, "body" : { - "$ref" : "#/components/schemas/BeaconBlockBodyElectra" + "$ref" : "#/components/schemas/BeaconBlockBodyEip7594" } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateEip7594.json similarity index 73% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateEip7594.json index eb3779670fa..47c73566deb 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BeaconStateEip7594.json @@ -1,7 +1,7 @@ { - "title" : "BeaconStateElectra", + "title" : "BeaconStateEip7594", "type" : "object", - "required" : [ "genesis_time", "genesis_validators_root", "slot", "fork", "latest_block_header", "block_roots", "state_roots", "historical_roots", "eth1_data", "eth1_data_votes", "eth1_deposit_index", "validators", "balances", "randao_mixes", "slashings", "previous_epoch_participation", "current_epoch_participation", "justification_bits", "previous_justified_checkpoint", "current_justified_checkpoint", "finalized_checkpoint", "inactivity_scores", "current_sync_committee", "next_sync_committee", "latest_execution_payload_header", "next_withdrawal_index", "next_withdrawal_validator_index", "historical_summaries", "deposit_receipts_start_index", "deposit_balance_to_consume", "exit_balance_to_consume", "earliest_exit_epoch", "consolidation_balance_to_consume", "earliest_consolidation_epoch", "pending_balance_deposits", "pending_partial_withdrawals", "pending_consolidations" ], + "required" : [ "genesis_time", "genesis_validators_root", "slot", "fork", "latest_block_header", "block_roots", "state_roots", "historical_roots", "eth1_data", "eth1_data_votes", "eth1_deposit_index", "validators", "balances", "randao_mixes", "slashings", "previous_epoch_participation", "current_epoch_participation", "justification_bits", "previous_justified_checkpoint", "current_justified_checkpoint", "finalized_checkpoint", "inactivity_scores", "current_sync_committee", "next_sync_committee", "latest_execution_payload_header", "next_withdrawal_index", "next_withdrawal_validator_index", "historical_summaries" ], "properties" : { "genesis_time" : { "type" : "string", @@ -151,7 +151,7 @@ "$ref" : "#/components/schemas/SyncCommittee" }, "latest_execution_payload_header" : { - "$ref" : "#/components/schemas/ExecutionPayloadHeaderElectra" + "$ref" : "#/components/schemas/ExecutionPayloadHeaderEip7594" }, "next_withdrawal_index" : { "type" : "string", @@ -170,60 +170,6 @@ "items" : { "$ref" : "#/components/schemas/HistoricalSummary" } - }, - "deposit_receipts_start_index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "deposit_balance_to_consume" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "exit_balance_to_consume" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "earliest_exit_epoch" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "consolidation_balance_to_consume" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "earliest_consolidation_epoch" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "pending_balance_deposits" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PendingBalanceDeposit" - } - }, - "pending_partial_withdrawals" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PendingPartialWithdrawal" - } - }, - "pending_consolidations" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/PendingConsolidation" - } } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyEip7594.json similarity index 94% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyEip7594.json index ddaac27bfd1..7bd1771f56c 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockBodyEip7594.json @@ -1,5 +1,5 @@ { - "title" : "BlindedBlockBodyElectra", + "title" : "BlindedBlockBodyEip7594", "type" : "object", "required" : [ "randao_reveal", "eth1_data", "graffiti", "proposer_slashings", "attester_slashings", "attestations", "deposits", "voluntary_exits", "sync_aggregate", "execution_payload_header", "bls_to_execution_changes", "blob_kzg_commitments" ], "properties" : { @@ -52,7 +52,7 @@ "$ref" : "#/components/schemas/SyncAggregate" }, "execution_payload_header" : { - "$ref" : "#/components/schemas/ExecutionPayloadHeaderElectra" + "$ref" : "#/components/schemas/ExecutionPayloadHeaderEip7594" }, "bls_to_execution_changes" : { "type" : "array", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockEip7594.json similarity index 89% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockEip7594.json index e9f8fc4a9d7..c6c35b11df9 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlindedBlockEip7594.json @@ -1,5 +1,5 @@ { - "title" : "BlindedBlockElectra", + "title" : "BlindedBlockEip7594", "type" : "object", "required" : [ "slot", "proposer_index", "parent_root", "state_root", "body" ], "properties" : { @@ -28,7 +28,7 @@ "format" : "byte" }, "body" : { - "$ref" : "#/components/schemas/BlindedBlockBodyElectra" + "$ref" : "#/components/schemas/BlindedBlockBodyEip7594" } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsEip7594.json similarity index 85% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsEip7594.json index 49df0784a17..762b399d7c9 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/BlockContentsEip7594.json @@ -1,10 +1,10 @@ { - "title" : "BlockContentsElectra", + "title" : "BlockContentsEip7594", "type" : "object", "required" : [ "block", "kzg_proofs", "blobs" ], "properties" : { "block" : { - "$ref" : "#/components/schemas/BeaconBlockElectra" + "$ref" : "#/components/schemas/BeaconBlockEip7594" }, "kzg_proofs" : { "type" : "array", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/DepositReceipt.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/DepositReceipt.json deleted file mode 100644 index ba24132856e..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/DepositReceipt.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "title" : "DepositReceipt", - "type" : "object", - "required" : [ "pubkey", "withdrawal_credentials", "amount", "signature", "index" ], - "properties" : { - "pubkey" : { - "type" : "string", - "pattern" : "^0x[a-fA-F0-9]{2,}$", - "description" : "Bytes48 hexadecimal", - "format" : "bytes" - }, - "withdrawal_credentials" : { - "type" : "string", - "description" : "Bytes32 hexadecimal", - "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", - "format" : "byte" - }, - "amount" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "signature" : { - "type" : "string", - "pattern" : "^0x[a-fA-F0-9]{2,}$", - "description" : "SSZ hexadecimal", - "format" : "bytes" - }, - "index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionLayerExit.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionLayerExit.json deleted file mode 100644 index 38379274909..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionLayerExit.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title" : "ExecutionLayerExit", - "type" : "object", - "required" : [ "source_address", "validator_pubkey" ], - "properties" : { - "source_address" : { - "type" : "string", - "pattern" : "^0x[a-fA-F0-9]{2,}$", - "description" : "SSZ hexadecimal", - "format" : "bytes" - }, - "validator_pubkey" : { - "type" : "string", - "pattern" : "^0x[a-fA-F0-9]{2,}$", - "description" : "Bytes48 hexadecimal", - "format" : "bytes" - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadEip7594.json similarity index 90% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadEip7594.json index a9d96fe2378..3fb3656b007 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadEip7594.json @@ -1,7 +1,7 @@ { - "title" : "ExecutionPayloadElectra", + "title" : "ExecutionPayloadEip7594", "type" : "object", - "required" : [ "parent_hash", "fee_recipient", "state_root", "receipts_root", "logs_bloom", "prev_randao", "block_number", "gas_limit", "gas_used", "timestamp", "extra_data", "base_fee_per_gas", "block_hash", "transactions", "withdrawals", "blob_gas_used", "excess_blob_gas", "deposit_receipts", "exits" ], + "required" : [ "parent_hash", "fee_recipient", "state_root", "receipts_root", "logs_bloom", "prev_randao", "block_number", "gas_limit", "gas_used", "timestamp", "extra_data", "base_fee_per_gas", "block_hash", "transactions", "withdrawals", "blob_gas_used", "excess_blob_gas" ], "properties" : { "parent_hash" : { "type" : "string", @@ -107,18 +107,6 @@ "description" : "unsigned 64 bit integer", "example" : "1", "format" : "uint64" - }, - "deposit_receipts" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/DepositReceipt" - } - }, - "exits" : { - "type" : "array", - "items" : { - "$ref" : "#/components/schemas/ExecutionLayerExit" - } } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderEip7594.json similarity index 85% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderEip7594.json index a00d9819816..57b79c56181 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ExecutionPayloadHeaderEip7594.json @@ -1,7 +1,7 @@ { - "title" : "ExecutionPayloadHeaderElectra", + "title" : "ExecutionPayloadHeaderEip7594", "type" : "object", - "required" : [ "parent_hash", "fee_recipient", "state_root", "receipts_root", "logs_bloom", "prev_randao", "block_number", "gas_limit", "gas_used", "timestamp", "extra_data", "base_fee_per_gas", "block_hash", "transactions_root", "withdrawals_root", "blob_gas_used", "excess_blob_gas", "deposit_receipts_root", "exits_root" ], + "required" : [ "parent_hash", "fee_recipient", "state_root", "receipts_root", "logs_bloom", "prev_randao", "block_number", "gas_limit", "gas_used", "timestamp", "extra_data", "base_fee_per_gas", "block_hash", "transactions_root", "withdrawals_root", "blob_gas_used", "excess_blob_gas" ], "properties" : { "parent_hash" : { "type" : "string", @@ -104,18 +104,6 @@ "description" : "unsigned 64 bit integer", "example" : "1", "format" : "uint64" - }, - "deposit_receipts_root" : { - "type" : "string", - "description" : "Bytes32 hexadecimal", - "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", - "format" : "byte" - }, - "exits_root" : { - "type" : "string", - "description" : "Bytes32 hexadecimal", - "example" : "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2", - "format" : "byte" } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetAllBlocksAtSlotResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetAllBlocksAtSlotResponse.json index 80cd5b0740b..04cdd34b0a8 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetAllBlocksAtSlotResponse.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetAllBlocksAtSlotResponse.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "data" : { "type" : "array", @@ -27,7 +27,7 @@ }, { "$ref" : "#/components/schemas/BeaconBlockDeneb" }, { - "$ref" : "#/components/schemas/BeaconBlockElectra" + "$ref" : "#/components/schemas/BeaconBlockEip7594" } ] }, "signature" : { diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlindedBlockResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlindedBlockResponse.json index b6bba539a51..5142d0303ac 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlindedBlockResponse.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlindedBlockResponse.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "execution_optimistic" : { "type" : "boolean" @@ -27,7 +27,7 @@ }, { "$ref" : "#/components/schemas/SignedBlindedBlockDeneb" }, { - "$ref" : "#/components/schemas/SignedBlindedBlockElectra" + "$ref" : "#/components/schemas/SignedBlindedBlockEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockV2Response.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockV2Response.json index 87ad068b65f..fb7d2a7fdc7 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockV2Response.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetBlockV2Response.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "execution_optimistic" : { "type" : "boolean" @@ -27,7 +27,7 @@ }, { "$ref" : "#/components/schemas/SignedBeaconBlockDeneb" }, { - "$ref" : "#/components/schemas/SignedBeaconBlockElectra" + "$ref" : "#/components/schemas/SignedBeaconBlockEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetLightClientBootstrapResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetLightClientBootstrapResponse.json index 2d83bbbbbab..faddc373e92 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetLightClientBootstrapResponse.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetLightClientBootstrapResponse.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "data" : { "$ref" : "#/components/schemas/LightClientBootstrap" diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetNewBlindedBlockResponse.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetNewBlindedBlockResponse.json index 7d75499585c..581f563c0a2 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetNewBlindedBlockResponse.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetNewBlindedBlockResponse.json @@ -17,12 +17,12 @@ }, { "$ref" : "#/components/schemas/BlindedBlockDeneb" }, { - "$ref" : "#/components/schemas/BlindedBlockElectra" + "$ref" : "#/components/schemas/BlindedBlockEip7594" } ] }, "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetStateV2Response.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetStateV2Response.json index 27b011ce585..fcd39d7f559 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetStateV2Response.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/GetStateV2Response.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "execution_optimistic" : { "type" : "boolean" @@ -27,7 +27,7 @@ }, { "$ref" : "#/components/schemas/BeaconStateDeneb" }, { - "$ref" : "#/components/schemas/BeaconStateElectra" + "$ref" : "#/components/schemas/BeaconStateEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json deleted file mode 100644 index 9f93161f54c..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingBalanceDeposit.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title" : "PendingBalanceDeposit", - "type" : "object", - "required" : [ "index", "amount" ], - "properties" : { - "index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "amount" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json deleted file mode 100644 index aa9b77f2895..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingConsolidation.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "title" : "PendingConsolidation", - "type" : "object", - "required" : [ "source_index", "target_index" ], - "properties" : { - "source_index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "target_index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json deleted file mode 100644 index 8347212c107..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/PendingPartialWithdrawal.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "title" : "PendingPartialWithdrawal", - "type" : "object", - "required" : [ "index", "amount", "withdrawable_epoch" ], - "properties" : { - "index" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "amount" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - }, - "withdrawable_epoch" : { - "type" : "string", - "description" : "unsigned 64 bit integer", - "example" : "1", - "format" : "uint64" - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV2Response.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV2Response.json index 53bc8c5a503..578217383a2 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV2Response.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV2Response.json @@ -17,12 +17,12 @@ }, { "$ref" : "#/components/schemas/BlockContentsDeneb" }, { - "$ref" : "#/components/schemas/BlockContentsElectra" + "$ref" : "#/components/schemas/BlockContentsEip7594" } ] }, "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] } } } \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV3Response.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV3Response.json index 3abeb040a5b..1b50ae401dc 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV3Response.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/ProduceBlockV3Response.json @@ -5,7 +5,7 @@ "properties" : { "version" : { "type" : "string", - "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "electra" ] + "enum" : [ "phase0", "altair", "bellatrix", "capella", "deneb", "eip7594" ] }, "execution_payload_blinded" : { "type" : "boolean" @@ -42,9 +42,9 @@ }, { "$ref" : "#/components/schemas/BlindedBlockDeneb" }, { - "$ref" : "#/components/schemas/BlockContentsElectra" + "$ref" : "#/components/schemas/BlockContentsEip7594" }, { - "$ref" : "#/components/schemas/BlindedBlockElectra" + "$ref" : "#/components/schemas/BlindedBlockEip7594" } ] } } diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockEip7594.json similarity index 73% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockEip7594.json index f4bd8db4511..87cdc1176c2 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBeaconBlockEip7594.json @@ -1,10 +1,10 @@ { - "title" : "SignedBeaconBlockElectra", + "title" : "SignedBeaconBlockEip7594", "type" : "object", "required" : [ "message", "signature" ], "properties" : { "message" : { - "$ref" : "#/components/schemas/BeaconBlockElectra" + "$ref" : "#/components/schemas/BeaconBlockEip7594" }, "signature" : { "type" : "string", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockEip7594.json similarity index 73% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockEip7594.json index 89ca16c5a4e..7cbe0739c80 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlindedBlockEip7594.json @@ -1,10 +1,10 @@ { - "title" : "SignedBlindedBlockElectra", + "title" : "SignedBlindedBlockEip7594", "type" : "object", "required" : [ "message", "signature" ], "properties" : { "message" : { - "$ref" : "#/components/schemas/BlindedBlockElectra" + "$ref" : "#/components/schemas/BlindedBlockEip7594" }, "signature" : { "type" : "string", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsElectra.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsEip7594.json similarity index 84% rename from data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsElectra.json rename to data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsEip7594.json index c1e294612a3..f4a9e559bda 100644 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsElectra.json +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/beacon/schema/SignedBlockContentsEip7594.json @@ -1,10 +1,10 @@ { - "title" : "SignedBlockContentsElectra", + "title" : "SignedBlockContentsEip7594", "type" : "object", "required" : [ "signed_block", "kzg_proofs", "blobs" ], "properties" : { "signed_block" : { - "$ref" : "#/components/schemas/SignedBeaconBlockElectra" + "$ref" : "#/components/schemas/SignedBeaconBlockEip7594" }, "kzg_proofs" : { "type" : "array", diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockEIP7594.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockEIP7594.json new file mode 100644 index 00000000000..ee89ad769bd --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockEIP7594.json @@ -0,0 +1 @@ +{"version":"eip7594","execution_payload_blinded":true,"execution_payload_value":"50756583220288449835724789919752990744036228048165053817930899246206127260481","consensus_block_value":"24799950324699182119107049583125116496986047597328004586475399986067975839137","data":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload_header":{"parent_hash":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343291711b9de6b9dbebc4c9b49","fee_recipient":"0xbf886c3ec849316e3b187793c3a4398b6097768d","state_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","receipts_root":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9","logs_bloom":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d078748f9069c96e9d6a2801cf607000a52447e46e1bef4e056ee30d4bd3517aaf7bf65ba04dd28c3a4a14b8dc72a300f051722a6814fa3931d90a82d23285d4c1127b6c67bbc4f8682ddbf9b31eb3114c26dccc5330109d6f17799339c2d7ed7e4e3a7de5d515106aaec7be6d78be3e21806d6d30c39b77c75dcf354b63033fb200b3b9dc023d948278f0956c0ee99323da0162f2a84b6a95749d2fa1d4e089af416d412ccd992683f7e41f7b496ca04f9f463806e3643d1c07f39d2a65f84e97b7dfaafac740d1e03f30923a4270fcf651ad2ca3737859a524e86e02229a55abd1a7","prev_randao":"0x0c0d553e4878ae811024144112c88bbf79a372d5dfdf39730cede08696ad52d4","block_number":"4489858063226749928","gas_limit":"4481595642848361992","gas_used":"4479943159631677864","timestamp":"4484900609281730248","extra_data":"0x6bb2373e68f20adada72181a3474f2c098b26daf6fcb0516f0723270da91e789","base_fee_per_gas":"91973405088222260025272995045243630915786868313949746451634391325697029602367","block_hash":"0xde78143e27b846779904841e2aa96d8fbec4671bb57ffa72037ac721f8d633ca","transactions_root":"0xa415263e48d5a8a8ba3b4e9caf0e3028abbb6a65922580447af6fcc869b40d2a","withdrawals_root":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","blob_gas_used":"4476638188903342311","excess_blob_gas":"4521255257228650249"},"bls_to_execution_changes":[{"message":{"validator_index":"573888","from_bls_pubkey":"0xb8343e90edaecc9df1223293465ec067b3c9804f43e25817d27f1f4785bc5f554462032370781d9c65ab27bcc3d21415","to_execution_address":"0xdafbb23e48beb933bcf49f8ad83a43ee157382b5"},"signature":"0xa519e1354ad927358404a58bdc19113e5fd97d5cc19943888e22105ee943ca216a14898283fc3712500ba767de00022905e4198939b44a5f5a43fa0c87252969c56a26345135572101b257f87245a5e42fb2407a0ee67a6c2d039bf908b9aa8b"},{"message":{"validator_index":"189139","from_bls_pubkey":"0xa9ddce0cab5b51d3d2c710396b85e3fd7a87f1738fb5cfd5a7b25dbb483c167a80e785cb4ca7250c14a60cc282b1d9b8","to_execution_address":"0x9946783e88b272e45092a83c1c9310f154917869"},"signature":"0x8edfb3b9ed80067d0626019a1be330bac43c7ecd813f7ce781d0e6e34fb583803e9d2b047ad3294d6d3a54d020c68231085f7d9085d0afefb047def063a4698277e66d4a560f4b5bbd16586976f0bcf90177c00abd4a1b4cbd0ac393e5b904b5"},{"message":{"validator_index":"2357271","from_bls_pubkey":"0xa287d120292890ab1aa49bae1e3cd88bb160b5640f18c64f1aabae5990616e53099fe61698c3b812e2bc2ae6b6965960","to_execution_address":"0x09988f43d11dcf2aa7811c9997eb4119e8f153ce"},"signature":"0x8ca190827c66ff26c1fa594eae169b7efbd84c9456304f2194df7b0c204b0a29ac53034c9b20e4977b8e8b46d6b246da03a9337d3bf5e6f7ac941407a2a3437d7e2c0dcacda29b7623141833e02b4b12350ccaf8b27dbf96b3c520078f49efe2"},{"message":{"validator_index":"1972522","from_bls_pubkey":"0x8db8ee645b614f990839e4d98fdbf921263bb62cd917fb4eff9084dff23d7cc453f6cc645ad8b869aa9d31a6b9560630","to_execution_address":"0xc8e25443111288db3b1f254bdb430f1c27104a82"},"signature":"0xb0c3172e9bab8d04faa5d27f9818c36ad61a71b114f5bd9dbe77306be3edef2bcb56c215511ba76145006daec95f24be0f1f0dd24377cf7b440b5cdc7d0b520d6b64c539eaacaf14875d49c293af5974751bb0ce2daafde3bd01e097a466e75f"},{"message":{"validator_index":"48778","from_bls_pubkey":"0x8ba697cdd6f8c34a1fb96a4c88f03360d19515ccc4e1ea24aa5e80075d821059806a0047e6bbf5d908d312d1902aff5d","to_execution_address":"0xee24494351a9466526a5f3a1825538b6331648a6"},"signature":"0x87fadfd11bc5612e06c59d576c91599bc21095531fcc27a177967de7b521c377ee7a2b10d0fadf38779089929cfe136518757803c369b4ce94873e28d7d9cdf54c31a53ed86b07f76ea6104ee65d76de02267a4b736c949785ef233cbb73ad4a"},{"message":{"validator_index":"2820011","from_bls_pubkey":"0xa32a5f28ae7d36f888820160335232fc42ef994b4f93acf6a8659762b2ec52ca79354cc07c73a229b529bfcebc705eff","to_execution_address":"0x4a4dca439229167a13e413e752937416aad35d1a"},"signature":"0xa2089742415bdf32fa2dde853661a095ac24d273413687ae04fabb99ae2982700bcdb885d239e32543ffb95763a43e690cb1bf3a33df40d24e12c46d150e9c59dd63f960dec39712dabf74c08a55ba1bcb6db664ff9d5b2261da353e4374466c"},{"message":{"validator_index":"896267","from_bls_pubkey":"0xb679b4b686530827b2a201eb2b18454e9a5758d7257737b29bb215b9f354c2ff57e912b19d4a051556187aa24c97371b","to_execution_address":"0x708fbe43d1c0d403fd69e23dfaa49db0b6d95b3e"},"signature":"0x8da9cee45a3046b209da332512a6b4e4d7c89768f55998eb79ee236b4fb1fbcea87e0bba7b05d19ea7b8c5ea6dc0081e17a7ad0ec41566a0c6d9e127b87691e1d5b823fd178069e3f30091dcdbb44c36408656941755177c45bc976bf270289d"},{"message":{"validator_index":"511518","from_bls_pubkey":"0x83b8c61b63de768821cbd82ee3c67c81bb848163d6af0186ffe1ca3936d283bb4cab886f3fbc7f6336fec3da8d542c76","to_execution_address":"0x92fcc742102977503966d35cb217fc55bd583232"},"signature":"0x8c90298abaed4b5124cff46e41c9a4ed2b2baa0d2089add6b64c70dc7547f1a83bed76aba1fac6d36605beea72734b490b7b98994c7c65fdb436286b0df898731f6ad536e5a603da85ec8cc4488b94dc8c61e11363d1cc18733382dca51c7008"},{"message":{"validator_index":"1431791","from_bls_pubkey":"0xa532ee397fdd9e388888d90f712e13b085ad5043402debe1caf3dabbb514ed0d06f7c897e4e2795fd018cd672bfa8948","to_execution_address":"0xb83ebc4250c035da23eca1b3592925f0c95e3056"},"signature":"0x8fb8cb9373db269dd2a05fe0a07484db022a95b06c03807426a352499fcb65c55f8c388fd4cddbdd9936d5fe5ac5898e0d8b58ae09a73bdc7e584fe9940d3aa967607a0c4a1ad1ce5ccc0ad83f63a273e140ae0510f709cd0c214b645d68e3f4"},{"message":{"validator_index":"1047042","from_bls_pubkey":"0xb7d85608c3cf919ee72c0481283b468c2825850f6f6028c000cb19bff464556973909667d0353582d673e1049795f20c","to_execution_address":"0x778981428fb4ee8ab889aa659e81f2f2087d260a"},"signature":"0xa1079cff71763f60894927a0ac68cfff88642e5ec4e11d1f63ce7d7b15f2567842c80c0238a0f6e4d38ac2a9d09787c50c87daba460e05a0336332f1d37b65fed7526c5eb51a84d3a0169d09ddaf271d13710d22469e8dffde8859d50a2dd0a1"},{"message":{"validator_index":"2279280","from_bls_pubkey":"0xa46cb4c6f51759dd36e897cf8f5f8a774dbb5968defec8bcd85b9ec0f3d873a6569fabde6c6cf3fa5dc77e910bc39938","to_execution_address":"0x3aa93143d0d7c378fbb0904fd1788aea4c2244ee"},"signature":"0x988ea703ce8fcbd5bc7811c49e1eede7061ce461966a9a52f03afdecb157f065a1993fd71ea29c6769121610fc9e3e190eff938fb8c2f77dcf5f511208ad23cf427c05dd207b6c6004ba2a1ee3b6a84949e39db4ef1ee254635d3527010f7794"},{"message":{"validator_index":"1894531","from_bls_pubkey":"0xa18343c3306dae4ff3c78428069a4ae7876f0ad620219648b99b4bfaeea1d7898df50d508533e756f5903efbdf585076","to_execution_address":"0xf9f3f64210cc7c298f4e990115d157ed8b403aa2"},"signature":"0xa120e4f3144799db31e7487d25cbe6d8724f0076f23fdd7ff1f00b24b304a93a97862a3ebecb5e1b91018a0496a3c4020004b5d49571f4b9a3faf0f9d8f1f067d7005b5600db18872732313acf1350e1bec278384f3e0fe28d43f00203ae10e7"},{"message":{"validator_index":"2970787","from_bls_pubkey":"0xb23734206f673528ad12bad1b7815a9db722d7a5afffdfac97e17d453fcd2616a804619bd9f8db50b9547a357b1f5813","to_execution_address":"0x2036eb4250633bb379d46758bce28087974638c6"},"signature":"0x8de01f498b48fd1df0c20529228b7e8616c7bfc35457d392404110e394db4c884dad325363be1f2a83ac383486cdea460e78e89a728ac9464f71dfbc685ac8be3fb9ecb21d67a6c105354c58bfb78f2adf7ee65f5a4d7fbe5989e522b52daccf"},{"message":{"validator_index":"2430055","from_bls_pubkey":"0xb490d2df5759bb5115690df9aa805cddc1787b17fc3984ec400d03ccd5c6da6dbc54a724816ccf0c86b4b23e4daf0b17","to_execution_address":"0x42a3f4418ecbddffb5d058777555df2c9ec50eba"},"signature":"0x909ac7032213a33af76294ec19617f3fd9859bb22201e0502ae7187debe740c5cb0367ef03e944eab7fdc5ab23d303f916904a1ca5f7aadbcfbab89bdd82931dd7ff3e0efdd1135698f54774989ddd6d8ee07bebff863718c927072564a547bb"},{"message":{"validator_index":"506311","from_bls_pubkey":"0xa2810855686190fded08fbafafc427d3540a58c2b391c0d05a71be7a4d1aff2b4ea501c8e4c1ebb79cb49f1991ada976","to_execution_address":"0x68e5e841ce629c89a05627ce1c6708c7aacb0cde"},"signature":"0xa108770fd60463dfc982d8725440e47c54730329420bcf05a969e4937d06e468385b53c4a5f6c69e55a775f358fa0948171dedf3bb0ccc1679280251b7abe4cc644e10b46bcdaddd590951541bda68373c8a8dcbfb86d3cb97822a5dfc21f481"},{"message":{"validator_index":"121562","from_bls_pubkey":"0x8deafeba9f0184ffa1f3d1422b9d97d6975fc4d5a21df265b48b6e831d6aee5a6236b3d5fb9e03cab1e0795f3dd45206","to_execution_address":"0xc40d6a420fe36b9e8d954713eca4442721892252"},"signature":"0xb489851f8a8fd535ee14505b9ae32ab27cd8d5e637236f491f71bfc987316491ef3f1b7670378875580eb247993d82511128502ea093d108730e070bb8c5919b39e78893139b3f1a499e885b15d385073e227d6a4e85ba0413ab9e2481d0b8da"}],"blob_kzg_commitments":["0xb1ec6f426f978c599752e0e7181c305a1b8623c06088b5480b9aad7fe5f419d6c81a8f6862abe50a5e8cadf8649d347c","0x109252428f11e9b162a1e4c03bc8965b3a951e9aef7381ebf01fff6828d9ae8bbeb86d42469047b0c205fd55488427fc","0x23b34c422f5dc8f657e44bec0e51ab2840981d2ca63caaa51da14231033a661656d833a140f1279e0a1e40020f4c8be2","0xea4f5e424f7a2a28771b166a93b66dc12d8f207683e22f77941d78d8741740768f79e18451ce86d434d576fdbaf45f2f","0xfd705842efc5096d6c5e7d95673f828e34921f0839ab5831c29ebba04e78f7002799a7e34b2f67c27bedb9a981bcc315","0x5d163b420f4066c536ad816e89ebe88f53a11ae2c99624d4a7240d8a925c8cb61d3786bd2f14c967df66090765a3b695"]}}} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockELECTRA.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockELECTRA.json deleted file mode 100644 index d7c65381b8a..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlindedBlockELECTRA.json +++ /dev/null @@ -1,358 +0,0 @@ -{ - "version": "electra", - "execution_payload_blinded": true, - "execution_payload_value": "42104374537666016842731412608176468386512470599052556672967227278486679620790", - "consensus_block_value": "50756583220288449835724789919752990744036228048165053817930899246206127260481", - "data": { - "slot": "1", - "proposer_index": "4666673844721362956", - "parent_root": "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef", - "state_root": "0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e", - "body": { - "randao_reveal": "0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71", - "eth1_data": { - "deposit_root": "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f", - "deposit_count": "4658411424342975020", - "block_hash": "0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379" - }, - "graffiti": "0x0000000000000000000000000000000000000000000000000000000000000000", - "proposer_slashings": [ - { - "signed_header_1": { - "message": { - "slot": "4661716390776343276", - "proposer_index": "4600574485989226763", - "parent_root": "0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b", - "state_root": "0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb", - "body_root": "0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486" - }, - "signature": "0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483" - }, - "signed_header_2": { - "message": { - "slot": "4661716390776343276", - "proposer_index": "4600574485989226763", - "parent_root": "0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6", - "state_root": "0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26", - "body_root": "0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1" - }, - "signature": "0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1" - } - } - ], - "attester_slashings": [ - { - "attestation_1": { - "attesting_indices": [ - "4585702132744102314", - "4590659586689121994", - "4589007099177470570" - ], - "data": { - "slot": "4580744678799082634", - "index": "4579092195582398506", - "beacon_block_root": "0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c", - "source": { - "epoch": "533461240", - "root": "0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565" - }, - "target": { - "epoch": "538462976", - "root": "0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650" - } - }, - "signature": "0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc" - }, - "attestation_2": { - "attesting_indices": [ - "4585702132744102314", - "4590659586689121994", - "4589007099177470570" - ], - "data": { - "slot": "4620404293179370891", - "index": "4618751809962686763", - "beacon_block_root": "0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b", - "source": { - "epoch": "538078227", - "root": "0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb" - }, - "target": { - "epoch": "536923980", - "root": "0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5" - } - }, - "signature": "0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65" - } - } - ], - "attestations": [ - { - "aggregation_bits": "0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001", - "data": { - "slot": "4605531939934246443", - "index": "4610489389584298827", - "beacon_block_root": "0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b", - "source": { - "epoch": "529421377", - "root": "0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2" - }, - "target": { - "epoch": "529806126", - "root": "0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd" - } - }, - "signature": "0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc" - }, - { - "aggregation_bits": "0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101", - "data": { - "slot": "4544390030852162633", - "index": "4542737547635478505", - "beacon_block_root": "0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd", - "source": { - "epoch": "527690007", - "root": "0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8" - }, - "target": { - "epoch": "528074756", - "root": "0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8" - } - }, - "signature": "0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120" - }, - { - "aggregation_bits": "0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301", - "data": { - "slot": "4529517677607038185", - "index": "4574134745932346122", - "beacon_block_root": "0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947", - "source": { - "epoch": "532884117", - "root": "0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31" - }, - "target": { - "epoch": "531729870", - "root": "0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672" - } - }, - "signature": "0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683" - } - ], - "deposits": [ - { - "proof": [ - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c" - ], - "data": { - "pubkey": "0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d", - "withdrawal_credentials": "0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c", - "amount": "32000000000", - "signature": "0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb" - } - } - ], - "voluntary_exits": [ - { - "message": { - "epoch": "4562567354825622634", - "validator_index": "4564219838042306762" - }, - "signature": "0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e" - } - ], - "sync_aggregate": { - "sync_committee_bits": "0x01000000", - "sync_committee_signature": "0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206" - }, - "execution_payload_header": { - "parent_hash": "0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343291711b9de6b9dbebc4c9b49", - "fee_recipient": "0xbf886c3ec849316e3b187793c3a4398b6097768d", - "state_root": "0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34", - "receipts_root": "0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9", - "logs_bloom": "0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d078748f9069c96e9d6a2801cf607000a52447e46e1bef4e056ee30d4bd3517aaf7bf65ba04dd28c3a4a14b8dc72a300f051722a6814fa3931d90a82d23285d4c1127b6c67bbc4f8682ddbf9b31eb3114c26dccc5330109d6f17799339c2d7ed7e4e3a7de5d515106aaec7be6d78be3e21806d6d30c39b77c75dcf354b63033fb200b3b9dc023d948278f0956c0ee99323da0162f2a84b6a95749d2fa1d4e089af416d412ccd992683f7e41f7b496ca04f9f463806e3643d1c07f39d2a65f84e97b7dfaafac740d1e03f30923a4270fcf651ad2ca3737859a524e86e02229a55abd1a7", - "prev_randao": "0x0c0d553e4878ae811024144112c88bbf79a372d5dfdf39730cede08696ad52d4", - "block_number": "4489858063226749928", - "gas_limit": "4481595642848361992", - "gas_used": "4479943159631677864", - "timestamp": "4484900609281730248", - "extra_data": "0x6bb2373e68f20adada72181a3474f2c098b26daf6fcb0516f0723270da91e789", - "base_fee_per_gas": "91973405088222260025272995045243630915786868313949746451634391325697029602367", - "block_hash": "0xde78143e27b846779904841e2aa96d8fbec4671bb57ffa72037ac721f8d633ca", - "transactions_root": "0xa415263e48d5a8a8ba3b4e9caf0e3028abbb6a65922580447af6fcc869b40d2a", - "withdrawals_root": "0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be", - "blob_gas_used": "4476638188903342311", - "excess_blob_gas": "4521255257228650249", - "deposit_receipts_root": "0xc7dab83ea972daeec7b1385f04b22e210f708323c38b84160159653a163f259e", - "exits_root": "0x8e77ca3ec98f3c20e7e802dd8917f1b9fc66866da0310ae878d59ae1871cfffd" - }, - "bls_to_execution_changes": [ - { - "message": { - "validator_index": "958637", - "from_bls_pubkey": "0x89ca6fbbaafb3c27a42f70699f42e3f79d3b7681b103e5b393042efa90512aa9e590fccb0b654a5c1590bbe675aa584e", - "to_execution_address": "0x4dc28f3e0884f5d07b860b8fce6fbebc3b857c21" - }, - "signature": "0xa207b627176533d6fc3670d6c4a48afff107ef6bf9d7520b0f77162bf7df5505aa3a17c08ee53067daad827b75ef333015a61a81b421ec8888ff31dc50994121420862d11926782bf6983aab5f6f39c17264a77943dd6618d85444c56d469c19" - }, - { - "message": { - "validator_index": "2034892", - "from_bls_pubkey": "0xb384518c8bde1118e66c725854a5919c4a5c0453e8eb822fed707aa5c636e6e2a2aa56c95966817ec67ebadf6c77b44a", - "to_execution_address": "0x7304843e481bb45a660cdae57581e756478b7a45" - }, - "signature": "0xaf85c50db6dad8c04b9a22611e3174fdc57a3cfe255164e0361897353984556c3e7b91b040ec3b59cc29d774aaacf4b204e4e1960b9dd5bf9b72f8c3e09bdd64753a65084567c10b229784fe46be3de016c8add60d84c970943d221882294028" - }, - { - "message": { - "validator_index": "2742020", - "from_bls_pubkey": "0xb2bffd7d93c4b1e46b3f4511f97de3ac70e2f9b2e3428b26ad7864d9f71bbca34904c81dbcc1dc2ffd1c02bfa016b096", - "to_execution_address": "0x7b5e6c4391e30ac86613889d8c20bde70e044e3a" - }, - "signature": "0xa2a6993708f79d7b3db30c3968aaf4b706400145cf70fded02b06d54922e54a4c1db14d63860cc632d0b2a8c15efc5280bc57ceed454838f2a0848e03e610e130309d8012aa5eddc1fa84b509fc8178a685baaa0413ec58709e738aae430491d" - }, - { - "message": { - "validator_index": "818276", - "from_bls_pubkey": "0x94e2d4cf94fb757578c496885af2075c26e2483eeffa6e894ac791f7c1945b0fbf9a6f7860736db93e03d511c4b08516", - "to_execution_address": "0xa2a06043d17ac951519956f43432e6811a0a4c5e" - }, - "signature": "0xa8b4b8e92e67565ec430f2fdda94ed0f6f06d8cb302770191d614b795d194e4728c11e72162f25e04d0f7dda1dcd54da0d8a7c39e71e945873168ffa294da70dd1acbc1902a2fb1598267df5d277a0f95592967ea222ab0706571001c315eb2b" - }, - { - "message": { - "validator_index": "433527", - "from_bls_pubkey": "0xa5041469fc5f6a944fda64e7ab422c1479ab9d0de12a2f3ac7292dfe368408cbc6d2b0ff519b521427da731e7378806e", - "to_execution_address": "0xfdc8e14312fb98663ed87639047022e291c761d2" - }, - "signature": "0xb66c9d2c80f5a12930f0899b9ff3d1a6a37e0f9edb279ced767eca8ef0380227681b15bd3850a00a383491ed1d8e869310f10edea2b912278e1e2ec1cfaaba8c0981af2e40fd233a9fd2f67ec56540c66e062212ee2781593a4714914e15cb52" - }, - { - "message": { - "validator_index": "1665765", - "from_bls_pubkey": "0x9105e2e35c7861d3fee37cb3bf07e8fdf3e0911d251cb11b956d4edbbd62a951c7ac9854677ce19a7748a503c307028c", - "to_execution_address": "0x240bd643529257f0285e4590ab814b7c9dcd5ff6" - }, - "signature": "0xb3b0b28bedcc6e28d433c2a577204a9f7ecfc2fd4e3067ddcb65caebf7fd32d0389dd1db836600b0ee19a2ac8b6d0a660788a42abfde02bd5bddfdbe8cdde83a890ce69ee178ea314cfd9c06e5507ffe5cc4a685004f955219fcbbbec6fdd144" - }, - { - "message": { - "validator_index": "1281016", - "from_bls_pubkey": "0xaae4f1779eb7e006a9d0195e39af1f14a05b017a4a351ee1f3c22929929fb510cae4ba8e01b6d2444a66e388e655d92c", - "to_execution_address": "0xe3559b43918610a1bdfb4d42efd9187fdceb55aa" - }, - "signature": "0xa3acdd589f44c5b4201ae54cd119add73b60bcaca91f9e5d028669dd9b52f3ce15c20bb0ec39ff9ddfe96d5c1ce979c10376d36f4840a04cd90ed9d4348fa4a53f0f00e35bfab055a102ce3b6306255ffba3ef9ce7e1548048139d574478ebbf" - }, - { - "message": { - "validator_index": "2201289", - "from_bls_pubkey": "0x842bb38ef27bafce4e8aa9abd3e31286da4d36eb87ff6a2fc4de272e4878230a7ac7a723bf3f76101ab2c2a642550eff", - "to_execution_address": "0x6cbad342d091b8c64ee004060b06d3bbb052340e" - }, - "signature": "0xabd643eedb5dfcc8f2db27bcfd59f6359517cec81ab4d5ff08bd5fd246ba120883c047e0cffc1d215104169a335628180df5779f128772f899546fd260328d4a4368a044c3e2037f4284624728dc94e05467b1559aad3077cf9557bf62fc56e2" - }, - { - "message": { - "validator_index": "1816540", - "from_bls_pubkey": "0x990cf4f3bf6ede0aaef3010026465f98f381860535ce007b87879afbf2c955c13d07d7c2d91e22fddd8ef5531f8bd22c", - "to_execution_address": "0x2b0599420f867177e37d0db84f5ea0beef702ac2" - }, - "signature": "0xac8ebc3beb6cfc97c27f286e0d2e582707cbcb972d0898a41831b2d1393a684ce54ce54dc9128dc3988930ae4d92b4ed0a51b2bf639d8fd8e62e40ceac222362d9bb67f9d1b8419f3123dac1bb2e4e0cccb5c7c0985c83bd0501ed610935aa96" - }, - { - "message": { - "validator_index": "2892795", - "from_bls_pubkey": "0xa0695f8f6f65e3d8401e144eb382eaed73f9ec56be6de71dadb917af79a08ff7b74967dd4f4766ed77f7bc2fc01cfa38", - "to_execution_address": "0x51478d424f1d3001cd03dc0ef66fc958fb7628e6" - }, - "signature": "0xa18c2c70d886e11a592393a7bef6fb3a515100e1436763854eb96fca9c031a959e4c105be367a10ea87c3d1a8bce821303470a1d6053cd89139bbd86fd7bbdd3e377b331884bedb0f9b10eafcb3272561fc5d71b96b219d7fe3aacd6e1558c97" - }, - { - "message": { - "validator_index": "2664029", - "from_bls_pubkey": "0x97e268878248299c9e4d2c86957935d6cddb83900dbb6d4e52a935bcda58978f6fd33e0dc891cea14da0feafd5173762", - "to_execution_address": "0xad6f0e43909dff15ba42fc53c6ad05b972343e5a" - }, - "signature": "0xa2010187045aa6d63130c7ff23464438af57c3e42eaa90823205936a94c47713b68bd93d3b6837947e277ece630a6d200d428979548f340f6f71ca33e8731e059a8c20f75d71d36caebbbf6fde28f37a919353dedb7b7c7e4dbcda553e5bbee5" - }, - { - "message": { - "validator_index": "740284", - "from_bls_pubkey": "0x8aec1b1f595063af33939f3c3322ad38d2e1de1b11fbc8a9d04235dc7fc9792e1c88e51452d337855d254a71f42816e8", - "to_execution_address": "0xd3b10243d034be9fa5c8caaa6ebf2e537e3a3c7e" - }, - "signature": "0xa0ba14bb9ce5877d9f9d607da9b2fd2d629a1de42d6d3beb5a8f4c1661aa1d6863e01de14c548be8a9df222efc6373be1290581da81c76d71bfada1d07481d7b7de94290efd640aadca41d6b4d4f81091f4c459b454bd6e333eaa35c60faacf5" - }, - { - "message": { - "validator_index": "355536", - "from_bls_pubkey": "0xa912f4ad989d87e777e45af7c265b430daf0b39345987506d4158cdee406847f294fc7745154eb52abf0934a5e1866ee", - "to_execution_address": "0xf51e0c420e9d60ece0c4bbc926328df885b91272" - }, - "signature": "0xa7f77c7fc98b1c3a364dcac68b5cff112f7745e6dd41918ba56a6fa6945507e0ce245334e22d4581f49bda913baa2a6b1176b44d52168151b3aff9a625dcdebad1899747c42c4a43cf31f49124fc0d4543e4485592c243c5300b79214398b770" - }, - { - "message": { - "validator_index": "1275809", - "from_bls_pubkey": "0xa77e90361be2a534a386cb689d6d763a98bea5f23f325b553a762648668e4adb4991fb5f41ad7ece1578f082a5c01b5e", - "to_execution_address": "0x1c6100424e341f76cb4a8a20cd43b69291bf1096" - }, - "signature": "0xad188010cb0db88e067c2699030353a1c215ae9adf083916ee2069a805e0f2cd00c76db9250a859106dbbff4430b4dd114d6293c4b3c2e9cfd31f07949f04e53f63423a08b56d7247772d07959d5d92b17bd8c7c0b294b71d3db903d56509177" - }, - { - "message": { - "validator_index": "891060", - "from_bls_pubkey": "0xb4582d56f8ad9dcc77eb5413558e63a6b562e42534c579a85384e7d7d6ff8974ff933d05a444c1d2784945f4cd1c952e", - "to_execution_address": "0xdbabc5418e28d8265fe892d2129c8395d0dd064a" - }, - "signature": "0xa7f07c5a20159b029b2dac119315a0d439c541e63b0d1f6d377fd2867e5559d6b6302eb609d5796fab97cbca121ddf400c840b9ffa60dbcd89c6d441f84aff2cca1f68fd9e258a969b0d511ad1d90c0c783dde3c093ee8cd56cf6f70a61fd77a" - }, - { - "message": { - "validator_index": "2123298", - "from_bls_pubkey": "0xa5849044acc283563bd9b40fe9b01a8c079093829fc3837cddf20a8f9c13e59629251481406f415c8e2df65285ddb41f", - "to_execution_address": "0x9ecb7542cf4bad14a20f79bc45931b8d1483242e" - }, - "signature": "0x81df97c3071aac41af79494001a1c4404b5121776a71d6cbe3b8eef000e803f59edd2fff33331d2ea037faa919ddd6a115e09bead88d7c8f23368628f306e3a244f2ce0a54e4472d29e4b79eced6da3e5ab40177e96fa0d94d97f5e07d2e6e95" - } - ], - "blob_kzg_commitments": [ - "0x23b34c422f5dc8f657e44bec0e51ab2840981d2ca63caaa51da14231033a661656d833a140f1279e0a1e40020f4c8be2", - "0xea4f5e424f7a2a28771b166a93b66dc12d8f207683e22f77941d78d8741740768f79e18451ce86d434d576fdbaf45f2f", - "0xfd705842efc5096d6c5e7d95673f828e34921f0839ab5831c29ebba04e78f7002799a7e34b2f67c27bedb9a981bcc315" - ] - } - } -} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsEIP7594.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsEIP7594.json new file mode 100644 index 00000000000..37ea0fbd18c --- /dev/null +++ b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsEIP7594.json @@ -0,0 +1 @@ +{"version":"eip7594","execution_payload_blinded":false,"execution_payload_value":"11103013652248291612222159278156471032005615587981889885338966324871092985294","consensus_block_value":"19755222334871137581966673778023884694586692954373005173000436914838007452761","data":{"block":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload":{"parent_hash":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","fee_recipient":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343","state_root":"0xbf886c3ec849316e3b187793c3a4398b6097768d06bd968a54e8d2652d2a75a9","receipts_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","logs_bloom":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9f770a36a743c8a3abab61dc439ddc0604dd5015b1ed3835787d9565dee0f3e64b25de4c097defe3001f483a4b6feac22b992cada114bfc709d483b4d94f07bb0a1c4fb9e93ca3c31f4b9683753ba33ffd971777e301367f1edfe6809da491535c711a7877b4c97fd1a756136c412b4f3c4471ba439607333623558a63030f2cb6bc2ba885822672de14ea697d44fbcde134b6909208466be0b4c981658ba30f999c991aca746c3331766af1ee10cbe69624066708ae086999a0a3853eb777b3f9f0455cfd98a98c7719710515b97c596d2b662d353a90206e470c523d4374853","prev_randao":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d07874","block_number":"4491510546443434056","gas_limit":"4489858063226749928","gas_used":"4481595642848361992","timestamp":"4479943159631677864","extra_data":"0x58913d3ec8a62b95e52fb1ee60ebddf392af6e1db902dd5bc3f1eea7003130ff","base_fee_per_gas":"48712354854557871613352262057776104244427151172201944877932608112921551169417","block_hash":"0xcb571a3e876c6732a4c11cf3562059c2b8c16889ffb6d1b8d5f883591e767c3f","transactions":["0xb736203ee72088","0xc7dab83ea972da","0xa198c43e69db1b","0x135fa13e28a157","0xed1cad3ee80999","0x60e3893ea8cfd4","0x39a1953e683816","0xac67723e28fe51"],"withdrawals":[{"index":"4864971916622804241","validator_index":"2164897","address":"0x09988f43d11dcf2aa7811c9997eb4119e8f153ce","amount":"4866624404134455665"}],"blob_gas_used":"4858361979461100433","excess_blob_gas":"4856709496244416305"},"bls_to_execution_changes":[{"message":{"validator_index":"625901","from_bls_pubkey":"0x8b772ee4cbcc67f534f33102671346cc3d0ddecc1a81f0350f68dd3210681c9e4bf907b49211cbd390bfadc7f285214a","to_execution_address":"0xb5c15a4371c6a89646dcbd1f07bbfa4e210d4bf0"},"signature":"0x85cc5356a9646f0ffd512b7d2e7d3242c81303a415e61b490d28635896aef1f2db03ae8a1439908d03cc131515ef83f003dd7b36ce480c43f4495ffd339b2b9d1e5461309a02ce193202f27d216a4f0e13f7b47295f3e1a44c8f0e8ae8e1e5a8"},{"message":{"validator_index":"241152","from_bls_pubkey":"0x8183f3f5071394e20f83599fe297dfda37f77a040362da8f8fe926a451eb9cfd917e953c51b81619f8e4925fa6177b49","to_execution_address":"0x10eadb43b24678ab331bde64d7f836af97ca6064"},"signature":"0xa19b99131e621d31846245039e99d6540418acc08844a3996544cca2d9965f8a24cc49cf695bd78959a784e1b5646b480b88aec749a62ea934ed3001af50bb8babfb15bb9df1f3d57abd738f65de02b77398c82302f500218675cd96ee3b2357"},{"message":{"validator_index":"1473390","from_bls_pubkey":"0xa83cbdf40e5c4bdb4e9802d94d765c70150d9926521b0ec4d273e788b83a9f304694e75d2e381ce631b24121ffecc9d4","to_execution_address":"0x372cd043f2dd36351da1acbb7f0a6049a4d05e88"},"signature":"0x998cb975f863e95fd53ee74c5beb85c19b8b3858773432a371e6a3f229f67b653165adf3bbaa6015dec12a1d13cc9d5c080915c55c921fb056fd32e9da643d96dcbe83ccd456b3072dce2610d7b96e69488468c83d26b7251a466571a5424351"},{"message":{"validator_index":"1088641","from_bls_pubkey":"0xa7f8aff7b912b6363efb810f2b661643624ab914b034e78d72397ef84fa04862dee94c9b2f46b872fe852f197f558fce","to_execution_address":"0xf676954331d2efe5b23eb56dc3622d4ce2ee543c"},"signature":"0x94773ae9e3d605ba2612dbea934955e7af438154f7572136c97bc3f858144ab833aa7bf6caee2a2c4ad066d36b1d0c3501359ff577de486f81a5210258cf63ff45bb0cf91526eca949dfee984c6757a957f9c7ddfb8b599d3cfbec0b778e9396"},{"message":{"validator_index":"2008914","from_bls_pubkey":"0xa6c59055cc0bed5baf1a815f59d9d1cbd7aba1a4fb8d83de7310ad85640524318110a6a16f5bc141a81c34e103d9b7be","to_execution_address":"0x7fdbcd4270dd970b44236c31de8ee788b75533a0"},"signature":"0xa28a524424e49283f416545acfa1dc063866bfe9892c60370705cd8133fa949d724421b1ffff5a757d573391ab2cd9fb043022aa8354e4e93c91d126cb40f9ca7f7be8e8293be5a97b0eeb79df9c051f90e4d97c9157efd9b64125cddfc257f3"},{"message":{"validator_index":"1624165","from_bls_pubkey":"0x83190d18858cb148b28aa89911959562dca6653f220f8b4878a5d580958dbb3ca184d97880f7c2bf0fa970cd41b70dce","to_execution_address":"0x3e269342afd150bcd8c074e323e7b48bf5732954"},"signature":"0xb469d5c6626f1c42e7e914ecaf79388360d2ad196f2edc1f7b6088422b4f32f43f36b12898bcccc46c5ed15285ff0cd503f0ba5f6def9b4c1e523e941f1de95263bcdb014637c359464eb2cb974e06faa164827b21ee15dd68b2375b7e76a700"},{"message":{"validator_index":"2700421","from_bls_pubkey":"0xb7ff61729743df75a8b0b7e5b95617b9aa407e2e6a30cd8101c6a4c851b2cc366cb80e68a19a23e19625a596fdd1ec61","to_execution_address":"0x64688742ef680f46c246433acaf8dd25027a2778"},"signature":"0xb29e4c2b22ba8da0947be521fd1710125a95d9465632c3d2b5cffafe6e7070f4a6bd71385760b2b1670add9981225a18060be73e5f486535919fd3577b7ed850b3108dfa0fcc9215cd9d526295616e09619e07977ac7208edc5a2af93835a18a"},{"message":{"validator_index":"2471654","from_bls_pubkey":"0xab0a4039f2f00ce09018af228a696b7b87c7bfc111e7782bf7a3ffb423c681c04fe335152668abc7d20b6e9a9bc4933d","to_execution_address":"0xc090084330e9de5aaf85637f9a361a8678373dec"},"signature":"0x90f650befec00b055e261a38b4ea0bc65a0d71fd735b46f8387f565fb0d31494f90645c40dc07b0f3ee26d7807b82bd914d4d7c81b3ffeaf9a32730ab7cba7265dd09a0e0f94ccdf2ff3bc53d49fe99a488cf7238200ae12e6c59960e71d5877"},{"message":{"validator_index":"547910","from_bls_pubkey":"0x83e4d3825bf069cd0b19ca5072eda2f7d141de02c9e65f9c0733c18252c1552cda074eb613e1f435a880262de2a4672f","to_execution_address":"0xe6d2fc4270809de49a0b32d641484320853d3b10"},"signature":"0xb9b292bb598db604142750cb641cc511a9081656efb8271132d7e0de30554dfed4b16e418100d9085951c1502d6ab657179da8804cb08f1c69b1210ce94bdf6a0b66976233a34a0acfb4b947cdc192cdbb8576a3453e50143e7afecc8cbd264e"},{"message":{"validator_index":"163161","from_bls_pubkey":"0x86c03ea323e3551ef39c8c4e5355c4d3a2cceea3c8acb3d947b39e245d2ffcab53b4479c670d8b268828fd4fee89eae7","to_execution_address":"0x08400642aee83f31d60723f5fabaa1c58bbc1104"},"signature":"0xb58eaaba3ba51d7098d65fbec3829ace78576a2276fd9c97c293aabdb634a2c50f52611f48088da5d4a5b5fa2c5f4c0513d8dd91c8534b50a7b8ae0072583612610ada0c81a261641c66ac542428cedf20f1b954ad03505fc058b40ce0bf4182"},{"message":{"validator_index":"1083434","from_bls_pubkey":"0xb54bda7a570f90c2d38e836a3a256a6a2230a6384a29af7dacac3eff1a981d3f50918e2b546b3d78e72a545870b5ec9e","to_execution_address":"0x2f82fa41ee7ffebac08df14ba1ccca5f98c20f28"},"signature":"0xb851b39a32955a7f05acd7707c6859df4ee2b1472996d6a805a61e14415db550a92a7eaaff14a67e858a9d3633306efb12a62ed84f76387a84deefe726afcf2fb744f616f67d144411689343e6e0dea7a88b57449b2cdecb43cf0b5a80887550"},{"message":{"validator_index":"698685","from_bls_pubkey":"0xaa3588a5cb0b5d8eadd316046b661044c97559a4350464e338456c5b728880b4750b94af5fcaf478e3bbc86ac3e12d0b","to_execution_address":"0xeeccbf412e74b76b542bfafde5249862d6e005dc"},"signature":"0xb99cdab802f2f2683fabc52c8ea095386730c43694a9a5f7a42033e6dea53f4896092b207f56b1402c5c69937a3e2fb41958e001895bb43c2ee1e360da601e1ac56ffa8bd5371b1dcfe85518f297f94c02cd4981a5961201d2c2fb4d2a15c888"},{"message":{"validator_index":"1930923","from_bls_pubkey":"0xa55017fe14158ad9caf1d11f971b71b1941799466d063c6c77d7e41e20d5b74fd7fbf969243f3f507f8c04a5f76c3722","to_execution_address":"0xb1ec6f426f978c599752e0e7181c305a1b8623c0"},"signature":"0x917311e1a5f7a689ceee1af61f06519a3e4c6d68a4af6f4d24da0f57a2246c963c964d0e576607222856258c0e34b0b1014b68dfe481454ceaf521bc6f87c15e6a21f6db1c303b2042d5857ad4506f00dcfdfc5e65bbaf1b4ee9fe7ddf7b738e"},{"message":{"validator_index":"1546174","from_bls_pubkey":"0xaa865744dac51436c21adc2a1373eb6b8d407fda20bc67492d80a43812dd2aedee636192b1fa742570ffc2833ec58b29","to_execution_address":"0x70373542af8b450a2cf0e8995d74fd5c59a41974"},"signature":"0xb875609f4aa01bb03c08b4f13459fa7696b969fc5e8440c89f690478820b8b5b4ad75e7fbf03c4b0e919cdc80b07857604bd81f75128f2bbc61861d0b5a7744e21eb4ad008f05b46be2c2780900a7913abc2cd3591390f29e05e2d5b2dba570b"},{"message":{"validator_index":"2622430","from_bls_pubkey":"0x99c16f59ffb2e2138feb9b6f1804752cdbfe3796e20c52a3ae489f8348df4c1a9614cb6ce6860bed51544aaa1d22cc80","to_execution_address":"0x96792942ef2204941676b7f0048626f766aa1798"},"signature":"0xb9196e6383fe7a9eac1809c48fe10e45ddf57d6ee7946c22d48873b45064a39e66f861d7b36d82699f4b1858c3ef093f13fd758af1ff4deb2b7e1ffc7a7179306726cc556abddafee546ed2a6d7c4b17a1498494d994ff4188a2edf3c261a683"},{"message":{"validator_index":"2081698","from_bls_pubkey":"0x9786334738ef86988505249871273257e40b3e3c47995e751a40a52bc46f915fbaab7e2b1802ca3dcbf2db0567e8c9ae","to_execution_address":"0xb8e632412d8ba6e05272a80fbcf8849c6c29ee8b"},"signature":"0xafeb0dbcb7463673415ae2897857e5b13c4299ee60273bbc406c38f4e805cf7bc147ad40d7873740f3d261bd592574e618efe8f93cf439d13db8b86ff91918c57578b1080c6e51cf121d816eb3e5a2003ad57799d24f1ddbe495724d9e5a292d"}],"blob_kzg_commitments":["0xf14921410d6e44af323bde913793c2037f32eb41f938cabb3c5db5168485eeb88923ac822543db013af49d53be186cc8","0x046b1b41adb923f4277e45bd0b1cd7d08535ead3b001f37569def8de5fe6a543214372e11fa4bbef810ce1ff85e0cfae"]}},"kzg_proofs":["0x3ece09418d9cc1c206477b3f86b61438983ee789d35b6da4f361c337ee08cce3e8a1c4fd0fc75cb95755aa04da37fb61","0x51ef03412de8a007fc89e26a593f29059e41e61b8924965e21e30600c869836e7fc18a5c09283da79e6dedb0a2ff5e48"],"blobs":["0xcb072d41cdd6852547b50f3b90819969722ced1d8ca77847e05a2e86cfc37fa35ae41fc530811a26acc317fb2f89a4fbd13c6876261f3f7aedf916080789e969b18e152f3c9ce7511d6aed2a6c3c7eefc18473742e0343b6ea6965c1e9f240fa5aa4a90a7fd4ce8c81abd7536ffecf3f4d3c78c58d9599280f217e798a2aeda204de9a8cdfbc36868414133bd5f24401e6c476bfc18f14769977e3dab02cce1200d03dcd13c5d92a14b067862f180e96e1f1146e5c017af54a64e68c550cd8d8c43a57e925800dfce50bd9ec8132dfb2f4e0fc1a847e698ce74802072805e4616cebbff8f63a3c45ee4ca2f6c485ae7dbd620e95729e48b9b9ce35e20b5ea1b7342c7494bae28ac5ea53f0c36460c10d3bf324a8f3c9d0850dd3eae4961bd0f6ffc4f077783be568d8f10b0ec275cf3b534274edea591837b4633f4693822f4ed38bffa38b66ea187c2ce69ab30847004c4cbd5acc05319183cbc1ad806aca5a5b85554b23b97383db9238a7ffe5fb945302debc22a2b56dd4aef14410608be16495a4bbc5e2aff1c3a1ab5fe32ecb2ef8827d980b2fe135043818c8aa97423960bf0268c7630cf04138b55d8ff3fd6aafe2d6c1b736098af35349e4e8af8733e6f6c833d5536794281c2141a99d747b5184e45aed7b950187fe9f581a4421f62f804b1f6f7843a5918e75ebc92ae5b398016ecd86f8ce77299f0741c14edc019be00d8268abf8c155f0a596fb36b659a6a3bac4ef2b20cb467524a9145cfd962a5f541069911af19576c5fc40d7407f7f6bea20abb9a04dcf142d067e8cb5c2041535046ca08fa733efab0a0d4483428aaa375ccf570667b9f2c67e1d6745a4f28d8ae4417612705695be23ca538efe142ba3e0850d55117e0138f8457cb7e3e2f8698e0c7f17d8eaf54ed153c537decce8cc5a89c1e8ea9b5d7fb8fbd75ce766e8fe57c5eb502c1de441d47b5adbc54859fb2caf18b291100a270d7b44751036a3ee5db6f84adce47df0f385c234b97f4493c35ba4c0dce3bce0ceb35cad2aa904a54e0184da1174407591a9549cb94e2d82b2076174529dbb3fcac56e5c4540ed2c5118f45726c92cd7fa959a3098f3495d31c184f0eacbc7210e892eac601c4376f7436ee02422fcc4b5e9aab65e950c37c2aa9da9c77e1b99e0083814b483822b861d5d17768264d27dba533bc1ba3b6d8d7800290acc7569a30262d3d65fde783e17472227316d697e1015bf81ce9af142f4865f5d4c376a4169e050bebef066c0f4f4d8fa3ad3e16467eb76cea322cf1f7d9e21169e8e5fd6e4368f5953fbaa0e4be86399eb7d704332ea6b6aecc9ed0c84a6a608e1b41c234dfb1a8cf3b70e370922c3bf5703e1e153a683dfc0dd56aa0e9f25523b3a010833681f6e1ab9d124ec38f745d73c4b05a5703b101dea7b803527e95e556223b059bf8a02645aa6ac09c0c62f85eb366c7360a03e6332427fa7cb7466dc90b94f357c6c0714412c7446fd59c1bf6cdfb8fd2d4e05d5b9d46825a5957001c694587256f71f906c0af1e1ef26beaa7e8947fadc7ece1cdc7ec4054778b7fa2abecf6e14d15ae2d60a5ee7a6e78dad141dfb1137750cc375117cb1fd1de47fae89ffbd2d9a0239a5d92ebde79400f23b755e5e16ccd5bc9c9454265dcb334db1af19a43dd7b966e8493d9b249fcfe80f1815f47f748fd9e84709761f4a0ea7c23f7d9f45a61e60ed41a50dc3dfc7c3be487a558e6cfd544c33ee465318f6d06c7e3cdbbfe995c01db10670b2fcbcfa9da579fc2b796a477ccaa551dade049211002fbc81d368457031af7b523804c94ec420d39d6c9635e964a3e337c957bad69254596ef912526936ffb3a9e739adf27d25bdddc3c180478cea5eaf98e899a2c98dfbf85d7a6babeb38f5eb0882ea6fdda00dc5a47cf0a763b9b9b28c5185283ab7a27716cebc1725c9f050b44908c6e18a0d0787d63220a992fca07ae35605c004804579f3927e0205a83a61cc5272861779fb112b5603146ac5d4ae46e9eb56127bb5fdbcdfdf2849f5ab394556e4c3f10139f90f51aa118cc6027198a1e15d80afd437a1ba3bb361020ff6c167027a75caffe1d679cc223844e84598e18a5519eafc994bddf23c24ce4bfb011ebe7675ee739bad0c685bcb9a49480193876b6f2f37e62b25b7a72fae2eb5a9d5b80acff7a733a6a93ac7c6b440339da4df6498346e791c160c7fd7c91e67572bf8dd086877b3d7d4c9b4f394dbf7f7857fb92ee370da5255571b5b9b24edd383aaf67e34318b5777fb2866d10af8dbacc4f206dcc436112c81e6aa724027af84fb232a4512fa025c3efd2313e040e8111a9f18f248b7fa09256428f41009159af83ee5f68ffaeeb4426ac5981c5ea3b1a46cf2aaa7d67fff51dfff97c9878d7485f32e9a727ddc9342017ab20fa1660f003db1bf99239343dbdff42879e0a6846003cbf3bffd3d71da557b47c5f45eae0a44daa1f818bab038c9bc45a0f03e82373552bb75b732aa71da1c508499a998c283a8f1a0f59844f7444de3179b3de9cb61b41f140b5bff32b1565a9d6144da3137560625be4ba1b95ad6c8477317792751ec3ef8d790139573df08894922056bfeaa6b4fd2398dc8577a11ad285db3e059a9ae86d75def7232a78e568536af95dd9f5e75c0947233e837aeb06cf1606ff47ef82543817fb430cc386a5db1f00a613a5399405ede8003e5e1cd4c3c0b91b43e17094ff513871af0e18b74fbe68178d8705d81a404ef8e8bc3083bd882bd5a7dfdc943acdf25bb2a7c455135314cdb060ec6309f3a44d7f2bfb43ec259abefe70fd440ac7b3273b58f91442277a207a43eccea7a792f44a3158b070c66e41bfaa49ef147639df1d0b3d9076fe1002f8241c0f7ebe038e73b5811dd6b466a2ddb89b7bff1781ef1541645ac359c93a1f20f7c84143142c883f14fa4cbd76a7da3809d4b8e7d3dfccff856b5795ab38aa5d265bee55397113b9ad87525bc042810bc6a363a9bea791b31dd7818ce71101168427de6ce8e7d52e31c7e91dd1b48b7664f862edda684ceaa2e1e5333362823e4d7643552baa7d9b0142266e43d29231cdc2fceb53c4e08dfb9d6a8c669b2590c60a8af30a723a7b834a2b0c09621d76c1c8eee750812a164e329314734af7cddcad960df2e7b7708b5b51926f505255e81d31bd7d0d7ee5d346e077d489e1e03d16ec3795776027f27d87e0bdf69e6e79bbdf1762f89f104d9c9a1afc05bb762d6e5e43bc44e504f5e43262bb61da177a715a052891be8266f541f2b0b0ca6909cb43308b6b06a82a2cf7885ec9c5a6ce4c7595097d006a62434b2c2e6e8a658c56a98e79bab5f733a349add485e629cc29bbcb87ecf44f925d04b4a6f01523825b27e8133f46e5140620d393b38a7d0dbd636b57998fda95c729a1b3a3f08e3e5169ea4391a249fc8f3cf65414bc043be540fd90f1571d37df2389382424cc178c1a5c2aa9312b758e9fc6f5b24ae2787ddfb96de4603a86844aa9266e7e46a598c697293bfe242ad2f142b90097d204dc520c5f1d67aefd4035cfe32864f2fa01fd2ee704261c1d0a779af81a25dc75a1eec23ac07648251577441c040f89164fc9c94215e4c0882ab9bd8116f7d2839087ecf3fcb2bcc92c4a0c6deee66c7efa1ec666527adbdea4776c416bee4448ae536ceb6007d5211d0747fd37b0f3dbf36c48aeebaa4dda5a8d5561f16e490b38293878b19e827f8812862e4c6bc10575fcdca01d4b738166dcfb6474227745877b648d1a9e375632ab0e7f4589d92e42c3214a766cdf00ce267ce1d7558cea44cddbfeca4c2fbeefe378c3281d1ccb04de8fb691f47febf593dce95335a6c67328781dc48538a946b0751d1aafaf762e98534aaead4ad101c61a80efa8295fc4c428611fa7787da30b13cd8fdb30213d39b1a7ce5e61d22a93a9466cdf18108e6ebabd791316d52a93ed7fd9c0c102cfb9b2b195aebc13f486f8e1f2a07dc010aaf2f4ee1404867d0b5ad941214f71e4b8689e0a9d8e2131d4dd393034a478c5c3436f06abb3a7ce4f2d087be49c1e454f17ae746dd192e3d26683ac584d7a22d4549ad94adf95abe4f566757482e518087e9191f72d9d97f5d93fb87b29492199bd491d42a4ff237fcd784d0563135a0bce2ea1a76e34402a46095ed68fbc4bdb4a7ff99fa99643c3cf11f37a06f5119531995916bcff5990b1d1054c77d5a4c4f7676f56649665bf9c4a258b4c318054364ebd46a8a92af1fde425398fe94089459ec409065ecf93677ee439bbd42fab83db936dfd2f63b2bfa67b63e5712f61f4cb64a0f052b7a01b1b7f7351e2e652d41b329aebc9ae12f8cc1633bd4d644cbb000c59dd8e3f46120f3cfde5d139a08b3f75ffa2f495cd7f24de495b627447dc8d7fbf2a0045eb9f651843c25070be3f91e094f6626c1d5528c7d35b135e6683dc01301b1952ea767cec902d5cc4b341edc91a1720649c810510f4570971ec59bdf524576f8ed482c48e9ebfe78cd2e4f2d8742b6139f9959aa27100d94a573d2b436ecec7534310331dd8ee9e37b995e1c7f8869c5edd99cd4fe63993dea0fb5605820251d01ee2c49c8d50feca6b3420f29ba182624460c3f4d639a5b3d8f7e7e61f9c7f17c57ed6bd7c09e88e19295a8e77e216a2def1fa02de887257ff2488c93268b6a8529106ce0d1fbabc0f5153896e738d6140f121a7d034ecdd98894e3a39ebe3f989b600050f53af6a33923bb5bf3e6f54f9f626f6be5cfd952eac541ee54c389fac32fc038fda837f2fc476dad4e0522a93054472d7f5f513d20a4e03f328e2f3594d32f95cb42c373faaae8e2ec4557845a6e82027a46b4d668dc1ebd552aa5402624dd0e193b91b4d59ad6fd06ab5c7854fc9e02b441c798a27fb8a3f035faf9e2008cdc5d7a3198e30a3087fc0d3a76f28c2d614f002e0ab37f8febe55f38bbc3c87289c2198e89750f0ea48d97fef84872551c85a9d5172c360408b5ef113192e086b726e77df0d0640ac4e748d7f7b3456947d8532f410adfdd4b1d3de0c79232d92e3ce726d7c7e72f8b08757594d8608b63fb8c3a21a51ac3b15539f24977ac02cc6ccf30e9da197bd90c46c5bf7e626278b5d45f1ed36c420babe32a89d0bda01fc28ac736e8a2ea111c34f44ecdaeddde58201b4bd0bc6ad8d4d0ef2d77dce1d04db76d65adcb1f03c292c73a88ec09f0e95876c1afd8f34957ac67058d012215a2279f38cf038f13b40143036e86f509e8d95303feeef62fd7b20869c92a97cec162439cab5b409330f99e71b62ca59cc7f38829c21337d75a367799bcd80d249023f09af609205561478f3202501831906bc8eb3abff916111cb426978fb5a32fd8fbe905e9def16c89d902c0ab5d9cbdfb5ccb8b4f6f115e5e9bbfcb5afd42f12360f5b9f762562cb43b6829b6b7d077ceb3ea0d82ab689b89b202934480cd6b5b6c9526d6c77fcc8fa2307e32d1fe7004b75b1ffd2dea2f40472af2df0195140c48d8784cef86fe35b03113a101f93d34f8ee1ac7faf260b8ddad66c17a743a7345891ac4a15dba9792021ecaa10ac9e5aacd99cd420e863bef32c203357894020460bfca3edf02f829aae43970b593136f683f5f9c9015a5f86613c1f8bcade26201db93fd168c6f7f7b1dbc0507d8b1eb8417b1cb3157ead89cb19c4858586c433cfa72bddd9e688b9ad8e759783e213974fd5b1be7678e7f47a23bae5bf9af1b2c9d64663117234ce341915789c9bd1bbc9ada4480a02bebab98c51645f0ee582b7b85d9e7e4342c2de755818acc520ebd4a0de9c85eef7764c1683f6e941f92bf8c99719f0801b924f412a6463fa01c1f98b89830a71b7e8f64d6314d436c8ae9d284130fa147a935026ba57e84db8d0f18046d3b138540c32ac3d097c2b6038396d2ce173f96333fb0484469c52d5f991efd2f3d07172b217a68749cc90d6194c5300e3a61d0b40c335311a958328ea226131521384d6122f2039761dd3919cc27f15750a9aea6cc747c596a477a3358cf9a014020bb9cf78555915ecb35f9815e8a9eb1193be6ff26762c9be70a6d13004a658720855b96162267dff6ae36f00715211d69574a9b5f37716faedb18011bbfa272b1e65ecdb6b4a3655d88528ce8d2db1147358a55f77c0aa3d25a5f1626fa079935a979faf4594698ae59cba9b0a288733cdd735be4430b2c12e60d1c79de56d2b5b3ae3b9fe1be9414ec9fd0cf8b2f9faeab72e4394ffb4ef356c1daceb4dd330d779f357821fab251c0a171ca3747516dce65614aa72b472570e0b18ea71f68d1804ac93b727e0ea98797e2b17465ff1dcac63fa7171bf538736623c3490d2392eb1953243298669fa72fccb1b57f87c9f61f7b5eadea44c68e7d1e790ccdb4e0ea414dda41962702bcc273218fbb56cbffd276e33de23e3766e91233ca13fdee426f6f598616afca14e68a2a3dcf6963632fda85de04f4dd944117b23e0d417bcdc5a5df68621d6431ce6ee21df2c836f7d868366bb5b8ae25fcab9689d4a52cf92e796b56db45924b8101a6315d611c627e119f036b5ff41848e541ae7c517a48f8c2a83f7790b8ccd986a39f5b5eae9fdfac408a756598e4b705ef15a974b5cec1c56b1a4dde87d2582fc131eb633a7f24275e26d24325efbbd3340cb6bed4b6bc05046031c32ab0c83a7ad6ec4cd525830dfac20f567d18ed33fd4171fe2dbd397ac18d62a6df6dafdcdf1fe54e898836ba108e43095d2e27d2884962f160b587f2ffa3459c9043914ca6c34ca5cff4c498867d0d45237673e3431da383540422c8eda6320b612103ac091b7126c989963cf7c5ac400729c34a089848024e222f380c20516d592a8dac61a6e463f3bbdb43af632436d7f379aae8fa5a02d81f4ffe189f84c151345d1f6e877f386ad0c340a0946fa59776c82cc7685efe641aba0aaa33697dfb4ac9d8c102f0e51f6503230b5dc619dfc5d60cca696bec57cbae6d45f15f12742ba2913403db23d8eaaadbf205f2a296e9be2a376ad74e63fa30d22872ab5719c855b0ef968cc21c214d352696b174db5f57345f3b6d81fd05182245549c6b71c9e1237d7038c3d4a3751ad55f2e5e1a98cd2c08913545ff5f6d2d2cb8cdddd70996ba3a65ca07a15b6fc43274800b2278105d3682cfcc5f0b1b6fcfc271344a16a79eb7ef46fca3085b8c9b2c4343d5d80af7490b59cb6725ea48f81dae1e1eb290e2ccaebbdbe3c0955812a239925a05aef5268de747a359d150537408e6a3554847b5e2bf7209686fe0ae360317233ecc03844c9037506a9389c1fae2f0b7a83fa603f38481d73c3140c309e05fe639c3738cef002006adada063e308680e0b37a91aea761f594e74971484858cbd9ef691b803a844d98b968b7ce94a519410e0b9b3f56cc26ffec7ab7fd10226dc8b1adc1e786d204a024c1edac82bb199fd4f5316e769cb1b87a8c9c760b55e010e4ee78722fb2de8253275ebc19c85984f421fdcb5ecba9472c55d1b644daca0ae05ff3d399540ad98414c0e1bcaab54322b8981fad06e082198000f54e2edece3c5c6e8d982efbc14869de59df702c1091ab18d74bf0ba3464aee6c6a17906599ad68c48306d661e9ec20bd166d5e8eec6be8cf852e745368ec243c27506141b2d01a2b9be6a9d877638716d6721889254e220cb2f26e9a65fca2cfcdf40717816205b7e8856ab7b4458b7e325e58f2b2100273285fdf94eb3b8083e4c64c6f4fb0d339168c2a85a91c30b103ae5dd2c748716d32f6b05bfdd6da0ab645ab81d9eb7966371e3665f5accbe5273762d949c3e796c4a958c674dad2d6d222da37d0c5f9503ea7978e4855b2d4ff60e5c45347cf3cf3ab0d977bfa0d2c8cc63db958d82fd783303da7e9bca0a9f0717267b528ad5c7bcf1b1a13a97df17ce4762b89e1f39c424a400031d23c9fe2eb336170aafbe53878f14b8d7c6120fb1f7f09f1cd15851b84121f0e194871c4f8e54f3b31ddf7ad1bcbab12e6bf02a9c0056f11930178937590f581495d2c06e7f9bc014613ee80511efb1e38cf1aea76fef9dbd913d892563da9203e53231db6fc586168e353fb73264c01fd08a3633094703b79e93ae42729d095329ffb02b392232db1d33e8dd034ec5d9de11382fcf12d8bc033deb86b43981c5250d3b3249af7eb4469264768a6e8ab565e01f40094741c92dbd5246e1c6409967e7cb1084793579652096040a127b68103f02d7e1229b44d7d209e1d570640f446ab81df499cdf78a793b242fb4f1d29d575e3e398240b8b87a684734debbbddaebc2b895350250329053aaeec05d5ef631993f57a424da486a7966ca92e080d096ebe31888a8141abc99b177054a64720e6fbfe99889b1067407691f4d0c8a4b575ca4db3867db812119cf97175a9f76d9a9e188c4e898eb8842f0f60ff329e576fe4a64e5357b78f37a5d5318dd0f231a9447cb713a11e3c1cab5f47a68d87d9f62678ae33827a017a46e476575e7ed40577e9d3c8dfcd46753c822f55b992db1eac9ae44b251eab45b1557a376ba8f1d6061a84ad3450c36314c444d1a7f364a817c22cd09a60a46d3b22a995610e32b98546f986046bdc73cc189991dc8ef2820ec9f399f034b0b3df6a9ff882f30531ccbad2e0a72f9a4ee102a5c6f3f73c25b90dcc746a25635dbb69ffd05fa73c47747aab3deb05fc4d320ccd7c18c04f4044dee0adfe85c30190f20582623cb2115df50799908659c584ceeddf8d14cc2762f5ba70d776cd564683feca458e8d162bcc9560cb251492fe8b30a428ad700bc7066844d4b6848966e4f8579998c80dc4a4ef16c4ade33fad57aa1ed30a41c7ad5b578c06467e969e5962ea411dfd81d30e57261889476b01ce87a60c06e6a0507f7d4836d80b2c4162643cf49aaf364978160657dde6134e5dc76d62ad21c272fb90705c1773dac4092b7cbe939207d6317db98775e93f4c1ec41efe7370a1d824722e29b6dab942c874fe7f237c297483a7db9f7c93a8983c133217b031d3caf70fec7e52d738f9f20d926ae60753ddd084df0d08ae2731ef751e7d69f1ee97045ca1f327c279cbbc19b0882f1f5810cecde731728644fb00e23a0f78ad4d0f6c1f0f51924380bec64a9f338fe130d0c3dc9edf848e8e3fe9e26845f22f6707ecc29b1860bb737c406d829ba9a73924e499c2e173e76a2974d563a7ebd87dd9fb5b57faf47066d498e434445d1ab7d9aeedac73a7619949fbbfdf53837119e1f890445e3cb56437befec98639984efb656bfdc556bbfc132ec9787b42e28a01632664de6b600a3cef3e7e0c456330609225ad7df227215f6a8fd2e5cae6531e294729c99555801c1b02ec561451261d98de41325f6a93e9a4e22c69d518d7a005daeca20bf68e36d21e5f3730f7960e5b078af2ff0b5ad2e1a2217780ec25f6ea75ae9f90ae658dc6623557cc8dd253f33d47bd907ac91d3dc5358eee771b1080c7f12b5c08d345dc0f54b1cd02accd713e7107d1167b3383c165c6342034e3ac79cf64ece7eb29cabadc1621f775a5b477473afe7d666059eadb479985bbe936f3609da8e38b284a2d76a961a0f8c64bb63b321e98fb53f6718dd02149fb9e6d7b86dd53dcabd3d92db062cfc8da5e6d97dcbf1935a3a26a1d913172850e04e797a75f4f4147e10a4f438b95674561fab54a26a7423b4479e8a0d1fa14df204cb57510adaa6bac63283046618b3e0693c36fa004e5fa15001c5e2877ff9a7a356de91512b319b2cf9a0b0448915548bbff22da82fca955f0989d46bca8a1a117fbaa42693bd114a002180ffee1bf660fbfa274dfe13e29d759384bd79a81a53fb2d6a36582fe1a1c7925015daff26388f44e826f63c419f66c9aec7b05219bdf8696f7d4b900aca187954b3a6c1b86d0ee5c0e1a4de190ebe890f5c24c941d2bfc5930c2032edb55ee48af8df895dba7bb509322057a60eb9da5c1384177c783098ef467010c566560b0c7e7bba4b158e36484c307ac3b8785adc43bdbd6bbb5c8961ff35deff5d457b37c38283c1b3c6eadc3984bd23b07330d02ab93da1e44ff743f15ff5732e262a28fe55ea86f4e66af2adad8585a52c69c95e4b36599c5d4f11667c5aaabf38cf41331f230c9d3465a06661de5fb6623032a1437dad85048631c9b065da219c521f0effca3890c0ccd969bc7bf5f6370d684d116000ef9361cfb7f2b417d1e648662900adfd1f9d4149105327c311e1ccef80e1dd5705a056311d31b67462734de797089010832491f9dd4a990fb4eefd8f05795b2b5363e5af1e261ee2be49b8f9784c1f159c5346fd9291af2ae46eaf7695f77c79d21b89ee201303bff50cfb1f54d894235320cbe5431e6cc1e38f15474dee45e673d147f5ec8b13ab80528731d213355cd694a99930cfc238410892ab8c214cc5807a898952753c6ef16074771bc6cc899e4da360f8d6a76fadf26b8eeac051e3b342456e84be7c06386e20ca32e0be2d3c60130b4be038b02d2d3ac64bf91eedc9c8a446e4fd93b764360343b0dfda6fc14748e6442d62800b627287bbcbdb98c3a457372bcd0cdaebba0bc15fbdcc157f8919b727887d2bb17dcaf29755d2ba033701cf2df734735fb1acfe07ede2d6913f52d287bd891e0e7793e788767baf2f7a8a222ec311e5a5daca0a0290fd97fe6c4c541e9df7f7fb4e3dd056d92ba5e6df489e7ea3e380ecdbd352c0d2616284483a46f80c2fd8c71be1682d914c6462c8e715d802b406895a0a0b0825bc3b7d54231d687f4805fd1fadb3469a3020fccb23c527dc24922c284532d410381c2af79668f0a4e6d352d0180759f054ea23fb788c841165d1910ab29f4c3e22fa6f6e1ba26e27bb6adfd41af3335dccfec41ff4677efe76fd06db4dd2f017e3400931af31d9197e94be32c626fb772c75ed2a4d1486cd270ee0b31bd5870d4a935f6f98d67edefcef83ed036c075b6ad6dc1e219db34e34abbfdd08bc06048c3ebeec409ed9a4c0b805aa3a8cec0acec134d949f02ff8a33a769d8b20aa0aa1a8e8dbb140d56360712213d2bd778027f4ccd5bb9442c1028c449b90bad70a48ccc18aed7150e0eeac467f9422cec8ec39b7042555b0ced3a47bcf4ce75d0f99219128cedcdcffca50dc6ce54b505e4b18208e3c1b6efc3340be4a572020f5ec12c4a1f47ea7f06e949fed60c69cb45660de67140f809d3170d59588e47b4d4268005652e9c65c94639f64be9d72ca3ac48c6331877241248f1280c2c0cb7d8dad43037d90185b415803ffbada1216acbc4ce35a4be2064757b6789486d03c4eaaca5377fafc9be4927307242fb175150169a585c0b4b86e8174f65a75a513131cd7e2fb32e01e90d198b64c330b36f0a5854002bd17ca77e53f15a89154b525ec47c54d47ba514fda8b80f5e1dda40aea7e7fb78370fd1725f77d945b603749d607a26c88777f9a1068f9ce2b51568d9a3387539989d8b0f94f2e942aabd2bd134f1d6d1b6e94355cdaac1d87b3f544df272fce702986986eabcce483001e654168e6c48615a26a8785728480b801941bd6500dbb7a561768069feb8db02d28fa3d51421541ee748e7075654919d1c9c30f04585aed7221d18455cc2135a636ab8d855bf505b35b6df5e1c42509a69c4ff7d304112abccca9866d0469307e4e4faf811418ab24e2588c7d17a1ca3cf6cdc93ed08c2dbb4f28a4fa1c8eccbd0a250bd8f882abb3fb6ed9dd4a7e04d1c78026cd50ed006251f9c1c316001cef93b63b6f79ace4cc9707fe71a0f1a281098ada29281021d3a212146006d8991a1892573fa85049ab48b939bd0ef5a95e6c79ff2e2e754a54f118ff0176f913d14f3478590e3d31aa02fdf7b46bcc6fd8a239d01036874cfa017ffcc3d38f73271f2c195063f33478f3d017db3b4994cd5e5f03c17ff45560980b089936ef0f2a0a74e89c7129021c7a0f8be8a94a0be6789e5c523255d32104f85d2832c8355b154edb496d24b91c15aa91568babb0008214d6e659a861ca66982e11e640b10120c61222c7a398d41c75b21d570629e8dfdfec41d2340e04164529f488eb88b6baeb2e464642216d28d3bf768c41dc0bfa0503224c73e7e4439c4535a180f0c0a93aa7c79c343013a55d95904a20b3abccda024cec2e51ecf7949e619f5662517db594deceecd92ae94a9812cce5ceac140d04c00b7d0d4740778ae28b069831b7d922022e063e8cecd8475c6a4d7677839c5ea7ace4e2f72088bd44b6598ff828b6cbbd6a2a033b21124f3b47655000f8ce829b37ee29c5493df413007c6071d16c182df328fc492f2abe71ac94407cd5ea68dcb42c29d3b8278218c739b54d34bb22fee3c31e4dfea1d7844b02574ed12f06d7d2f5ac61df97af0bfec13f0102f2194457cfb7ec0d1e19978fe0dd1df18bc830587c691bb6db11e59b3fe8b9344356f92fb559f502c443b350528794899828565845b042a6b0a3f18a47e28a121da515a531cb8230af5fb973532275aedbfd38c6d1e1e1fde18c85cf18b0adca33e93b21c20bfd5a28a75f502fcd362da75353a9c4b1d6a729064184972022fea55553133a717b519fa13e108ede4a565a9a4a895d090416dcdd3abfa52028cdb57994a0de84ccfa3227e279c7eaadddee42a5979cf3a517b4d5a4ffa9e0477253d648b66919ceacb408e7d9cbd2efe70b3cd8b0720c3b11c31c2e8f19e42972147f364b3425136b77bdae592cb531322278b3940cc3934b4c1eb70e9edbac8fe78007a5a10edbb1c59cd0ee75e4254ac5666a1239369abc2e6ed4a73fc0768df1817a07b0324db18464e2b3c4028c603d883ba6d67fa1c82afc62eb38e86d25b31f8062198a65a2514fa07d4ce4608ad4a561d3eef6304e8cd9b89083acc54fd130f38133fbcd74576eb5e2d79512343ceda357e05b01111168b759fed6d01d3d1f5df2cddad9ee78313acf0481c76ab89e5d07f380d0e3c050babad65162a37c20994f74c00360b392204a5559a1980247e56be4b31d0c438db0dabb7ec74390010068fd77e260df8feaf944b2c562a4c4c43e5b48b6a03f184b767c94ac8e6ece95744bf15caca03d18ebfef3e04003314b33e1e48fd8e966ba17bd8c386d5a859813fb87c2bef039880b79439f2770c430e75e720d626326d502ef47ad5f19bd6f9686cb140eb83545bc9a5bca932928b2c23a301c86ff112249f7fc9e682f171183af7410a7c75c02ce31f302e2682927e069779e4c6a7512ba9b23583c719cbdbae696761a8ab97ca3e13a28eb41cab10c73efe00a64adaa0b718b94a5545140c7847f07ecf5f70f3f529f564d6a6a37831c8fdb17974fa70821157689bed26e9b80bfb84b8ab23824b185f9ccbe9a1006f96c1a553e20c6b745103b2864ca8d60f9f80959212c675a32f3719f6b20fa140bf208997f779004f5ed86e4ae23db865e6ae3f7cf62fd0d9d0141c9b52979b818f02e09b3a48b379159e3dbef0d1ef0334134e781f168de6ef3d7a491f30c93e03b17b3bd33c964d25a0d596ffe72fbdd0943fad3ab233839765058cf13aed7f4dfb787f128c809792d49bfc59019489793ff56b57fbc4d465babc60a6ae9f785628f0dce919d77d345246a88e11b1d2dedd5ec16b6e08f30b45a1a69e54a03637b9c0fc6d4a0df1351708bbf10290d025643046cc667984ce9647553f15a50bbffb22a758623fd0971ffffc194bc4c4652aea76b806578c09c880da7254f5e0f16ce9378a7788f5accbde09618f10952bf7bd1dceaf98f6a8c6b46b245e645b82eb9bf384f1f56f9c96103967594b43f9d91a3f920bc963aef4c771c6e27c365fbad6b0eb993a4be21cc5bb90db7504c2d5e988eea30935af16a677ce83ef852553978c0b3d8367cf72af8083c53645705bc57bac0bc1de5692cc2c3470969ca70861cb51da0e0e06cb64eaaa282bbe5f94b564fd96b516e1fc6d1ef610a0306c79639cd0646c21fe6c9a4ab46cbfd6df7c3bc5f5a39688a0f908c394c5b2f42fd20c2fbd13800284fbbb61165482f9b5ceb4f60c73365a79688c5594b01b465de0b16ef342b285360b94d7851024299abb62ad9e8ff6ee67e462c6ff935ac09af435c370e3e99d080901bf7d679319bb779d7e6cc82808f691ffb9e2bd9c7e3c95dc68d67b34b5c370f0bbbde6ea4c3794feb419b86696c93dc625ce04f993ce00af4cebb83d0581531a50c9282038a5298c8caedd214e15cb689d59d47ecb8f801fde198ef8a3332e37286729a73f8bd22e681745ee5723f213cc9b496a11f18ad4f1efe51b631b10aa0022aa7b70e875fcb0a4bf4943d24e05e6b25f0fff1ab6c5a49ee254135211d9ddcba64236abe121d789ace1b812bf64183e2c4ee3667a142a7cae50364319aee1f2d975f33935755b5e5199476a117b1ee52ba249e3c44a9185dcffac2ef5f9fc7151cb4ceb1bfbdf9fc2d20d7203a02cd09e1e72d359e9ddd558b579a6beb318e2124b2d8dd7e8fc2d5cbcee5a9079522df5e9642e07703c2fe9024014924e1824d0c404e11aedfecbc9a479628f664fab4b0a4c1da749bc793d75c631959f43daab97861ab4c9c2a29f97a760a22e9501f8948680f245b93e909160904d0e3ceb0dfe3a61078899bd82c4193d7451de6725937869238b0a2ad7b6d11247099e03884f895459d27f8ee1729178478f4e1a812d739f6729209283c4eb6f0af9d411caea7d82e93bfa673c84f0a1d4848dddf6f0301c23ba73ead2a8e6b4632b75c7a6b972f0dc819702fec62fdf7874ac737d3e4a8f02b5134caf3830bff870a41e2881ed97f80e71fae39b5e6bb496f126f337b6dbdd649cf81ff33dc52f67b7cfb36f7be32b4c014ff7afed13f98bd4430abce4966aa6a4ce4992bccd65a82eda3f9b0d35eb874ee614cd3cb8c8c9909c45433e8bbeda7fd5ff8b5333755cac0397615e625a7eb8070603a2d0231db40405b0de39a2b739b8e3e314268ce34f50addc333347ccaddd28abe6e4479cf8d6a1633051b3dfdb4416f19b295395228c04dde7e3f3d62dfaa8088513bea18a6f9b2aa53b576c60ec77f9cf9663cee5c8201c7aa4b1f242d04787c78eb9aa94afd3d12a927dd32f4579c439160ec69fd40c99951a4bd28f38b5da481ac6328c7ddb7621a432d3ab8a7b87dd2a71e6b9caee505ede3e89a2ef77803a91c6e643404fe589f3014d1419c1cd94a6dfa06084acc590fcc9aafe146666456143c938664393dab6283b06f3ed3df63097f81921a0047975c24fb7acadb7ce0b0922e9e0c00e6a589829f040185ff3c5c63d35845c1f8bbe410d011e6d12e9a5165345a451eea5f8c8dc1ac968bf7635e5b2cbd58148f33f1f265bb8dba74059dcec732a9d965de85b10507c4b1e77a2571687fea10648d63a81ce280209dfe4c99b33f01467402f8e77bcec2f7c2605684a482e352fa551972e480303a2b9182dc91ef6b01f4f077b6965cfd9311ea98b95155a9e9bdaf744854441f3259c5a6009f643bc91685f2ee36d7e4171abaef9346db73fca05001c24d7bf426d262449b1df5eb1ed495987c9717804d2a987913b36429fdde118475f9bbcb4884384262d3cd5264053cdea9471df65cbab7c11f70e389bab55e09de43f5ce80ab69baff247bacbef20c5c5d023f9de3d8b299d55a59eaece4b6dcfa02f2871ca132082284520744e93ffdf1805b03f6edefd93051bba45ebacc75c2f518c7f9ed1dbe05c50a3358f2d6e7e7e2a135f1438bcb7493497229b686c635ff55819d8cd2ac2e554dd93b3cdb9557fadd35b5f8bfea2bb3586d1b0d5ca3797d440e2791e7f2ec4cef2bc419fbb4cf883c76c1187ae8f76e2982dc556c8cf206a5c7470c6dfee242905d6d5d2010fbeb96841153d8b6d640124f5401c3efda813a8484d5b648bd891c057e0144f6fc54c55f22ef6aa48a72d476e541178787b1a852f4693bd995c0bd17a23f61c9a0560d181aaa3f2ff587b5f1b8f7db7ba453336597b29a95b506067b4c17811187e1440ec29fb9e4b1ba7907f09a9b0a86a1064eec5847fad6e5cb942efbc8da00a1baec8f4eabf13321c3ff352dd0b0d315a9b389113aa4467587fc9985824bceefc47796bbfb816221a99889571e5e256bfcb5a805d253e31b777fe903d1b37432ce0eb7f04c0df0da80d00a22c4e70d3d7c799e26103fdb06ce1651732ee63af286dea55e8630d4bd1ceccd52bcc30d242c93438f8e958cb06041fcac7cb2d186889fb6ca602e8e9db52290daad78f30c0eccd498e921c40fba7c0546d14fb2e3ef1d2f54c4e4829c7d53b13765a730805b16294cd4ad4e52d98456eafe15f0ab109523500d1cd1065d74a42a4389611b27bd40b6874ec20bc2909f8fd7ca03204551817ba6565abcf9a01460bc5b2e3211301768a0520b01badd34f52e303ccc601a45166ba1f9ccfa5c43168497d63bc246be0380637288400f726e449f6c85c5a02be25d557070743165d9a816ee41bc072f2271515b96bfd7a70b295fb4d4e5310c5758b290a59050bb8431e5ec997d91d1e7ce4171699055a3806e171b123014b04f30a383a133cce893e43edadb7c970f135b9c59bed7ecd753dfc23c1371f308c7051c4e7d38036cbc50bc162a9cb560af9edcb0c9452aa2a1ce9ab37b58b2e5a27b3131c052c6a8fe1008d46137f1a6f496d4d80b16ccd619a5ea58be7c726f0d359268e09df932ca5a5edc5836a6e4d513c815b0f3f98dea546b08222797b2bb2bcbd07e6fca61611ee120c4e730562bfefa070763e7d8f963f4332c7ebb1bd692eb3f4462b349ef6c33a2edb68bb8a542f279d9c659c15251d4e58588ba2e827529e163fd64d051081fb5b56774d45453b5e8197af6ff38a65ac35236c396055a0cb9601ad7c9c9b785ede21b5a21caa633e2bc09d873414d36754bdc4c67391e382f02622c4d92c6ecaefb19ba446d88e9cced4b657a0d2d4cae37a6cdfe0ddba9d6dc1eeb69b121abeb64183a7f4a1b5a8e4ee0113ca520d43940c63cdf1301a6e2fa38a9b0aa6aa5aed552f63182c66bc4c958c988f2bd2131da74c90c9f5caf4fdbcb7a0c9113ebeff27437b9f3d427948cbe96fd900afd8d571eb6dff5ce567576af6216aa56a2eabe3c69767aa7e376d7eacd5ad1ae3d70d7dab12ecc15c097ad411fc5295b8126818a50d6dc266c4e41eeed7e801e0240b2c1b424ad3724d625390f18d1b24bf28df3017f67bd670977ecd822baccd92dbd16c170da03e51f7d8451f2d4a9e0e37fc9b9cbc65bf6f194de91cd4a00477613bf5d58d232ca1dfffc1803a848bcd3ab47e8c584ca88475a7e9731129943b4604b049736e7c4fb84cdc84637e45f4b22256c46ce42a022f3cf438630dc0c1bcdfbab7f413c25aa88d31b2d2dc96b16b9f028958b973181575396d199b7d943d71d65e5a1564648a099f5be9ff4145bb27468f6f16c473c1f167dea4acf8eaa6eceb87dd1f1fb7265dc964cb5579aa8fcf4b94fa9ef32adc5e35b3ca370e7b6a7881264a93839d377a9716ef92681297f51d8682cfeafd7e5fdb070d75d31bb3374909047272083239b3032388aa2e0d552c3c28b126e451cf979ada0b0c38acf5929a510da5850c540dfdbbe8cf4bf716ccbf0a9e39eb3a1d2a8857cfb41055a6d4300efb27867e6d5816dd37dc3bd9b3b524fe134865bbdf5818d17f7bcf895429ef25a4d8f471fd8aba0b40ca102dce83f114398d67540d6c9a2eb542692ca0cf357d85eb1ca19860d081ec06a7272d3718bee3d6988c74b151f18ea05097a2a7a2250f68e09a0ed7eeef17f0ff93a6b47e79ae04507128fa71950660f8c3eab9a102e8ab3ea746b4a1a1e85c6ccf15431765dd8d02ad971b2c66c90126cf45ae8d3f3ee32a4699febd359e148730750f8924a2fb028ded6dca164f3514c533546039b794b242a17ec65c4953ff62ceaa68e5986b8f902d4eddb27a6265df3b1ef3bd831574942446604fc24b3382fd8600822f0f305b339f77b77324e69ecea2cfc217136589c227d7ee56a09afc317da90406ac652462447b678dc91f6b071990f5060ce9f18939b2c115ec008aaf4ab30d3e1da8fe57a8f5ac02d3baa16ae2cfc791d537497757583e4f3990be900edddd1cceb48f6a368c19057ab13fb0ffabd88015141420a0c415e38aab0434cc5d075a412d140ce1f8d71d27528ec696d3dd06c7d1ef7df84a70335299078bec7c07f38d85c8789d96a027fa117e7b71286e83bcac0be1846c4e24fafdd8bbbcba4a62da421136e3fd0239462f99a67fef4f2b176f068f32bc7f6160265b62ea885da6e689fcfd31b196ae0f24b84051912ccdcf676a782fd3e1415322ba470b934b9c83fdf6aa88d9e42493e8e1919deacb2330b141bc6e55d90218affd9418976fce16edcd98ec121d9abe050281bf2c39695ac77c66280637c3e8f1fd5ceccd35ff1ad358b9e848e71fb4775e1c334e7328c16e25f356ecfa986f0245d9c7e392659b23644c07a3e47d4b60f7a41c0b8b9dabea97a53987a7594f45a7dcce8031c7bc6ab7c35c81b1e58e893ac2c175504eb5833cb0d31217319b969ccd8861985a31bb50ddfb887a613bb8b4060a17f2e74f5e7b7e6bdc04ef5c44a4f162ffcaa1c56c2f677dc076f646ceaa99079f2fd83ba1e42f0da7bc0710d93df22cc9263cd62a487180bcd88c54bdc4ac6925028b12ecc6780820a2a025b6404b9beb7e04d4527ceb25dc088f4149346007fadbafbf1daea96e6382a5837e9cc1c5a9b8aec6a2aece91953ada9a9a01db5b05e26b016b84949790e1cfff50cc18a7456a7ec4374ff96cd2ae0a24a167a802b9c0e8edc44f639e6eb6bde9d286fca2783e9e7edcf8d8655c356653466ade4090fe416b8fc539e22046b17f0a757bfa7b1e16b3a7f9d3ab59bfa7d46f102d7a20ee6cb4a72ea887eb11587709fa7e56a1645cbbeae02675d914b703d2ebb8b9b9a159daba558bf307090379675563437b9904fe6f6c937c70ffe77592d88e2e97823540ec79f25189700ea38caa32b1957d1c7b096fcf7ce57bb470ba1e92a89f6d6a1ecc7f09115133d404ccb60c74be615e426f8a1bb7edc4056e795f011fb677affe9ddad266138c67214e40f4c4dd0f7efa6dd6ca71f948089e37f8112d9abb4242efe7b1c76b3b8572cb8792bd5ec67b5c65cfcd7510dd366378c5ac9058d32d9c7db7636e01bc97e6164780e0270817b51626848ebebec1d38c6f0aaa4bdf48936d7909dcbb266b5b78064b9123433168c34a320f1037b63916abc801ac3c8a05d10c555307b93027d7d2bc97588f2e6a9bcd784c9ef05393581715bfb04d139f7d68bd5a292fbe92b49ee39e920829c57bee141fac52cd145da4b53240f5546733d8dd3da5648959f6d7f4b89a841915fdf50264527348a3e4aa094d30a898331348c88bb0ecbc2d81fc70d8ffa7ee0ed231d47cbc72565b2a209a261736352f65182096b794e632a337da566f9be9f475aeabce74513f09768f7276ababb85baf3348f6f1022d804e52e86e9de37d212b08de42c19370b17ec3471cb5f05a0b1c61cc0607e3ada6238063c0bd1d608249eda1c40f44150a9d49649a8d031f65300d7366c252a2224cd3b6d18ba2651add2b98c772265efdcd386e9730f422e6d2021277d8802bea2461da89e351557f946433bf2d2f492985e65c5bdc00c1dea1ce55e2baaf35e6b8616cb4e908d058ffbc30a6518ec7624f73605642764eef99c628127d0eb4730bd4af81c48405e51c2b647869e2f92e45591099473577920cf04bb5c81994e13271ae06e4b300773a3414b63664da474d66f15b55006f92e63d013fe2762bf642f8eab9dab7f4f3213902e77d1d151c2fcc467539f2738e46e15b66bd7a8f6a071de8f8c5191a5398a90513e37cfe4f75302de796c0677b63a6e8a721cf39698ea3478346f359252cb9d146d6d8903820e63151e6ab9720c397e5c0dee7991483726cfc080e3b0a546f161390b3196028bbf37a063f2599c0612d34113ab70058a6596ede3b4338dd2caf0e1ed2c707d103decd5c40e4d5623050ca7667d4b541efffc3646945f9a900fa517f789ba0cacdb9d78576ec188301110ed3cf173001c4494d3f2de32ac065c4f8d05c524f2565a4cb9522907a43a5a3cf633a9a617e33fe3edfd5debda74473a58a23c9721395f5fee851e265d5e6949f6589e0ae325f012fdc37b93719522dfcca76f4157c4ec36a84697a1a90973b56a8a9b16e63e8909ddcbe7c32fc24d828b40a047c5d9efb2f068dac2d5776ee9033306e6f2cdad9670e8385dcb59da955d8207a9dff1d810e26c30005512d002afb578b715adaac522f658c77c778e833b65c02a3f64651ecc5f94c5deddfafc9a35197a915ab2485257339d7bc26387a45fb9a6df5547f55924b78c63b03d2535717393f5c47ca0d281708e76a43756093bf330c709fb9f732299823927af273a936f2d04c24215a77d355247e46b9246f263e480e9522220b48ca2b833216198c4205ec40618dc0a58c0a205947958726b3ca1ceec2814f8a78e8ea24b40ff853dfc799b7f034f76fa4670043eef44e5267b736b69134960cc6f540a76a8f07cd9ad3ce0f04d49c256731ffe5ec63c2b2b7237878a2d1364b82ee603a95b5015f02fdf912447caea18d94e91d98a12f1c90d67a64f524115d1e7753451ff1e541a9b47a4fa0dd11d3d5dfa0167d8768733883054a44062d47f3bf73ecd86376c2107a2543bf060ed5f3679bba0b4941ba2ce299eb3a8e341d5cd6f0e7c9d9bb96a454c4555982d1975db26271b0fb142c3a63e820775af5b1c3854eaa989e80423ddb93a30856ea18b351132ef7273d6a73d659c97ae3e1d787dde5d63f21d48bda2ec29757aecd2bc5b4e0cd91dd9eb8cdc9868ef9628e31d46a60088c6389ff8771f75618f352d27c878fc65b651c4a7981be762c6c3892d6373fe71ba8b8859daec9f94e963525252bf924b5351f9d4661844715233beeeeab5590dcae3a06cedd4697382f97ccd9398bdbce3e16e8aa550db8dbf29adbbc7a4a4b3c152f1db6a66e1f395a7b081cffc860c2fdf195354bc673aebe7ba22cea19918d1c7bdfdcceb505ba654a49d04dc49d8e06a5d2880bcdecd1cba9dde5d99362542e44af58c885bb430b50582622ab2c54deac2727e773fec60e11abbd20dbee77f376208aabbafa5b533c102e0c38e76a108228a86bc05fb318456c0e07e98992b83fed9e0224e0f249a4a51a763ec52a62292f2606efe5759dfc1617df07f3d92693face3108d668587f7f7677230b82eaa628f7d9ed96106e9015c4fe56eb590ea96c85f3a04e77f1a99fc90c60c8b29b82710244de8b52ce7eb783a84a1c7b1b7bcbe753964e86ba629b743902a6d17e49c2457b99a3eb3fea3108c32a4e10d74a376dd38dc73885cf23c2cf2c05e0790e2c5b7f270554381830199a91ee633d19ad77dc98c2393b781e7159936d9bfae524120e477231baf4f3d644f0e6ff8d2d5b2184c15a9455c84d39e1004c5c4968a46da9ed6924768b0611b809c329b39305b0a28487209891890c2cd2009e98335099cfbf1a27ea907892fd75274dc59a7a60ac51b7ce9085871647793e0a1f68b8a7669725e8f1d8b285832098cebb54177c570d3adcc9be33bd2dfcb721ab2ca42b060430cb5dded651f9c88e00521a5cea08917b3ab5369923b1741fdc28286efe38c846797140af93b981e134840591b0342a0eb316505af1b88f74d9820971c5095b102b9e3f301453337924ce767a115c178592194eb9606b0ea3fe210084082d67cf16684388de5c1a5827818e1d3f170f1fe44dd830d5c84771970f4287ebe34a35ac2955c0d76346f0f61db794d0a4b84088807b9c7ccda3be57678900dd4297048852a4ec796a19caf49a19f46dcd31bfafc027f1bc392112d65791c7cbdf958349d301ed9425cc7a2fe5253e648689fa85ecb18570b8d27859699cc2b50bbebeb3a262bd36dbe9ea9fb70e6813a245c813ce4feb32385bb41424efb2a1a3419bc4ab62561d8bd2ec58194b6e6809de2da5281e5640f376b928785311563581b9abffbf1d54d23923a3701779d56d41eb3b16a095a9cf717e087b94fa5eac9427cbd9dbe6decda6373f1ef1107a1f4dd5d8bb369dde77ad191827052c35fcc5e32bdf3d83941af56048011c636db75800a6aaaa9cd1c92030a1cbfb0f9844132f10060de38af2d539711920965c92abba4b3ba8a83401d3b376c44dda6cbdb0af8dbd9bc5b6524ae794ba472eefab0157facf40f3934f65f2e1e1dbb4b44f82635940d8f9a0052b8fd6692182a3470cc7f23066944e6b87f3b2a204fc6b21a5622f79db2e536aa41ec989013f15f8f005982672e2affc7e278e032e8b6482e67180825d54ded0b66059707dfddf01c652530ba15aabb6a565d12f41196d984a654f963d8b1c50b264745ef1e58fcb1b3bd54e9219fd56e83aaad12ca557e185526836fea5900ac234a416fd6452086e757dcbc9e3b0bf498837185ff2b02184935b70443985f260dc1c7d03f0588fb58bf35d2fbe51da5e71d0cb50153b9fe498e6635b3ccd3fddbef3c0aad91f5ed8553a66901644fbd19acaf1f60f7d96788cb49db039c8f3db3d7b0b5e360b183544c8b81b7c0c0adfb85992517ef9b2c6c3a06b550f8cfad3affc97cdd00b721b7a6e0de6e784c9da6d622ce052def18b02e50a813f56b291a64a636b2e3e5d4fb8182968954239a7785a4429a33fbf338ddd730ed1a6e4b385e557efa3b56f8a17b0d6fd6aae2f4133904434842f4e610b4d92fabfa6862d69acf8d479e5fef892aa4266f291fa3461739610f744246d3a4aece6fdbbe8c75b5411d9205a9bba62117446e11a0a224eb9754de56391e16f41159a026d579ddd1fe18176b4216526086da6319768423c767f812dd854fcebb3cd7fecf5919eba90899419196181df1ef471ca6b07dc228668c4bf15012ffc9e5b233131e50f310e489cce856b0cbcf826f9b707b8cebcde9733caafc7a2edb47ab9ddb52b0568b92669f4be00f4b011be70f4d5fd0ae7a49a70f55837b0cfd13e96674d6eee35ea5d8e08c728a0e4ac5870c06fc4154f1f8920b1fe1d66065666b50c236209370e95e13b12ff68e9353f748b916b8a71e07c6c90513108ad50e534c44e8ce933e48eff16e8e318c89ec95c6c948895e8c12981b64c588dc8a3e5d5be9d64f6d74d92c52ea5c572da84feed296e33790a8b8410538e701e4a8c137e77ba6bebb7310ed8e8cef7a875260f4fd379b30bd85c23ce2e93329be137d1b95316cb59a8278c1e502b1ae664730b8de88ae6343b3dadf547276816a052d735d436bf2364e90243f94ee6c633c28818130dcb72955b3fc27e9d00556ab6db435548bf0083c1fe35f75256d948f4fc456e322c579ee827f551a0ae543ba612595f180ffdbf72e3a9dfa47b25d0123fca5f728d09929bb754abada3c1363fc29387d92af88f51c094df2289a275aef54500db18c1af3e3427bc8fad7f8b09ab2de0524626088accb2dff507bcef8bbab513f7814c08dd27bde964722326d3bb0af95ceb8d14cca602b9dbead4596762ff914a858d9c6b61e6c908c87a333e623070ce9d8bfed7e04779d337bf5ed8266abfa7eaf9cb07fd782ff02b0173adcc4a123a4d4d43a53489b75d4c4533a16723d5deb6191325b3cb46ca306b59789d258b204759ba1dfe92f8c254a360f257ba398a870b1a447f3cc2784f1f0391e233a6774e050ca7397223126b0a5f6d33a349f9e902130d68ad1631e9b8f605a00b1fa977fd77f454fc05c9f61e00f085b7af7bdcc268e7d303c8d87083b709dfe7016b6ac1ebda9446e9585277a972a8b04d8f8f24bd685ea7c5eea104286afe249c13b5246921b02d36f96f235f83d4b0b4b2ea9837359406d7936371bca1f4c5fc4aefcebc8784cf297a33229e2717bb2dde4e9e232b5411188476357dec4ef1043c84bb847d780e9a097be9062ffb3852e58e4cdff3d5be8513556fd90afc7108a71ebb86433114ca1b5de0d740fa7434431ccb19547f859a7fbc70499c5c33fe77dbf05671c2ca4eeaf5e4372ea31f1813f5da61d5c2e3fa272f41c13ba6c940c299524e19615f033b3bc55053ebccba37f64a04a33bda31805f1bf3c761ebde43f92ed104f2c70d61b1c9391150752cf3d2803ab41b6f7dccdbec6181b0f07bd2daa3e5c27f39f3a98827b2aa44f6377119f6a89dca2babfb82d943e80e59c05380241a83c2b0bca89e8dab0de88d5b065ab92da4449b043043b634847f475a23bcf9bbf6686c2b111b9ba45325615125bdecfeea62d14bc96290b8ff62008e08fabd5db2be6efd14e466d30231f8274d124419d7b6e0c7011d248e41c66e561d4ee1adc9a1ff4175f2f61f5accbeea71a88bfe010c636170ab65d16d9a9f154920289d9f7629beb9a1c8ee31d785daa10e1ec455f5f7ce01eef9edf81d46d5307d29d524ee3c6140754cbb3b800036e9316f712c03bcd13ac3bd6136b93917bf8fd17efeff4bc9166f6882aad24692b62b83ae8c61d881d5ca3d4f0db4f135a548de584f6fadd7cf01f3fc8d7755c3417a74c2dda6f4aa0bbd3de0bc750d5578c3641e1d8e7c55c7da3baa6a408d54b74af0e1ac8cbd3820b93378da72cfe14b8b4c3d8cb819a8853e80aad87731912af3b8cd5d2564b3f9edc9b6f3889da3eb7ad0dc6b2ea695357576693304ea1c90b822c1e0e21fccdfc7604a7e4a815c17970134cc868a195ed1c718a41f431c438398b0c76ba5f9ad5e8892ffa8aaf961eb413099f6f148522f8ffa10c82eedd63217d854cdb6240d388912967ee9539624c96b73084a470968de7bbb107d6f37d8b1995c1291648691deb34eb33580c35d24744131c74a50163a1f972e6add593b0bac14a87ccb804eb5d8ef1d27b05257b26033e2e0b088391a37a36f0f3400b8e0982a4340437c56703e24cb7fa2c81926b93256d02264b56c70d14cef2b51ab7c8bbb28aa791ba826b02f099da314d809acf85d16ca506a0f2d436d6cc14662416f07d80cddcea522899771081e8fbf359544dbf69487a65aaee7c04e4d35b7255ef912b9a832296307692debba09465ad4ec70f68aeefb940fc5cf422ded52325fdf2c8c0172ba1b6e07dc931293707cf3c32a6131411b791076fa58fc38d40575eb57622241e131f8abd7f2eccf7f731d8c85c7ff3031bbafa6af855bbaf650fc57639b9840e5bf9a010e1f120c6969e4724c7be46f667f9599891f4a6730595a49fd9d90db7b94978646d4aa64856058cf3614d9d68fdc4d00de640e2a2378fbc6145132f3d1b2df9d7bef30aeeaaa6d1c0eec861ad06053b089f3ad65ae9da16569a40de917d1457adef1eff7f36fb36969a4fbbfe88ae9fea850f66062c7027daf0897da36db804d398215cf992a5abcb89e6d433c51c7cd7c6515da958eb702f1f0b31f9e6efe64a6eb51a35c2c8a1a708210a30f9b9192f3fcc144649a7964fc57575e285d851efe9a08efad180a3b9fbaf9bf6dc725e32c46f4746929b2fbc33a2ec5ef2fa8def0a21005f765351328f514472b24b450d5572eb8258d58f489195c3f899e07737d3805a696e042bd13a72814ee77aa9b67a7549f93ac20c1957a47b3861a658a573726bf7027db703a88ea40c7796e8a379216fb9e7ff976f966769e5044852cdd9dc8f4f52eff8790112452e357e8eca9d9e6ccda93dba99eea7aa05f73e25b6d0c57dadabe3ecaef02e7341030dd96241f842e0a8be7a46b98efc69e322f4b4d4ae2f910f13e7821dfd1fd5c9b195b106d1172ce9bd417d30384ac47c0a8d0ccc1410df6b890d1426e649ce31e6c51beafbefc120faf93894619bdd98e390106ffc30a1855d70e5b3a64f4e0b86ff12035019e9dc2e6706d7eee30e6c56b1d67376fe75ee13e0a1f13510f9c5826cc10335c6580a7adf8e64bdd904abe2b3a31bfd130e7c742106c8ce957416363ee4341b9380844a6eb2754a6d51b875a4f399062e05a47c492f87cbf11c332063636db50ad2932e2bfbac04d74f465309684c9710a20f470b8167edc7e0491042400e222e55b9e3030c3bd87e98f4c6f61cc2ca94a473679022a707665114245f58019ff7247cbbcf62afc34969e6965d990a01c10bec996829ef8b3e7e7784a0c50a91d87ad8efcccb633effe8499bf4525adea2aeb3c635bc5fa80df665cacf5a09d47e671d1ee0aa99aa5c049ef2bcf0b036d3d8674fe84f7237330478c6391a4678f6dc010aa7f286f49cc4d32cce209f3f8f22c260510c562bec1a297e32af9599924dddf3a5d1b728abb8a5c78a199f42f1ef360d760c46939127082058a6a2d74204c61d2518769a350fb1cd104c020e020ed002f1dca3072988546c2469a7e01b9a4d24428d19d3673825522a4dab4743f7e3b06c6ef73d2602c4fbde9274ceed291ffd789675b3a3c1d9d1543ab94f6c10618ce37d42fd49b05fe9ea3977c3b4037c961bb7d75f5e26cbf3494c2c49c0468f3f48aee29460e0b293fa58556567972a6b66f7cbe0b9dad393cd96ef0ecd9b6fbbc0ea167a095be969f62ab05c13033206809b8929ac3ffbf3d4b7ee56be05eb36258f7b468323183b64adb1b4b837654d3e5cc4b65cf80b692747815deadeb7493a09f20a74a662109be5e0adac645737f8bbbcd11fe11ba9ee66c1818f2a6ea33e9bc7e6a16bc14164e11abc1397844ccd57ffb74b2d21862f518255ff33c9376a5a4e55c686ef58f8f2bbbac14dda0f077673fefe14454dc62f19d5906a34442de77306844fdd06709ef5915c1e1f54871dd06d920caa53e2490818fe38da3e80ccd7e6af63ca6a70a4c8a4d36bec4f54cb942ff1fa874eeaa21f77afcdfad2c264db2fa9a19ec1c7129b617bfaf24ca4662e62fad0be1575bb0c928329a32998f3bf33c4ca40bd3aa2d28cfa9d03c8aaf3f6ed688913e901b98e5688f035427f95d2ec764d4de5d7cc79120a5a9cc792a0154a8a2b491c823ffde8ee8ee0c35559e8f958a8337f455bc8552cbafa53a9e429c618f2116907aec69c2823fa5cb680e0c0f748f595a41b1fc1d9e802cfbf2f049914957bbe6c677e1df24103f36aea9da1999b87f9cb2fad31675d3dbf4f79de5758f2da31f6299c4e8332a4de8834ff53f42c95499c57e49a420c6c03a0aa3fde96fd1a493d3543404bdcc17b4e1f49de3a7cc7a5cb29c8289c360fdd7568ba360d888c9232053770f8f1d39510efad38313a2034982db07fe24eb463ed2bfebf13516d08b978578bec50022330fe6e1103504fb1350994456fdf366043fd86e777be5ac840915434c687d96afcc86cf501e31dc8e9c13093cffb8baf4dd45cab81e4404b78cb48cbbfd472d75249aee5396bb174282e80a779ff4b46ba1451fa8dec5b37d416a29a73886d25164b151f9b6b292d7d19f5f45ddf45906fe2893ae64a04678ba0394a21c614449e7f54f830639255561e204448d44c4a0b20dd008133881edf4b66a2a3f8598fbced2033c7fd81c2718c55ae7e81ea1a4210e81339250dafd0ae4ee240643b55b134dcc29375db8fa81f4a583645bb2a715cdd867ef53fca5098787f2059bd6b56200cdd399c4ae68f29c8be50d7e4e5a0b010b285eeb469194ac3d0ee1240c068c4c6e1666dba66f83a29b7af51eddc55a4cd2dbeeb44abc4127403e4b69ba9e6d3cd907e278bdbe781bc40964e95ddbd940eb7ccc394ee7d6d6615412fa171fa2147eecf6e103d0d106dafbde09e051d2e69753d368ced8d20d977e4e4dafb965458f3b27c3fed214e814343d000cd564801ac392c1f86fec047c271c23e2ca6db085b3905c26679c4890784e719983b38b3e260c0c2e8771d7ce66810764bfacd0997e6cef6726f21dce6ad7c1d7bd6d37d0bbc0f188a6af36ee016154bb4a3c574c67bf38a2ffffbb3110e4fa2544979622a3e00c4f86d0dd64fc9cf1c5ede73ddf1c2cc92b61224982eab2b57ba5a4e188e8ac0a8361d4d259b8caf32fcd1125dc8719564c3365a16ca0ba93e1fd46fcdba15e4e561894ac1ba71a24fed7eb0f8b2e9fb4c1a2753501368c15f6048148f91a251aaf22f5689d89556e0a3e3f377da9e90a3bbb12b933d593796a6cdf8842120da4a952b9d9f440112c7e363cb8052809e9ddd106645c26faa9838eee341d05e3a411679451db2e7f465a160c5aba045c66c2bff31fb73828279c6c472c718c62dfeb6d710630267632f1b61f82f769071575ec7b3ce4faaf4aa2fdcd252cc944d6edf6119174eb99dc6b2c74b45b0630997728922a68439b3496ccfa91c0e0ff670216710d4f45c2b544cc47aa4ad1e4f5bc57fa4e2a6963330de749e8a189494fc83594a20b9d0332cb83f91e69dfed2d2eec4433f86b137db31b2981c8a7ae9713bab3a3142ffb36a47e0cf02647f1aa2930cf3b00ad569ec388ff2e33f9d1ed6872e50f7ecb4028471d8a122d15a0f9fd6092905737761a917a28c262d2235382dcb2f07f26207b7f3b3c2df450ddcd7099cb1576988655009afb84dcea2425d13645fbf0b8fbfcebc6795c301111ee398cc628b74848080c6b3c86a8398698f66211e3d030fb4180a57fc15ea01999860e5554dca37c2b421b8b39d4e1b7c049982d00c171e70305ec8f043bb18c367772bd578971b17e43a0b08ef22eb9fc53e6ba4689255693956451de4ca33aa5a0b129aac5fdc48c4526e02196a13b2a54870c1093dc86df2165291257d725488b67e11b7dc1a45691ce4790d90b6b50be0277e16bbe9303ecf58900e8136006349cd855019b089b06be8a4dd9bdec8bb71ff12f46cad7b90d486e1dcdf97fce8b5e5dbf85962c08957bfe8f37c2e7e59ca7f01a77a7ac90f07b7f249b88a0d2f52eeec0a246cb82c39ec50a972a286879f82c3cfb05e23dd5b79bd927b31c9ecc398372d2acda132ace2136236caf30916731f5ee8e90f8508eb9810d04795ada61d482f7b1cdfcf80d97d709cc437fdc73e91614652a53c7b461052c522d15e21a668cd42e5ce86e5eba54f8dbd0b784c8905a5135eba9f70939db2785409c1814671c9284132c62aac51ca706ab1c670fb1e631d709dc70ec546ee669e893a3b2a8663dc53195bd054b1e4f2c089498931706617d4db014e153b113979fbb225c3c01456c8d49fe3ba1cf2c4f3f24deb3ec493476e8f79c8974f49e6163f524917f09fea68bdbb6aee3c9ff4fa1f23425578b1f7cba3c13eb24e23ae10b86aed7a0c3202d67ce0a849f578fbafc78470555a43d965c8a5904c4badd04f031ba75e71c47a9496e342588a123a21e56fbbcb442cb8898aba1b48f8544b7474db2fb477874b1f90d020dcd54098ee295e7b257ebadd52d08f3f1f8f16c13fe93f99e0fd48a4f1f7256e6eab71376a38dd4cd281fe84c0629ab4fd8111aa9e5a06264b0a6d2196170a4fa112415cf1f7241c77096262d3ccbf8d43cea54d18028b71af8c777dbdb8e77f07bc268a7229ce24d72b235d493545e23cf15e612d0af33253c5b0bcdb6beecd94a3e6ad383091df8779888ff4a6e34ed0259df96d853081644f6005dd6223493cbd4fc2951088b06bee4b49c5f8527bc901511add45adb18fd2f3088ef31177135cf03d132227df6e186f429b0f1b507f2ff946040b4432d405956c27d696c3f41fae7995c4e75e4c124a6a6978fb39bd956431c34414d313561936e5d35a28f3121166e9e28a8bef553cc13e7a9c84ee2213d5fbfbafbbb1cceeae70ca63b5726eb82cb656c83474c158c845e2cef08eea965813550c0c78a42c319d60d4bded5ba1f51c23a23e8f540b0be0739f919ef72e3879ea3052d5c3e6ca862a3fdfd045a665bbfc2db5247fd276b146509e0157ca109be9ea8ecf0a993a97cd60eaf584bd5b0a4bbb2f53ded82d6211416be9c70cf2362b7784a7541cdecfd1950e85a966d3ef0c0e23c307f784104a36c62acc0607eec9dbab7432513d42f8429cdccc233e41a085ba553a762c17b552b5d124f8b6047fda78da9ab40bc1dcd112ca081af517a183d9f025dc0b07444c706c47d4ad5b8e7eb97980f94f59be0184d323370f2d1256240ec64b2afb5dc86b667e8dedf45c8286f0fbf344c05705d10a21385ea646ed2c6974d7f341dd805495e0949c0973471a342dba720f1ad985c5b5705239b854cebe50612acd0fdb4f6065bd24795c7cfb6d0f35a1064a491dd31eb89668bc4c4037a28772aff5f557a3744853d2241c0e78a251d2e3d64a612a5dcad8dfc31dff534ce63ab194081872244b1d2b683b3cb9c8ca5cae1a249ad27a892bbaf1378f92bedaae2a23c2391265f6827c1ac8db91c6908cd6939803f84cc5faa05031f2bc526c69cf8edfadf3ec302df027127044f6c439bce5e643ab90bbb0751cf95eee36a57afb8a11bf910f89ac45c1acd042664b792de64a23ce6b255e167b052214f2d9e79a3766e54d0b38f7fba00c0f5c8e1a810f735fdb0241c5d21464cff1cbb8d00f51db52c63729b0558101cb59c2b87bd0ac7cfcabc655e081bd3dafc8921958955b5e267e6ecc9620b9d5955c5005f83379c7d727eafc80f271c43db015196663a46b9829578f7d1b0e1b2bb8b9101eac6fca2f39b535730509665e381ded26a6ea9dcb514127fc2ad01a1f9764350c6aa8f7d6021f9caac0f39e78eade2c08a3808e48a3bb34d68ba7c6b9363d7404f6f95635caf936ac71984b90be12cb1613c3a0e63abfefb39f674fd013eab88a1a373aaa3f9a1c24c3ac24bbd13d620adeacec2f1b557f0720cf360328d88f73dd2148f82954de507411bcab98a5db4d081a4d3be93a06c9168dabb04cdc9f887091f305a1846c848fe460f4c5bb9b54f9e2e4da5ea18d8d97a7faecd98e00046f442e82186715e664ff3f573bd04f2575bce8554a4166bd10c1240d5a586a42682251de00fccf43a3b2f87622eb6fe3303de06d114939fc5a54d19d98d23b63920a6e53622112ba01e6d1bfe628741d74f52dcf270c66e44ff21aa8a69310d728183c0d3c3079afae8ede3620a7991583b13d2e3944b71a08945fb0c3baa8a29f7a0f2fe438cdaac68a77a5e609cea72606e28a41a866c7100e4587828c8849a596e0a1039b129a1a7f706f38f8c7901eb445389c044b5120fdbb75a86a20365803474f7ce9157c457b73dd8e5c1368818d769b81fda14230537b333b2711f0f4b4801b465af07884f7d03f8d22865ad484f07aeb2258b059be143f8bc8360803966c2dd7ff48a611625c0f2bea68aba9d6e7ebb2b475d61c6b4e091e52b388494b89b1c01b7c44981ea8298aa76e41e2dfd6c811bf65da19e63895be336894a264a87adbc539d3c5facee70a287eceabfdfed79e7b7417651eb5aa700aebde882e790e4533d7ebc352886360147405523f5e26e106e1e50277eb03805f316d4e0b034a56498344d0d41585882801c39091d12ffc8f6eae9971a80c9f07ebc6c81deefea07bf60fa4da33c79243ead93780a1b21dcd9f0bb6e40968585cd8c70a9798141a0454acfab57c9eedbd1a306cec39d9be33ba785ff75908da23fdb82290c8f4bf85a895c2317ea441573e0594cf24389ce915bca6a8ffa766efe14ad76881f3eb19d773806728ab8071a80f53c63c1dab7ce6151f2e9bec85129bedb52eca46401c1c37c171c14bdd7824a68a05c4ad20c0643a65d0d2e80c1d8575e05601b9ac29ad444b8ef9a7a89824fe46ea7708803bc86986e653e8ed9bf866823d41ad2d074e132247a7049c3177ec06871d269615e40b965054958c7764192e85f9691b8bbc6ebbd22813a60e122f6766888b3919f7fe196a7743504b8e4591fe26a72b564a7976b3f4d8ed2e486a49bb1cafb0bae1fab0d5b34f35f1acb9c99a6cca100a55c6503a8883c56ce5a8b729dd6a80a8236f74191bb52db116d2039121a606fd48e05c89a6b6ff3143693413cb97591bb9266dc7d0d8752411d0c889797856195132cf503880551ac3445993345f3c3ca4be18afa59baf7662c6de1751ffaff9bd6145c7a8612572ea94f857724048c5c6513e560ae889d6d9fb32905de3ccbae23f90f83865ba1a6ec044dc9a8629045ecea600c9d81d16ca9321790c9dd79baa19d22eefddcc16990d9c31db8167b65c01a577662abc6a1caa20affa2032713b0c4705edb4253a299e9294df401903c749a171e3bfd0fabcca077d5f2e6d03c20b97e4e6072db7df9729cae0756932d48240eaf11e52a91d32bdd43a45527d577c864249f007e2328561681d21e00a64cb9f61669c3d96562274e509632aa78ffc1cb26cab97edea39b00440fb38ea6231c227e6a6c4e8481dd8957b9f117bde47cf964881a942619ec5261e6450b66af52ca534ba724bc2b79a468151d2c9b9e4fede355d60970231544cbd3587c08392877e0d3b32cf1c68b3f3d64d4ade194998ca28c53b3ede98a8a6f334499a43fad7210abc005fab38752d0bf46ce8bc43bdf46142daeae56e12ca2c3b490112db34d68307188c7f04583d54439d714bc1a5c86deb2f4c14953e2e16e271f37a35274551960011f5f810e936e882923d49f459b4665c049f53f0398b872f5d9f166d24cbb9a3b71815c40e05f9f8b24fa361659227cbca882a809b63a3d6f1e8a0c8869e81f5b9866d97c4a4301c8b5bbd2fcb1d36208333cb2a152bc81993bf229ae7d886387860c61d8a26ac39bef826869bff1350a0dba284aa88b108918d1e32ac7a4d0652fb9404076dc0ef5f21c6fa99f30491122fcf98417f00d01ce262596ba70426436a82533328ca0814eb56694ba07a3c85ceeb7970ebb706946ac202e96f689d32bf3a609f88f4c8d176a4e483f367e798b8f16dcf838831576a1c5dbae72ebd162c9bace60457530d07d2cf6ecadca1d9ad673a0970567dae5064b47a9149f05aae33da8b37c97ce74f589be789ca8420c7d4b98669190f5895caffc10d25227719f6fcf5070bfe44a22f29d2244efbae008bfdced7745431f852fa0ff31a65c2554751953840d284b3078694b14aa2149cb177d232121c5885ace32c67daf62ce44d70faa8839f4405330db1dfb982cdf0c3b9f3e8a9578e7d4d466ca8e7488360700992e7e0c0af995b3ae9c39abce414b3c309e7c637d66634b040a8674792759c23074ade29d19a0a0cda71b892a443fc924a5eb248cf53a835df6151ed88523cfb058342fb67d00168ccad50e55185eba49aeacc4bc6bec8ed399ab57a855da41a9940df7d92dab3af5e472c8e71fab89a4793e019dcc88c56a39b0f889f17f1249cefb530f5063b727022c7a634cfbd463b5dfef96a9f7416fe4504ec3fe50a5d6f0aff329a9ba39c2edbc9a62393edd5f84c676a7d9e86229a4b5991be8853cb8caf897698929f15f5a48d29b6afc0a2b24e5d26401f84ca23d568d85e9a57f0c34c895683e831425ab2682b457fd65b2144a1355a52d6578b726d399f69efbd23f60575a64b45c5bb28a3ce32ff31c6e63d49b94c006d9c315aa89e10535a0a43909d999b049851ef73f47627f1d0222fca6a2cc1991160bfe95bef08a6243fc71472f880fb7d11854e61eb2200f0d2e2180b579d0f750473596f75029f31a0f1ba6fdba2fb8854f01f7eeab2327d66e6ddc3382dc10fef829ebad3beaf84335b8d2fd7ad46c6aff5a8f1b6494d71cae0197cf1c7a195b4cfc12cd247fbb3de2e1f27d9289044d89f775bbdc4b8841a3bf8a1107bcd4e64db66836155b686c2d60d4de6b92e67f6ede0e198834bfcb83b492d50bbeace22ea6a93ed5ac1999eaddd516802e44555df2fb349bac3ae423152acad9314cd7990c9d2ee783c17052b62c2f158deca94de0f63d30f0d5dba66dd6f32940630f1791aec151aa25013b9f7ac64c0a0b57ad250a1d39b562a1bfe6782a36f8ef1a812e28a93afd5f40e0317c8d828f31c19cffaeec41631e4d81c29d987fc76f4a8989dc0a514e46853c94f7c9fa30b24c3b557e7b03910c99bf72e33fe5b3d4384ce37efd0718b40df1c4f8d4e062d4e103ef12cbc5b14b62ffea9c700a52f63df6199a0fa18eef77be4bd79c955fe36c398d7394921c6c2bf70fe5550db27bfa776914bf06d84a478d5077224e996c6163afdbbf37d7ec9b9f6673688dbce8d14ade71037774b166065bb6fd01514632da1e02ec19cf44fad364a4fa01ba8e6945ac93c4fd2109a0bb3cfad4a9ac1fda638e0cedd6694ab8820552ad60fe9c2d80db865dbfc42c24e0e766e7cde66c55e0e03749b8975ee76d824bfa13f373ce49fd1020131ecf089134568764717b2506e9dcbaf5d17bbc7b5575ad41f9a9bd2bdb0dd16d5cc2c3add64a9a00265bcf2eb4e1afc7ae2c1297e3dc6361dee6b2fe515d291ccef8b14006be1850dd125c33b2d7cb49f578e61f73551323ddd7280fcb1454cd401293779cd590ea0748d9584cff642d5b0bdceb4f8682a56403de5105e473edd7700c2ca746a46f221ad74cbd29053c562db9d63eaccdfc1f3157a820211bd0639f25fcb46688f51e74eb313cfaeca9abb9e6e2a2b2e308f86f5b351dbcd5aa834ac9f25bb487c71d7f2fbccc578d38a7d1f4e9f5b2099c098174c1406e9c5f1c2d4ac0bced7e740f33bf5d46f90d2924f412dd765b54f1c993722f14d66217ee17da8204225f2d1a3b3d8364d5c7fed11099952f383273d3cce700ebd48bac52ab131f2f09b073dc844ee0f1e8cb115f558135b45be2d11406acea266f6fe56e7170288c530918e7da1bcc60355af172bde9173e6ff7695c945a79a9a4dc7e60c6d159c8909a8808dcd1ad62126b96a2e1935104c4d9fa79a3d2c5253393063363fb9d7dc4a26f437a217950ed2a66c81c65c1f39943ad70e7b63944f8c785a56f16a88bbcf9a18aedc13a765973046ae2e9b02872c66973f6ed63cab5a1fb0ab62d1cbbc3875d9cafe8b493e45bf6150ccd04cced457fa59c23dd384ebea7cff3b1476d59cad796a7d60b247ba814e3f664fd356c7799d9df463c912caf246d48f7695a2d551c20254a823e4854ce7b85248778556907c9360a1b366e7752d773b58ca0bc4b3b3bac0a4622e50e3b5f7e261bb3c0f9517dac662a17921111acec89eca493e8c724dc5f4cbf5cb8fd4149a92fd2ef5c22e64515579be4e8caafd572f18966f8a6d0ec963eceffeaf6b8c190389cec9878d50417fefa9b5dcf608155b73be85e2de357807649466ec797b0df372e6d0b32dc502d31fc9c457dab81792655868f8e3f43f20a5903c222ad8041de4908e2dfc063600b432c683d09390b9c5ff6149b852c46ec030199f1c6ee853b17aca3ed8a947a15a864034d94819b222fcf0ccf1826d91038b15a4428c81dff3b3ca4175c018bba46274758da459d3a5d3fa7b08aaf8bb1fb019f15db459d486a369131922566cc355ed8e477d6ea322450ac7e82d44296d98ab8dc87364208c93eea3ceb5e21181cd74e8cc65e3c111d0af42e7cc66772da3926f0bcf369efd77f82cfa541990a13afb706281ab50aa23fecbcf89667519d5e20b8cf1213f1b0db01cad7e0eb6489451305b8d2598cd7b617c8f9103313617211b1c1dc6752c906635e75ddfc0018f0d7db6ec18fd39deb8a82e2f1c0f81044173ed3db304b94c67766c223f3a57a356bfdfe1ea416eda76fef17eb8840a63f864bae385b36a9b8620a28bcb9ec1e2aac44f793483d5e3f8271fae608d3706063c152347b524f3919b7a8f98034fba46a0ff43d0e29e25097a9120540ff18feeac52ec74512913aea646c9d39a79f67eb28e92ef08de98f12a58ba236476c0c09d00c5114fd381ff42d1f3c1c4eb3006784ce22ca5a7f3c6d5c548d733c4796e289c150661f5af7aa341c4f24364f2489c739a7dab117a0b6818231846c2e344d83c34d5c0b46ce53da9ee090a4e971f4a4ae174771151b0e9fd0297a5d68ff53b416793e6912148d4574af645bc323bc06b0a0974b480d2b85511d6a9aaa0ab4cf350ef517108fc638e7fbe6ed08f0e908dcf23670e529d709b33deecfabb96265e32790ddd5abc238e0ec21251a78f8c39c6ef6c3bc956e16aa0aa3f82c6904d50c90c0a7c074180374a16574a9fe59e737588aa9ac8464066238a9994bb97410290d5b5c0bf4b5545a90c57da5c1f2234713095a96e9bd5509b8260f2f74412ebdd2dc39e045570007cc97a61e419a66e37b72cd5432939fd5eac3e8627b2eccf005e1c7052a12555f1ff8bb9af09fe20c33223378f794ec18f92d56705eb1415cbaac570977cfd445bc46a7884c41ec191b5ffac7fd8251422095a3883d75a27a66a314096bc83a7662a432c61134a33650d16acde5b1dde38b31c65263d8906992d1a47f780ed26b7a79dd6961226d3fa003cde86b8bd917f7b9f82d916ee0e06e6259b3d7031c4c9df1c042ef274080e4e722b3290f55dfe4ea677b517c088c152befaad7e0c60f687a84c0f553bd22a74f70ae5f4e0654b707efc0c37e662f6f1fdfc9a330f463454962358e2c1b552d7593866cee77c74e53e4b63ea150431fdb3e76f553cffd0ed33ba43829e0735674ad643da787292398f592d449f8ee5f1b316af3fc735a0454be8cd2386dc0bfcc1a1731c83450fba314223e8d1c8d6c42e69252b424259a4b9767f839586e43e2fb0f9eb2befa7ec57383f1b68c24f4d72c9fa655d1bd12fcb8c4d1809611397d4d7c695c13aae153929631c47a7e1002929fb28fed99a5edae3d945777b7d44a91f5e3ce8da8cd27596eb3e99dc46913125a74669779386902ae9d796e34a15a06906ea604db871e475b0f0b25d181fc583397b20dab5200af63e299ae70599f714741962835fe98ad5e9346799a9dd292cff59d76d6ff05a73990d993bd5372760c0ae332eb13fbfc183568d064664fccccd329f60bcd0e68ce3353de7d49107ff401e8e3ba0730218551748fa32753e33883a8140ee8789f486194bcaa8418354e8491d1e54125ee12a1207db4815f07a53b427512aee1de5bde1ba6ff630e8a9c29e105b730499cf24656c4de8418caf80272dc72a4ac607f8fdf05d7b5dc4fbae66cedad1b1255a32ab6c63f6bcabf66aa898608eb80dc88f6c6b401a14c61a8b6dd95a8fe7acf16db13bf8f93e2af240c2ace2bb5c96b1de52f4423b28f5ac8e3723ca78f7efcfb12f233e8f17355c9e8cc22923b367eaa2169185d2bb9e1851b7b2c038b9ec302960e3af6e6b2af71fd3c4f84e639ded821395352e3427fb015deed324361cf2a87d78642517ae20f2e181027258ac5aba9588eced7860608bf96c3753ace696f88ed5279b7c9a685e954facf83a501be12f3659917468ebd842f7a003ef36862ecaa0cdc9d414b6c3fb9b1a01847284f3b2ad340ce25f9ea944c868513612b880296da4674a844ac79616fe24af5db12a19156e40b2d0e43a62273324baea4860ff0ed730d684e466e3dd8db4e7520f4f7b1f24a5a47217c1ffaa07c31f27979d22ca5663b101bf0278f231b6d52bf3ea188f6cd4fc64b3745b253572d4a2efd906f7e2800d19466f670916e515cbce0c30598e217dd34666b3d11ad10595fc38315da41bd01ae97714b31052b26dab56a8d5e4c58246a71051e503602c211c2252c8d98ef9cc1057d60f8715c872e0ffa795d42f11748a1d6f17507b1af0d5058f6439e50be8306e342288bd719c7e881963e11d697728b72e32570734d4bc0866bb4f8a82a82bfb159056006bd5d7feea31c04a367dd86b335df05cc4d6f4ef4b5a00d3cc634021e61ddbe4f151d38ddd62d9ea4b06468cbbdebf39065576e27b2a91c771d9e6952e95d05db09750ce8583e6fbb7df50919649eea9acdb6d0f575f1d079db7b5b3bd53a353443ffeb8dc4fa0ce740465efba9e14772c3c2ae0cc48d74215507cf12dd2487d6b273c67aac0afb494b1f13711c1653ea07ef2ef19cca2f7bff45fb280c10f6700bad2fd614fb8b2758330472e440a6fb5fbaf45ca93cfee4f3e94e2941ca7ced43bdcf6bf5f804b4028800963a8a1e78158b2d8bcfa343b071c253e5b1e1ffe0161211f5bb5b8e8e496ea4caec0bee63749bac82a49d2acc59bc5f8163db1d0f84a9be87c8ffba63120b4a90dbefa8d84ef6e7a9f32737d9b37235b9b3b95f1a4e568431ef70b086a56f52747dde67587983e8f93af3b60a274ff6d4cfd2834e7d148314f9e439462d4249455bbd59604d247ff52c85f965853a08d13df7dcafdc6d6bed230650623cf6a4cb94bd5edb28e05022312b7a423fddd02d53e8ff1f79eecfc79e8ddf4d449525a859ba86ceea208c21e1bc6569ea6f68d6be1e397528a9a1c90eaed8d71e1714576fe78c3a79ff58ae8d2e26d6c11ba68ffce084d700cc086b0c4ed16d874ae43db0cfaab63ea056b8b6f3dde422eb276be1a90393dc440b24a412c4699e4dfb168aa35cbaa79fb7889331fdae0e4a38cc16df24d5db75b395d839b6c368969c057ed7f0e4a38ecf6090c15a71ad16b3a695d70177e0f9a8781b454e0f4d8b3f1aa212fdd2297f2349ca037b1ac8414cc22f32a30c12796d27d673a5e498abcf15206d123bb1a04fd89381ee8a4dffd9f6067c2aee5d8daf6ee60ddeb2fa4799c8b5fe4a1cb9ec5391f45ff2a4cf86848813271ad5f0b6656a1c7e488207cbe47f32bd8ac241e3191926a2bc030464fd04a1d035f5bd60a39abc3c215bb7918293ffaa8d454f5745eb183c1e1645572045d0d7b42dfdf45907fe8931dde753c94a93551df16c4ba266edf4646311c69f801344c10ca901783e91f8dc13d61cc9fefda307b626dec1017b19a7f48abb9eb3b2fb7ce4026d183413c865b9a8d3b4955ef5b05c8c469838ccfee237f6785af0494a071e09bab3909a0f72ebe653c4316c8f5bb3b5e02d4ca0549c353f96459947b73c4c075d85c52eb81bcafc151134f15c7e5b394ab53dea47e19c74b236ea702c2b3d072097534c47159aba672f824afcc42ab68e98864f75e1fc46b48ca95c6c746e5797240e6579adc9d4348653e91d934d4c644fefaaab807e8c9a6a54f5d818d60ba648c55cacbba7625fa7a9d432257a2278108f8b7e8f93bfdc6c9bfd8964e67a1227b20c1abfb261a82afd236ba88cdfcaaf6eb3a8935c7e5b7a8f86b3f810bf2a0432c0e2ba252f28cd968504e4d5311d3c81993e4af90ce19270785c2bd23979fcd9b9e798cf1357d782bce84d3f4a6c2da5e8b04d93cd31bdaa0e65377b0b894eb1ac751d51b3a50b0b1c9ca4285d7e3629ff9e89b5cdac762f59ddf6869dba686c41bfb5949ba59f29107c688c267f0be06f79666e397c4604bcab214219282dd79527abb2bdc10176964dce895facaf2681f4d6cde35556916e834af5ef991d39b95c857bec866da2be1b05ec4c121c61ae4ab18bc2cb32f5fb2a148c518b61623042a5915f7c90bf2e66e9a730585159244ffe05702c1565c31259eab73ed83c75ab6252358cc121a19f7f92d59c269d48502852aaf3586b7a3b3fbd5de2a83073e636ceebfb8cf163fccfc30963676f2eca33cfd4cafea7a66c729bc0c402680b16beb6e7f49627d790fd321b8e122123e96b8272195f9125bbfeb42599ff01915c9978f19c16f2f1bfb219626eccaf24dc2c8bb0256e95a45f852812ccd9904fd67baeb4be6b48bdf93c96b6ddd55664f7b525dbc433e628ddc550d3e7e8d30164ecb9420291bfd6c9f1d4f56b1a58caa62d08e69b582e8977b502f608167a584586958db56c88ef35b7c2809b27a370304160e6efea64f2f99ec1cf62badd7783c2e3ef2442df4e6cc9ebbb2d1f2825470a5b9606bdf963cd0464f5d31917792b94d4a383c5194cc217b3907ff694ec6c24ecd416ff9d2f5f3f81c38aeb76e95861bb496494efd602d37fedf594f37b251c7c22c9f6fe7ed71145d8b9a2764810231666c1148fef0c07d8417aa31fbdf8a1412648f6e2cc1faff3975c69708ae9b555e195fc8e2cc259a003071f5351452b4c2ae1f43a6839e97da80bfffc998aa5ee880504d3364549e22b44001908e3eab19d346be5f6e3840275e1eda9d1f1fc18891584084c88838bb7338acd6a954be690f65411ee93ebfeae6ec7d68290e52cf9f40376be2b319228ee1174324e56f9393f7a6a1ab9b53ec6be5bec72351fb951d5f67673ac4dcb026f5112ce3fae63736b627a1c55c74e736c1ccd58c79a47eb522c2364e266894f7197f1e0bf0c0e3c8d92f3e6e1f85532c61d5262cfc143896db54f1ecf5dcb1d5c661ebff96676376cf9ee408886bac00268e4b0d3349dcf10404d44bc025cfb3e543ff77027302816ab7d45a6bfe8a2858ac6d6810a91c42c2b860baaa6c480de3d64c55325807aff4d53e6a5b0e49e33a36e5ea9d97a555ed564bd0dfc5dc5d01ebf9b9d4380bcb3dbb76523c10d40d74b65440618aed227e8b837e0e01be5a92050a209e2091f169761dc6380a056951f7a78db971f6fb9a1286afcebe98438b36e33d589a1fa315a52b41b1a107471cac2605d2e16c5495f19ddcdd78f45d2bfc15d5280c1479b9d552e852d4671e2d4c553eccc304efee9e0294f241e53b049a9634a292c235fdb46dec60035e8769d661f1f97cfec6348ff7c588ad4d95b0d543b30636251842fda2aaa4aedb88932a4839dbf5462771697173e1a4d8a2af1bb0f2347f5b81b583bcfa10c968506fe00b6ca35c1d93da658d0be1f781acb588cc0c0f62de1e076795d20373a353e7575de3f6ee85ded6a9a9142259ec2e1ad855ec861717a6a3660b63e125d73cb3e249a16d0d55ea57067d768b02ec0b4af49b18c2dd9d5ea341193b2a4ab2f8780a27903910530b7e2e233e7919bd5e85b9cb43628b66980d66aff1493ee1b8d478b81400ac28b7ee27dccb7e2e1c54d820d14d8f1135016e10bd978319530503c41a947d04d0bca3023dc9708abdaba55afb54dc5e0c8d4838d5751c1cf27bf14365e5d1782b1d36baa2fd65b2162fb8bf62df28a0fb221ac5f7ea81534017976cb02eb2e3060c9e39aa77148ad2ccbde2047209fe51631ca8daba5175580113be568a91de461e54784ba8704d10fe3ff868285f6a89d2567940d64e58aab6548915a9ba89ddf16aef9f21236426415520f90448a0e15c12e5a281985664e006fbd13d84eb46c3ab7896cf92132877af98ed7e7351cf1a44e51538b66f1778e00d0c52504627ae2d1e45a6b3e6e59ad1447f32371acc7224dc03bd82ce6fa63101c1564b45fe3afddc0819142a4c11731d4326a8f65d09cb9335e8b7a85ef0f90969ac6d995623ab1efbc8b0d9e264f4cd6164b6fc16b7658249104ba862f01bae561dfaf221889448c77bffebaf780f44fd7a8c3b59a94acef7b2e1ceada3a7ce06c79cdde081b82183476994523ef593bdfb960a8836e005b1785471ed318d5d72e432f189a7f00e40acc8d85a17c631c4cdb369ef0ad50fb366c7101422454901149fd721c7f261a20ee8097ea4918677133e5fed05fba06fbd5dbc211f830a6f03f95be3cd37b6437a92e257a61f06955240edab4b44e465d39f18e7035aa329d9fa3148f6f28241e48be115274de0b328ed75652516e19a8d8783d52889eed5c8d55c70e5ae4e33bc2640f79a62416f7e140d9062f0ebefc579deeedbb91b26696ed7a9413dac9093aaedc766b46f2c70339744c0c885bcca3bfa03edf8f44683a454661903d7f94cb0899d872a9bc78fd9d72476dd89f6127504e8379a068014605dd9ac09939ffaa6a39aea4b74c1581901c3e2c6c1955532810bfcebbf247fd9e95c6a81650fe922da70f2e945b5d3bc561199adfd918741e163934742d43165094520a3a0d5c83797c949db54241f6bdbce82e51586fd8cd04fd74c5187083bb830e9ce43b77cfd8a2d90ff7e3698b14765cca29fcf047cd6c5946bc58400ee10aafd28d44bf7ccf51006cf22cb0a6416a4c847c3386c9cd9c62cce75097c17d9f4b39008e3af18f0c2db0b54fa4588997320d607af3b95b056d2ec7d54e241f6be059183b03f226470b4e253b0779f704ac9ee34249323cc2963ba511b14373b8cd35484dc6f1b84c45979d1551d99506fb7b813f70bea0a23c1f94b6018e9fc5564ee1a946afaad90e3aa57c2b47467e8702312ca463ab3683257b5bf37ca8e7243d677ed823cf9d9acdc214f55eea7b584a7ad4f4f6fcfdd08f68ee301034a2c3dc6c06e5c1f483a26ec784412ec38a9f562dbfc4c347d05179997ad31731b62301bec36c036c2de46b205055a6320d2bd69e9a4c8220fe73cbe91cee4e2d3118c0d7cf7d486189a1020224adde4843f5738c3e169e87756a429ee505466436da49f10bbf0524d11e2b7d137827c7fb95b70adb84cce46aac81ba92911dd1918651179beb42b57b2375d2ee16ec827ac9751eb647f36ea4763855827e10cd249a051c404e6937ec7ff4935aa06552d2f5d0e3abe95e3a8b46569cf8adce04e37d985def4b6219b8823d688b601dedcb9ec9647f454967925f4f7bd973a7ce65f558a522d705176381718093543f6a76ee5e81ec09b02a5a4871154218842b13ae5aa143fbd86858555b13ffb0bc46a9343f8b553b7bab314d47860259b2fc74baaebacf561cbdb9dc9b6f15593e1d13600877d9b1f3ed3ffc0e9ad4e77e38a40c2c4aae992ad45577e676746ae779eb8b18c599978947a9aa3c7050e69f279bfcc76d900827dab98b4d94e4b3e773016dee83f9edea147cec0f3a0c6f6ff5d8c67e001800fcd7d9009f7f3335def6abe5190ea9076aac0c1e3887080ef61c000acc76f16ba9d53dc5d05f9ca70be61375bc65640bb4c6addf88d28644c57c004db3a1024de387fa497d1ec4f454836bc4994f6476d7d75f93be40ac06a21bed77589a4e3b00a3fd0470fe98bb15c4e91ab88fa4989a1227e052c3de3cffe7d454285835df372d5fec41c774cec27dcbe790e94b132210353161119f4348fcd41391b34376316a7a000618b9b35c942f3cd1c5b460e755f8369b28be6cfb3122ca4ad7de51e2398ac105b3bd26b2945e50aa9fcf49ff3371624d48317a940df7ef2f62b457bc0466b48dd450957264f1fcb19bda70b0b5e06c73987328474d2ae0a5bbfe822c8fc5e1c9e639a210f2554053eab3c7599e03cfdcee58a189a4b15d955c5b5e694e2357b22427a5740e64bcced75e16a9ac9049616471066f6e7b09f30e278d9118eea607fe2e287fda0f37cbabad79219e9e5f25aca1ecd160d9ecd76512451a4a3c635f4e96695b72ca1c76ba64dff1acebd8ee5e60db4e8a2df329f0e6d08dba9ba740b9dcdba1c0ccf83349958c1b82bd3e89ac1cd00e25aa6fd4e7430b9a1aeb9050bd28a54aa35bfee0286197e8d535603a25afbb6108bc92905f6fe76e9562bf8e500d2170cc0f83a127d26db23885d3468d590229c326f53d3137ec6ccf12d9a1174b5ee1084f8142bcf24cf8bd5a6c86ecc6fd11a9f015cb2e43b1239c2a07348a9f9f84d66f182a8ddfc9c5834cc7084cb178a05cfcd1ad572865a00545755f96ecda89747309d8b41b4b6950bac4c9d34e3409a094eeeb1403490c7fdad62a95033a6dd7dd39f6e50b8d7ac0d804763a6c65dc763e93b3bd5c349b5cb1de15a2269ec92af634f9d01e2031bf28703fb9263580e2dccd83e6a60ecaead2c7b5a7e317fbcaa5a8506dd3e60a439cb3c1f58b407bc3900ce53ca555edbe1bcd61b3446e91e03ee527088098c2880ebbf69302168fa9c6a4b1f1b49b0f1bf7b2ef61719b9207af64b63a77429646833c3b6242bda29c934eeef3fe05207f363b8077c54f8dac053d202aeac2dd91d52d6351cf2d7d28d9a4327e48ce70c13fb05bf4106b7b09c1afe6b2b14cf1701c48deaa5ed14f35a8a8a1b843c28548fd60de382e8774ec079ce861fae85f53108eec364a0c1177013d7271f8c33240151b750a5b6c30b2d10af35ca9f375ea29c1e24439b2a67d61ac072e1b480ae4edb6a98bc99a591e01a3e19c329a131c08e97ec7738be0af7d219a16efd91b39ee8b3f79aec3affba1364f1b8362d309e8d04cf7e7d342bb362c6f7e9d9402d13bf65f3eaa4efaadcc34d6d593a380669f861a2dc7bda0420c41a20ac4ab4370efd9d6b1cad5ca1f2e33e1c83408de9396dd7ce6c2eb86d54c387db958e4fcb15b185f8e8ba70fb6d56f0b44a110bc42b5ed63265ff8757ad4cd604ce917e3028a1efff72b477633b644113a79905f8f1192bfa383eeec556b213dd6a9b4908d835775a067eb9db539b5506ecd2f38d476e9337871d04338bd379a70bc3a24f254b4b45c22fe193664e8d9a106e9e6ac86d7dd408712561c727830599ac12ee3fc2a713ddd9e443943a80f22928d94e34b8227e3c91bfde0263011fa8c71f2019f33bc7cdc1ee194d4e2d75a683f984294d5ac67fcca5ac7e40e5f9eb309f193654bb1d2bd858e7821285c4eb4f66d844b00309d2deef37f3da896d52859caeb75743203df0e0a2206315a141dfffcdb5ef69d7506cd9ad060f566c778c86a9f3cb2b4b65ca641e7015704c374782ecf65ee224fd09e291d58d119fabfcf89e51f901cf0a172368076177263dad6d35d6ae34e9b1e0f0d5d137c8c4d787b78b4579aaab17846afbaf095a0d3ae60ddaeaf144a9643bf9e471a1bb82c30ef3f7c44e1f7a24c2ebb01701503b816b1e0bd438419003cc54fc18336ca3726e00f961e7a02cad8e903632583a3de253aefc27d7bf3098a27a3d34c38cde80917fdc241ff49a9c969f0fbcdecd2b53abdbc4e4803a4e98b64f8b947f3e0f4eff30f54a90cfc54f55497f89f036ab882d315553b6c01c536eb25cda219036cdfe4a2d56ac33051ab86818ebc9d99f11a67a69f17c870e7c2a1e35776c467d93af110581e49374b468d3c9d3c354bfac13910632a6f7a73c260059edfef61e6ae33b4af2c46f2ff992bbdde386911129b5d51f07c7d6e02507285a0eefadb413e583446ed6944e10fd891d1a17aa83514b97abc794f19e47e1a5f68132a60ce7821255756e28c4c453ee5c656d006559b8ab638dd0a88f0fa0b39e2c626b158eae4f4b1c3e73364c6bb50beae74fd0a71df9e345e14dd1dd2efdf94e27bf258530a2b5b012f47de10e27ef990956878911de4c47699344cb6e190bc791961804c92f6a6587a265a62fa9d714e04f0245e7943e81b0799f9a1f313950dd59b158c8c0ca7d707400ff67da298a8b757076e81cd64ff9207d0aea9a6dca5e57b61229fd6056c1bdde63d9b5910d5496017ab947c972cb1843f197cb6d9ad677daea885f1a8b381fe5253b54b41d978370baf651b306e9f4096e0596b6d75dab79c8c89fbb49605497454e26efb4ba67822237c7b13f0dbcb482f1f02fddca266cc570fb3ccd7f80161a26da98a665abd20c1b5fbddfda85b5cbb25f73b02880121120d65a5c8af1b9ffd5424e951bcbdd975be5ec0938e1e259bb10021bb38e51ea38aab4a3a0452234ecb6df9c71c01f10f9df1b0b4d7a0ca9e81d98f4bc28b2565267cab91d356677cb76bf2736b1eafd729022b1e1341441b46db81091a5588f392864aa27e0e6921341e67f8c08c0e678ca5f6fe94b4bf372569b79298dfc79a13e32b926adca4fb10329424d403621bc7ceb2cbf39ba33a342a3cce79f06db803115a9380ff5c074d63ddad866c36842eb51b298927a007031448335cec4dc650459cb0adfebfd0a3afa4b559615245e88a4729f7cb7ffc104547b6c5df2bc3fc0838446225c016fb58e57ef13ef5fb708353c69e06aa9e51af124c9d170c299b27d8f01410ae1df5b73cfa9ac4b7d9172a3b9a28328a17af92a32ab049ada7d8edf21dbd48e82063de4a3ac144b0d17f08d450fd6a4c5285c7cbcd2d4def67f0b7490a59f3312bcc0995757865bc66219d2736cf83a2a925d9bc4d2a6328b335d05b806a8a823deae553f6f6701264c8a1a5900f0a837853f067c41e0fab778c9de61975ab60142c9fd73b15367c0b14a2e82b755a148dc88d0056657ffe49ef21cdc883f3190c35c56b9cd378dd8ef9e106967bc53571b0142cf0c0858b30edc8000da1eb699c5bfde4e9d1f2e7fc3d94a916e3c358b82c25a8fad21fc0bd258645d8600562a4298542f6d7e31e23fd6064542d3b9c06d1c67103a48febbace43eba155a7a447dd840d594e328c49283c07f222d25b512863b7e7210455ec32a1d6959a49456eea316081c41bc916028ce42a4c55a5ed2c901f1a9bd14e452f01731b4ca2df1077c1bef36c753c5da39f459a7b2b465dcfaa2e6e443a4839b5e8672a0be2b75adf2a2c8e9b7cb00c767587e741da626f7541d53fb6c61fb54aad2b047d00f65330ba24d04c37a205bbd19905779aca35764e71e56c2e38b28973cdf2ca184e36ac72686f9d64ec1505a5da11b6fc7884a142cc1946d011439e7d909e21d6a7c64a41bff550fdaff8bcc287a3df9df72cb4dfdf7209bb6a6fccc9326532f0960e35ff20bf4b449ccd9be7408a94eb91ebe5e26b09dd452734e700f9deb3b7b6984c44eb721867f81d3310548fbcf88c085c7fa62ccd388581dab4d966dcb23ae3a1090ffe66ae1030671fceca0362d503b6a8915c4b855b2023ef610cea494614fa1b6027863ee91beed489825742d53d842dbfcca98e68fd520f2dcf9f83dd1b535e7012ee6758ced513b7566c376474caa18d60a002f1778ef90965c0d180575adca068ba87eae9fa7894b43976031780192869cd4131f70f95271596edc57bf2ac526b2678313f9241552579efd7e914b3d26c2f586338a59ae2636ec47f00b1a461e515916168a3816d142b84142be7f14de6ab855b7d30b30e641974478c61d852cd8d385eee8baabe15134ac672cbe85a3a8856fd0125e6d4836c5778e482efe06c3903983f77615e8937e034d18b34aa18f34b0be510588194c122f1b6c746d00de6645eabeda5f1b762d3484463c6bf1c7c1413ff0c59da9d75102c2b95e2347f06f186864348d627a412aa78847e95ca985478f84dc3dac6681d808887319441302b8c9825b094f8cf7388ef929606f21dffdf4d96a9e93e80559d67ec9e8ccb9ee785f213f35387fc47400e25dc4d1bc83d7ed8c355e99b263096d547f5ec23ad3b9d5be03a4a7c04004d514bbb20debae908de4197b792c7fc0787ce90650160fedd90347c63a608e13bd8041931ecf79350a29161f948545f34baed613dbbded887b00b7d98b7eac890c4c5fd1f27e61dc2f2c9ba4bc0c2dc1dafc0bfb499f0e6404b342bb009d102cf71f60f2b51d4e1522a12fa33048b77f9ac9c0a008084e7cbbfd3426510a92b186174de1ec7716b99ba2b5669d52c5c8764861e047d2cfd23b8e0442a03a12613cf10fb573950c0145de4c35186a59d241fba953e8ba0fe1fd9a6bd0596ab210d4f0de5dc886b38e4ada424cc9dfc01a89226db83ab3857fff744db404effa98672a6c3dac49ee67117e4c4a19ebade5b62ec0c7d91727782732f54e21c98d09e7886f00ec42891d1a4b22a39036c05a74de4ae0f4c72d570a0ae9d2f6eb55e0e3d4e52e7fc2d3ad6862f34641623c036cd172d19af7d7dae935d72aeca5a1aa5221287630e363820d6118663d3f5ddbb67452e28c77d3243f6aba8607bcc1a7b81dcde00e3aedea562224a79607acf8c5760b61d4b801ef5f95c593460192d483528263b7667b6782fbf3da5084fd3be320f19e0d2391359f70756e24c8e48934466406c0c1446c98f591064d2963288272cbc89957e5a7ab898efb4cd3e13ac3d7c92141ccc6a6bb8f8118cf2748dfd0646aabd94c8167cf5d00cf12a8276a0d2413dd63e8e405dffb331a16392377cc820f50b66b633d612c090dbde7443a0710328ad3fb79a12f1394c3adb9da4536830e88f3e8ae60fc35211957772e7801302db2dc38d0c87d94bca5130c7e47f04210b3280aabe19c8c0dd450ba130533ef329d515b881585d3f8f9b06433187e251f437cb2046a9b21b5554fa68223d467daeb263c3666c457f373a3ccc88cc9954eb0b821b323828c58c9fb8f1d9092bbf12f8fba061e1ad08a76d281f5ce9f56f5b66c06c2389eaf746f8bbab82210dc82b374a219a4f5fece714337c4ae0441d1643020af7d3953c39527bca5adb181ee458de80929937d0581dae29ee3aad8e654c1a8ea6fbccf4a2679fc6c75bf338f69633d88fcf2d6e2b3923efb864d823efa5a2b8b86f55d233c922d87a7028047702e6a992e7aa143377799be0d4b5f36541c6e8419b765f42942480a51cd5623061ad58df4ba72403966f7e5bd95b4a0d271ca57403cb7552499e013ec7cea6cb6bc3ac16823b925d2ce9eeec1413240b5c511ac27793c4eb7c02e3533dd31accb65252de6c1869f10076c341026ab68a5089948af58650c05c077174fae17959d02a5b19eaa946692b4cda1c0ec6e6aeffaf055e210cf0224385391f69bb76da5c3db9b0cf7cdbc9d8a696931a48ce542b7484cc77c3d1cdd25e8f4a0da7335f3c57c5eb88df6e20cd1c5a5a7d023da65d43cbc6974bf2f854dd09f8ca0dcadb0220bc58d69a5781fc0a1021cb6e357c9a6cb88e4833a6c053c904d9ba29c8324da93d45720e8556d5a0a5ff01ca6e91ea81cc371d1f15dcaacc1e033f09ad0fbdbeccc69bf3fafd15758af2e47dd2842a2cafb7d742baa38858bab449676d91a5b65353ca854bb847cb35679a06ffabc7e7aa86ed70e667195f7f290867f2c4b2ba9ce90bd821e738db9ed8dfd3c8b5548c2bd0cf2c3c17d7add97e631597ef155fd8c9cfabba9b107dc27b647db41c20144533d95e3335b79a77a809afaea900e61bbe1f346875d59524fc1c427e6936d42d1303839720b103cb74f00dfcc878bcddf289ce11d081814748a23293457a38be77c46d09aa56558da1844d381bcf947a5f3de1ad42ff80356eed96975dbc22f67dbc0c7afa786a3800f7a88ff04202b3be1abccc6da3e3fb870218e013fd3e77580adbb4c9139c8caa59d323769ca02c17bc900d1a083d2ab85cf6f90242fa07e0741bd1e4f8450a477f8d87e0c7a1ecd3b453a8a6587958362d1e2150a94c10b4c34cc204f6597b5dce094566bb12df6c8c9fe5c76e0b9d6bbf3acacda43220ebebb6c9f46e586abb7ac2580a3248579f3580a39558c1171f94021aee86846a53f261ff08a62aa5e8d6c2b23605ab87360464d2e9210566b205d66d85cbd64443a541cfe42ce8c0abaf08b7262f8b0764634df90efb70306e8772d15cc630a7f98fe379913786919ee38ec021c0b753a8cec6b1be16a79eded1ec8894a1b2b28ec97690e4cbccb5d2a8c69a8550594c4bcf082770d94edd3f8e5c8ad6d5b6287481977c3607b30a8632c104a1b9f3b56400389ef633f29ffa52a400598696ed856be2f968f47033e7a80f345e1f83b7745b9feb6f433797300389cab06afb8caa6df6963dac2a18eaa46b53bd945f5d8c4f51f782909b0059e3dbfa733095b403313537fcb915632ca28efc1ee5f1190665a841ee876717edf4d257ac7e1bb6ae78459469fe5e03ea183bc15e4c4b3dce6fb65966060a9c246bcbdb1905c109156014b80d8be4ad01325ea6bd2761833b92cf66c4116d56c24a9a3545419fc7cce2d482ca2f0c5609475da49ce90486779204a184191e8cf0863f4862e32d2d09504727c2b49cba72a0162707daaf4677448ed89458558c2d2d48aa02fd8b1f9b8b64ae8dfa84d9bf23d9910d6ee1a8830ae2ec7232012748726c2698633ca1813e6372c37ae974c7e39ab5ef3f1ef451ae65539ba5fb336cb2fe3bc7f0f95f0a59efa4cde2a59409c5025c17f29455ac147f97472d79083fb6909a898a7326828856c710c3ad0414a86dc4e2860d5daa8d0ad47cac91040ccfccab201d136875825e32628c5cd2f6cbf31b3ff70e7e79c3183e6a3c7d07019ce009d8f06dbd80477492ce55d2d2c85a970813121692b589339917f8bff77a8aff019805a05cb1bfe565b024f01b3c52312379ee4f53510f4fbf631f093e62637318485e9fa67552e17f16b16387423a6678ca7a89e30e35fb1e532bfb47977769820166879c1d5c09897ae07737c25be80979f2ec46880a5f37aead6b046f3d2bab11d9f764eba2837dc7700c0d5a699149ba44b5e4c7b30620f015eb6b3f71b9b2ccdbe5c8b32d8f2cb08d2c3136cc69a101b636087139920385b58df4fb0ad1e92f29a9b60e1ed5b8554a398df40688ee4a467b637805e29d589d001de29a39575483a2c11770db1a3e39825199e0ce0135a638896c0e74c0b42eeee635dfdc36b1cf939ebec13a9d9a9c736f9349f51eecc414724ef342d16f7997560ea58f752e89a5a8a7837a61dee83620d12cef7ee44005fbf9899e4a6bc150f43146a4360772205d9c35c1d9a493bf5de49ec2cd644515631ea2233962c5404e035657517caf15df8b90d84de868c366371d11ace09e04cec6fe2ec007a15b757d4fe7d056b98a741bc05f5988e00a3f51cf2030231002c572c8e5937564137f4056b0713ac74107c70d686c202edb2e2afcfd6149323ab4bef9337deabaa104ae5cf527c8a5e2a4919f224a2e05f93f902561bc8c69136b9c88efdbd83a1fa215411899901d1b00462cb88b884be8c09b7b62b2ddb8b69e1b4ea51f4539d67af1b3e2a2d65549ee46c7dd1925349ac5339aa7274e7ef465c2ee724e743a60b032f929cf21dd65ebef253cb62f7cb0a5a2c2a4f34b62abbd7545b783ea5926ee9dd687e4a0412e938500497470c4d97b3a03bf64ab039704f212516ab943cee137411b585c630429b670c43b024190a609ff98ba812ad381d3f399c51e99f6bbc19b43d438ea9114aa6fa6465939b1499f4798aacf8b6fda2edf49eb72da66b0459811b7c989025ca4d5eaa69912f9cbdf7abddcfb67279bb9aff54467308079b30eb995f3fc9231337d54be3dde6c59e9963d6d1d8eafb68279e9ce94ae4dd48c5aa7b35ee34564f06bf2c50ca71f5f0836e76e168e14fe3aaeb59b5ae90b01a211028db9eacaecd122ec4761f68e13bca442627e2352aa0d52e4510cf96046de253a51cbed07630a78dca4bf4b07eaca0388c45c23a4d908c3f1a87044c76d24c40f6156f33e87799ccc90bc801e2167e8fa7fb376100816d5d2f08facde413b82dbd5ca367fb9ac2eb39133535735632cf774d9eb509d82440888d2f37015caad31f7c8a4ea816f7d3584cd8f6e6b76e0fef8a4e8926018c17d686f7af73c65cefcc573755c08ec5b4b19d796a7499192655366477afcc51339982aaa5992aaacfc7293ce289c5b2950526735ab1b425ac942b2e87383d7202cb663ab6cf16d702fe58797003047eefd08d60c316f295d6675fcc3abe7bec85275743d76bdf5a70a380be97fd957b1c9668202b9997ebd75120281e9826ce9a3185fcc9559474a923ac4c29052b7651a6ce9710f30a424353004e42d8f370243fbcc1d80b083064ceea8f096790015de6d0c1b348fcc5be85dd2962aae563cce0b5d2f6bad5372c50a314a4dd0bf26139127078155aea42219a5189564e8d99a48422061c264e9e37958b557fc83e7f554b8dd9be548c4e958c8dee4988b4d304a32407265f52f7dd97197d04a7eb1a405d463d1e7b8f1d00416f04318887c58b9a39d678f712ac8fec89d8325b3583748ce1ff8c51c1bfe794085581287eda9d45cada83b1f3c0d6d15081ba1babc78952f5bdc2f89322808ed77159bdbe586eb51e5ffedda1e9ee2bcde3ff410a2abb6fbfc84970d7501749e8aee2c5bb72ae5d364cb28e44585cae5be90f7f634064d48f655b6c8dd2c776fd3a41fc6c084bb0e04f0f7b91d8dca59265ea9e2d7d94c233b32050d37d23e9dd35e36c0e24efbd764966507bb373638694eac7f1faf7ac9814445957b20e9e105e715596b0346cdde5205a0caff9a78bb66c63f1ef8f52c4203f8c879920d9fedbac324ba14d2c260da6a8cd4c3353df879627b615dabcbaf59e510eb9531d8f00b2de41e0fe881a1cfb026248d79f510a90c224214dfd5a79c983b714954fd86f2a3bdb41b67d73985f246e7fb8d504e74f9fa6a1faba4cd17dff12b62658586db59053cb13ff281cad17f5682cbffccd77d75b6d478804f8c4e3bd8edca589ba6e895341ef7114cfaf46af8404249e0109209c68587b70df0bc12ac92da0b3004cab8bafd2985d68b820552c6346a44f55828f5b26b041e19c40a602263e95360e19ee89600f88e57a91c962c99c94a388fbc89c28d44e5c6450219d224cd670ff068be9cc012819a1bca25109bfa69951e3b44d0a77e41a7e3e8232d54ce9b8020c0d2c23f688a848c00f4a1188fdb9a9043b1cb4a397e8b8393b5e41aba87f17dfe4d2164b4adc635281ea924ba31ba92d0dec558210dfed3a020f3125e687d77e7f170af6a1f2854cb4b9f84c20e5877f856018a003dcfbf6d3325cea2cb6962f02fbeb194e5a1aa52a733334568cfb43d8211eb0369fcfd19b0005376b541f61890508f0025e2fbd18168e14700c0646fd3185a525a8a839c102b988687a7cf53ea2983bf756192e4f32f6f1bfe8ac4195fff0df3d5880be808e80acc35c5ee7e34af5bf4d865ba990de4740ed193b886c04d0904917a961f79030be491e936ece6c2f8f1296846716737bbefc6b6454e6e32aa4005cae6dac17e6e3bb730f07939d7d1a924520a682643c360006a1577e01922d8dfc8027baca5fd08e20c473bbb446d6a5bf45f14c5273df8ac28f6262a2cf29304f5b68e59c3f4173b24cdb00680ecedb5e4a3d2334e9234b91b5be3e8f4724e5fe31047e78b8362623ff590949f7fb853afb70122b75b3e586ed961ab744fb9fd3de4fbc1734f7cc2f39620ef00ecc6ebe47f4f82eee77a1360abe831dd07742bb849c0d099f1ccf64b6e1bc8c20b8d031b65159c29b8ee49f12f3f2a446f728ab81a07f09d759aceef9d2cb3374b449763d7c43bce067049fa672e49aa2eb361be77576cb9dde3b63426f7866b2e8a0d1bc563d4213aa96b987991e3ce3f469c3441116fb39c0b950e71aea653c84e902c2419a42774ebe01f30f6686a73a3327aa1418fd71c525c10cc3eb648441325166dc92dc507d1122c78cd6194ec50f8648187f8e83820e88ae5057bf65f781e071ec3991cf7afc8fbe52bd34ba4d907691108da28c221e55f511beb04eb1cc16c387bcfac82dc77012d1a2a53e41347dc44730febfe2fd34d0929ad5b4ecc3522bf595a2f94bb6973a97510d3cdb7bd82501836e0250c6a51e8d38a0700fd22423e966b5c51a5a1b2d960cd81d245b8ad320aa030e4e9ea372eb49777109f874940248bb58f97ac92dd43135f3c3df1bc2854d7bfdac928a500de0efea03ab1d4d8afc93759b8c4e7fc5c5d6ea5a23abe343ea3741da4519fe5f9283f645e67c01aaa855e6da8f24321e13e3c7dd6613da0a1c12db662c2294aed6c7e285ea6e29f651530bfa5167759ac456589bb4713754dc61f20ac6f9700133d89476183df0543e825f47d0339568252c75e2d6728027bd2b4cd7dad04b903a35334c113d86dedf0a1e8501560efde8880eac643ac5b5216aba1965fd78eee9b3af5b771d2fba66a4b2823ee299e563861ecf87a38d77e4e1b75b1dc3b7c816745a25e38c35aa54b1d7940426700876100777071b392d3af44958f0e54e2619badebfb1012357ef6284f6edece5997b83473cdd68be9fff27743d398350435fd6b90a22b4cbfa3fc0f57ee51570d49474e1ca979130054723f32aa10e38b386586e61fbd782cf10300ed50e6392898100f81ec25553f958bee0b488f23036b2b61593adc2c3647218ca617cf2cd7eb570b067787bed1e2b716eb30297b5c44e164e43f580d99383a17c827bb75edd66dcce3144b5c24aa0bfde353148e6afe59df9ae462e696c63fa3cd0c32b15f6824cba2c5f7336bab100f77f586e7656d4325217127e2742d54c1e40a678ec80b4d19a4523a7c1f0e2c82206a99746a68d19f3241a9b0fbc02d0f70016c4ae6f9e15b9d359d117a8502bdcaa0b402f7c13da01b9283a4f7d84826871dee681b39bd8914cfe68afa848ce6567d57b873d01e893da164e1bbcdba21745ce603ed2dffbce4ec97bde484ccfa19033dcea486d61b8d1a5eba9b04b8e735dee70e86bb8647bea04458bf5659115b4f2dc6361c9c7e17d76447700c1f34fd07f61534f41c25a566c175d5892e16da6303fc3b46e2dd76eb990a18ca9af9b3760c53ea5936d8709b5eb1b5df741144c1b1b4a6ff1b625e063737f4b42d0f72c1c270beae6bb84b43ac51b3d0343fc0d14fbecfe12191131f38cd69233848070cb6f369550cbc36208094672025a466237a4bf20cba18ef731c465fa74b057669a20fca416cd0851838042a820781c2728ff3afa0046ee0d85800c57a5d04760e56a06379cfe65eed72039c4d4502558594a88bc4cea3e75555f00bfc29b7f053e819da2315f63e41b9692143becd14d4c8026e400e589a15105fa63adbae8389de86792b845374eed0205f60fb9ce705367924a5079fa2a3bfa78f9344bc03982816af6d5befa5adb42fc1c437e62b3ac4fddc04e1603e7b0ef7c55d453cf6731f4e8d7b0734460c6a85f7a5f1b231239dfaac3ba3b4bfe5c907c6a624a81660efb44aea1f8125b32f237454f526bdf0e7dd0ac8eb610a291eba1afff7e9d1ff17a2c0338e7de1fcbdfaef4f14e7870a28385433d1d81906ba2bc8566725cc39ecf7ec2680c66ddc93f2e26bfa82bd9ac8f04a0abc9d7a8773a8dfe95f9d5cea3bb771e807d391138a070e04d3417fe89e3b8f65365dce1f4864ef3c881f0b6691ba16838b3e69b92670f540b03562826dc92b02f4994b18d2708d2987e619ccac694408660ee4106bfb5f6ce645ee38b2240bea942d3b4f8dc9d3fa6f34f92827e189405cf146af15a2d655cdf420e9484b80ace4a27b4685a3dd99e5fecf6867dd17ecc6e9a6f2926629c79901e916ad5cbc2698c6fd8146664dcac6e6f91953b34593944d71c8152ded0841abac57e4a358dccba946362dd5c776755d83ff64c40de78489e5c3922af7769845c48c5b5bd1dfc4b7f0466bf9760d0b4ab17313278db13f9f978497e6e3242306fced4048064bb66e29955f4e85ec0bb9799ab0af865629e8ee6c389190923f6a15e424b2bc478dc1339e188ba7de6b2106642e21e0e31641a5a88e9ba270a75c43b3eb4a1aa80527e2691c2590a7f591365f701024b243654d838016ff74734cb0f559522d0f89e021c9cf517aa067aa1d5c15f000952ae9c80ece58533f15bcf8e5c59f65a99a116de416dda164d79b80dee2cdc79b24f8cf199cd28c4df96aac79b7431675d1d6581a09a5c78d2e8c5b603c86ee61efb16e76f7c15e00c320cf4b8122f91aa3a9082ed2ebb180d2a05c64509590ef7cc783900ab1bfaf544d5c571c40a8568104b5c51afaa749c9a46558e538eb29afc04254ff436ccf1525bd81d07425ef0b692e3992bdf888edb60e48f048e1d3e026b364c7e16323299d55f573e9533f59a055b07328746f03ad205f5f08781e5eaa32a24430670485aff84b4b097c4e974b9e912018f0cac0e01b3cc2325bc92e3bef008c3180b41f9b6e89d48ec97887a194402a72c6221aecb112bab656d3d3f76caf3c0d78c3a168f4e506f0cda79683552c65f852e46672499e5be0ddbadac6028d25365e8350d8ba68d1b5fb1a94eb112edf0d769f2e747de4ec31864e686a5cf0b2a9078d22026552e2392bc048118e37d3471753b1d39cc7a8cc8a0d1ddff9bdde7b8854305d8f5701dd191c718da751f42e9dc97ca0296972164818f5e1aece9cbfbdcdf29742ab8607e6c2db2dc8eaa9067705a178595ca23cc3b0058b22a6fd20c62002a781d9b82c9560ae24747517c995bd55ce99a8574f1f4b3a0551798133846e46b4c0711b998f5113bbc05609c8ece92229079cf6f3272e738d0a85558c20a5cc246781f80a20cbd1b5d524218cf65fde89f46dbf18caa3a70d71b4753d8564b1bf52deaef3f79392287135db9c2f7f10f168b1a6a69ab9edda7d97ec2be1f2d31c53418fc7aa4cd38a667c47d599ed91a79e97d9e94c9189577ba9ab08284143e6af3052f13048ca7f1aba8fca396474518302984c3a41667c89e7b56891ff96239dba70d565f4bce806faa492978b88033b674e0be4449a971cb7969617c2a6bacbffe9d089d28a873761aeea42e8128c9d8019bc6a7dd9df7b1c5f3cf934518dd88d3f46c1ff6eadd04c13564ad781dfb2d11ddf14c2f6d64141a3f23a614520db89b29a954d3a3086c03a50c49a362239f85c30cb00d7d7df4a321622010083dbaa0b7d70ea872622255a144a76b31826cff21e9dee779617818203bbe46e45540478ee249aaeb90357edcf78dbc0df1f61e4a03c48ecca82c40bfc5466eca3b8a0444d637e70b29904b24d70a65baefeaaea9580a26fba0b550a8702ef84b6eceb8210476fab43eb5c4048576a7e0b522ee30ee0caf3d372e863141a8bb85cc7eddffacbe38bfc120e9b9cd6cd1752dd53d01afdb7c91fcb10a905b98e2ad39654384a4f6a04babc9350869af7bf95179e932d180a48d4fcf26c063c4af15c9b4fb3d018b18be5aad507e8d30afffaa9abcbcf971b91fb424d6dcac8ce772e89396ef3cd1e634d32169264b9b3365ef409800a84a1dab8accd44ddc3c223d40a130342fbfae768bd30dcb503c480b488d784ec0ddcd8659fb8a12c584631a339cd73b99226b1e94a2859406edf14eaad36c09a05572f7350df3a5bf8e7765f286444cc67647b10ea05c05a3c380722dd8fbb97623a09b14de6fcb7d4a803255cc9cdf8b031265b42073723b25b722347a42a52bd2a3b67f5db29ea6411c9d04c9e130e8b2e43c70c1d4604980e5e704e705295d5b9906efb1e6500e355c397e5bea63e7716fa1296b92a3dbd39f5fa08de79290c67274fe794e595d08a5138099c5ec8d848431c41ef66da71e6dcadc047cecde82ff9419430482e6ee9af350b66f34677e1babc03dfa58c094bbe0475ca65b894b0f8d5b3c8bcbd4424c6d93407c3be00618e46e6764fb55fe755c2596849f45cd460e3473e87909fcbbb4d42ee4a0c83e54615878065d150b1996952fbb04834161a6a28f30e6a0bc204f6e4a820acf4f2717b97ff83243c51c87f7cf7dacf325157f28090f6c0dec0d7623f4e73beabcb6ab45ee01e27886e73c5a4fd29071021cc08501df672adec40926cb95fc0e558c66733f656c41f0cd784cf3d72edabd21e7aec647128d57942f5f793a1b441a323ba054a00e5edf40fccb1a9a0b786e477037bad04e5f360b5a561306b5c43db62ddd5b8b31e633803ea37f725bf4817514878be81ef6c23dc7ea0346eb0d4f1849039e2059ff6b608cdfe72cf90390ee6ff34371751e69fd9569a145f01c0c0b505db663d94a0beaaf2d9a2f76dc97fe87651359ca8cdf67d6bad5c7d0636c34233f87622665eb70c704eb838250af659b4484b3af4cf7e1b312acceaa74c5e80aae2b0c10ab0dfca3a1ad30ae3311fadcae4c6ef4060c95516d53fdb8179b9dfad7e4f3ca41b196542a913f87d19ae9020c547a4e437b71c7c676ce7f41927b765c7a8ff5daac4861a9e7d50cdbfca05c7dfa5c904cf9c45150c690c17ed1c7fc149fcfd3069afbdee51aa87ccc712a444f827c3b0e776741de609220098bc85c9a691c2cfe90e72384c4ba6a8f6292d982d0a8dc4032f475b880ad2bb23ec5ae315277b7cc08d3138ce7c7958b5dad1966d545e1de8eff4ad7b085aa279fe76d1deaf50f405689f8348e8690b95c6ca34fc003f67248ad3fabc4da2f8ecebdd04df62a9ecd8979b11e2b6248ec5a626b9f272ccef83e874ab3c6b737fae1ac12d0a192ed8d811730ef75380bf9e1cf057ba3ca6779b944e915fa3d40859b761d6e5fbaccaab112b6a94ff465318120d3093363df1424b1df07e24dd41689db5390884335c7a763983fd30d16b52aa793412e5fe7812a176a46513829ebbcf9075a3c7f95c722f3c8829c999060ec2c01a9e2f1798ecb63ad8d5652bbc178155aef518f90444b8812b411536d33200d402f366421071fed77a038d199fa4ac6cb8d6f4470ba5a5740b25a15bcf4876a52ea09b69048b0e93bf17f0e5e56d3e09622e4195bacf413cc3e6d9ddedfed024ca8944034fe2874438ae36a86de4cbf7857bdb5ca9fe3824ef4d80cd21a71882766535723c68093fbf59638ae85bdc7ca64f77af35f4559d9053eb2362b901d80541e5dcacce344f471cbed5978bc183edd0cca745764ee925a71aa49d4dcd9f96fb0437d60cc307d5ef8ec2b11100a0153717e716cd9fc3792b99db21a3b99008c765777cdda8cb5841a894faec47a3ed51e3b218467b99a7772f6dab9b02cd96ae2f347b404b48dd70c1cebf01fdc9de012e8ee5b2c2221155660f313c727e3d0a681fe9f9c3518251a186b3974fc01bbcd26772e7146e0a43d9e87d308144730cc81df30d365308af18241ddbe401e50b4ab8723deab1d1f45834db460716a936e3f1c94a32c544c4cc14745d0f569d0db828c211c17400cfdd565cf07b78f5e19db5a2bc2b5cb4c1d13ec092976a3ff24a9d5d455ade036f4ad1dbc2c62e4ab9f39bd739fce0834c153c0b5014cf2381e9bc16be05445221feb75af99fc1953b0eb5abd778e5d4fd98a8c454d3d2ec933150b6e5fe410469379a3ff280833c3eabae117016c7b7e07c0d3bbf5107dc95c611b327d9f32f7d4603e5ae24f26667d3ca1a259eb150f6d93232954469cb0af0c9fb75fc1de5c88fff02559897bdb3e08660d8f193e1b36ce22e3d3172820788cf94c5315af1695e4fc2afdbcc4cf1d4acd0b0e038ff810f654704969425b63948f28f3f99a3b18631b3ac1100754500c9c0990bf60b3c0545279aa2e40cd30fc20950968b9ea9d8ef74de4060fea7f73d13c1d9a22eb5131f7c9280ef643253000f0a0dc36989629796faa310a162db70851d7dc9602f6e8c30e72b039b37b915b1c2ad9fd94283861c5b8a459595edc7f3e20644dbe56f6fb172db68bc5ce2257601925996f6ccba217fc8ec08b578757d0c8cb71c82262df975f9539eaf28688554cfb9027ab7083f88b8c3aa090649f7da599f01a7ac9d4313ac87da51be45c8cc735483db22a7bdbbcafcd42de4000a4f3adae96854bd1cd5edf46af81badcc7f9255afd6a9b81d04a6d64390fdd5adb3c8ebb3cfa168f32636d3f46dbff43b056033445ec4bc2471d8c6aef6fe3b7211d6636a5815ad78d8b1f5a10f3ae70afb5ed7cd19ba127bb3171a0df6cd0f26b7dd1ddc74c678db9e0b5fbd4f8fd2c0849c10d5e464f8daa0112cc97c4fae01b8894afc06ceb25f8fc4552632e3b454c3fc600e4bc3aef6b3cc98a7497ead696d4775d3e88006e7aa1c7b13d278e02862977773125bae868a9809b26ed131beefebf7fe677aebc04c8ba68ddd00b19e0e24ea69af6b1564678e7f07d5f219709f688e38c376b47ebbc7878014382c95f876ff19ccf0b92eb0a13d0eb8c57ac29f8ffc23b1fdae979f61513dced5a7e6550d9de0ca7489c17040b42528bd253457e8f7acd9dc9d6083e9edd172cacea2dfa9a95739b320a3d38310a18782338af093c7935772183959a981a500ce9f53530177c8610bc5b3aef050850eecccd9dc414c633ec798d8f3ec3a61893e3f15f17604fcbdc22995f4c701404843acbc4c3462ef107a42548bdab24ab24eb6a9ffe82c457b17abab42e7024e95db1011b854cfa64779accd3cdb60db3684f9fa468d1151fd22e9235439f993a8f81e435b582f78d08d37ba8693e28e6186d0ae93dd53c25a8cec1a8db64dfc0905e622b5f48fc58bb5bb59ce6b1a98953c79893794f0dbdd0a3fe274f5dbc8941887b70724afe6cc4d9642dba8666bc93c205d88b8cd8885b8a15c314bb2e8f9ce3555da790d06dd0d63e05a1473d2058a882d5ebb4ddd84344b9b147c858c3a6e0d79798746b97ad1e9615c35678ce7a27c0097a379c8c195d7360ffcb9262f5df4927250619153330bdad318735321e0e28c934a940a562d545a37bf24fe05ea07f318cf0a048df7b54618aafc7ef8228f9bb8a828af1219111a479ae57cbb604a850489ec7a41462fc8689bef5fc7d32b76ef31e538f7a5227b1c33fd6ea4718de39033910afcb5379262def1e27a30d4312542d665bd82bc833a818076e7e7d28a5b68894e3e26d7078aac7efa13eb39fccb99d1f24ec2d0c745de8b1502b09daec869c22cfb69d901ca1f2ad1291870d357ea240d48ae9c2889a657d0432eed397ef740ce1dc24577ec5a8fca646db539c172d9317e690de9736910144ee5dc0ae932b2efff275b9e2142abe702d0d78707ac1e12776226cc16aa54c19a6c225bb79384c8f367db237dc3a53955330b7aa905aeb4ed95d3ada82f7a73f3b6e3945dec7a2bbe0baf4578af4bac43c74d742db2397450a1d1e902e6fcdff9a89f88907eeb2f17152c756e21bb97c576b3c49c8338ab41a595ef224eb9f3a0ff2216cc1f1da72b8889a42078043768b632db03d8a0d517f85e95ac7ed4a5af86c3dfd16d74dd1cc6498a32dec157cfa2cc07f49564b15da8dc1765b473d2419f5b8c201363ab7db7a5f4ab62e7542e6f2ad10239c06e52cf1037b772a5d14616c071821b4ba6d8c642be77a14db5d1230a5f48eeae5167b35b8c31341ee9034baa3e8258c85528abbd14e8fbb89493ac14c5e3c0153ec5bad80804abaa298ea55060eed511ae5f02d48831615f546837f73373d67788c52d7786869179c3535d276d595e65b4e1bb36cfeaa62eeed3e2d9379ebb450d75c97879b30a9f5e928f83d39c0faf1e07534f308f7155ac70bc8d348dc019011a22401ba792738fdea51a6851ffdef39ed9816401add0f46c0f0c416e657324a8d598da257fb0109f0c983557ef9eae65d2b5010395ff4e0703215df0d6c3054c8ee37813106c079139c31b51125a2892db2faebe4d80c719895bbdca7addc4e3d4cffefc0f57374302f06126e0c89e4e2906043d0cd75c8b9c9c92348c185b80b38d9e8d04deb79942b35d82fa727a34d0b96ff2cea12dd9cfdf436bc8bec3ea27a1ac18ef0f6d4bd315b45421d918d6dfbe5b2fa65ab7972b178a322b90801b66c113149d6c4164d0bf884e3739c1384dfda9ae8cea92e95c343f4cbb72dec2ea6d0b9a88fcdfc32fd62369561e1753ad94d30ad08ec20934b9fa03679f0ec25d2364ea4854789da784345ef42ed294bf6c064309d3d2c47269c7a4f0dda0b1445a6fe49265e8f9e46ddb2d17eb3e5ac85c3f3ea5a1c6fdeaf75601eba12b5a85b96490c93f8232127c159a994c107bbbf3ae43fdac0429edc819edc51af73bb82629961e228679adcbf4ab7f7b56bf362bae7dffb242d206248ac3ea8850084b9e87c642815351afaa522c5b0803644c8f277e675db915116fcbe0e869c32673fd3e92a6e2451101e64a2bc05e3f991da5a0b886c0c1238f4c7725b24dd631f027c38f4aaf2c6643dfdd7cc961fe03459afe059dba5bc35cb71376e76611769e3333f81f604fcee4248c35ad649e3a1187b81fc1b502cfac75363d35280579d983d12bf9a5f91d88869b3361f1244d8fcd6829598ea4b2577dcbe7bf80bf71aefe3d290a6f9dfcbadffa944f0f9a3a655f962423a084f179234d66a0899ec7dec5cd1af9ebb265fc12673b1bfc4c777cf41d7bcd3b539f6231fead70b6b79bb21d9fc77ef6a02913ef88507488586c7c7cda781f07c8e6d1385f37e8da1a3f9b6b731f977b226ab4f840bdfc8a91f614550bccb1099c8587c12981889710b9a576ecb241cbcf217a523bb23c1b26e6180f7e0b3768d64d212a158c59ff1345bd538539d2efac99293d3321a786988068ccb02d5410fda349568a5cb688afd1bda7820cd16ee0f249ea0242ae316dfdab80f90f09d7e0023684347b299b9580562921aa6b15009f0a9bb911e8d6d609de9e3df0a8cb6a7675e30e76594c4528bc8c7c3575b2b3e8794577cb42d6e845a6fdecf462c106210fc7275f051e4cd42b4304f3f6dfc7690b3aba7549865bc683fcc1a45a5105b9f08cd34911cfbd423db8d6ce503e2d916e83205578b9ba94c733406c50d79508ad9472d0a291cb6505f706d5e9c2ca24b5fa5b77a1e9ea2c5f390a1cda2ac76b8adf3e8e4d387ce408e1a3b970fc7bbac9493d2961bb4dff6e44ee682b175454e133cfa74f2c679afe605351ac6e0e3d3656477fa2cb72f1bcb9f8686ed3dec9cadd1e3ede3efc1462437716dc67936f3ace31d033f92ddaeaeaf219acfda4e21e67f2fd81a75216d69e1ca7a8f1e56232d21e366b9c2008819273d944daa34ec3e0629283c1b11657f66947a4bfd6fe4cf40c12db5e3fbbc8d60deff8d99acf4997322c685a06bf3d7fdb5a5b3d0a7e92083c8716a5a2a876dd3a0181a858b9de4c4f027ea33f70113560ecf41d37f33a7fd0fa85bcfb77253a519bb645457787528fcc78181fbb2aa5552a630f1ebf5443fe984b3f5d45a0c13caf4ceb2906b11a48481eed5ee6259c73e62373451cbb5769d03760a123fd8bb01018c1cd780e1fd6b602d4326d251ff417913e3e935cac37d5cd109d95872f3cf5e7a11c73c7338f5d55300f7ee45aa757c164f985d1e5841d58337b137b7e047dee7233b0f6a1e104bd04537b99722e63f86b7fa6421186e40c4b0f8b8d51df25b9cfdb7c78a393794431e97ca8584b9fa7102e7c9f7eb0aa425cabdd49293953b90cb73a091a300e57d235ca251b0f8ffe177ae4243ba4b1b4ee085e3ab07acf5cd296deb39bae17cdd2ac65284eb35f0bb4158e33e66f80d7632156e3679b44b6d24773902e4e6d08033f7fec7d165c6b1b467c76da00623d1366828d0a2ec4b55b6197d24d6bee204763ffc5d226798b36258759e537e80adfa492e06b7a41f1c782fa2712d3fa20aee100e35001cb788486dacf0628637b320aac4de22715fd0fc6e36697b6f85184ae4fde5d2f0d419938756af3796b759c1de74983927b54fe14aa8cf9eacf98b213f02176371de3d766abbe52cc5b2f784ad158bc2413d2c77c3f0c83936de2fc1e99193ca97dcf49f1a41cf42dd2e35582eae612f662b80a92a26ce6ac44a0f1cb35354be8d3f8b5aadc95737c339615cb27f4147f504a767569379d31c8549ca862bb783c6974598fa253098726e7e5166f8ec469a8f39c0f3d37c6b8f22f4dc4f36b6c19aeaae3309a3d7d5215f99898211c08659b01b3a85c061d82c0bb079070f2ba22b31798c63cf078c0b06401e38d76e9683deb3b1719ede4dab2986af89128ec2cae9bb78a5203b229693d498676d4ad08072ae85e5810e1a84f4a2269c728175f7a5b93507ea0a62ff925758a9784104753a225bf13f0b3b19e13399ab330fea31631eb88b269e627d974851a1a782ae0e39b24dfd73540e6ade3d5ffad53d4cc22ac793cb5af2108ca68e7ef229dd0235b4a53fb47d3e4920e523c753efd162365a2cfdea5f878a74e6d28924309734ac2e591b69870aed655ab1f44a64325aa4950e13d70d6c67e2c6f1d4b04f4c6a50553a177142bd82c9b0d9bf9570a7ffc5d09e73c590ddeceef40f0c36481c52d745c8e0997a33525fe94609277f4d583d278737b790af638680fc59b525cc9b1437a1b8c155d60a11f98f16762275ac6ff71e1833caabf6228e6e9ec3421d5640b2b6b9f126bc20da2f4f1f027ddeca6de507b9ee04edc340744c2a161984c9346dc6c1c2870d816b16be8d606c722c18ceafdf0f946b19fa0bfe0aea7c0aa0fd662ce9985ec444c7a0efe1bf83f407dcc0d5924f083d197d2bb00c4c6a9fe4a7e0374736adc8efcacba8e0b274f99138593cf647fa07728402cd2c9f21e391d670bb908ada6a555fea69c78717aff7940ffd8323637ca0fd269c98e552195f89df8fa6b385b847fa9ff785de9682721abaf6625fa88e455321765598a32a5a2903231cc87418ef7e89233485655307b189f6e708173ddb450159cb2246fcf94945293772eb77eec7f8da31fb350b77ffeb3ddbcd7b74675df87659f257942d561fc8f74515e06bc32471bb5ad5420c577a153a1db90de97d5e98cac8732ad70934696542b9c6ffc491f19472826ff51c1574ad5c2996dc2ef3144a4b74743bc2bb373128767e4ad12698791bb07cafcbd048b0a94040b6b2b072681148ad82e0cb848844a672f485f685d482f3c400b73a41233aa20412a4b059a36751a1def7fd75527e760125b045eb3e103592ec68c21c8db3631ccf15911cd32a063442caf4823d2e9f68b605aa49b64eb687ae1d705a62ebb93ed221dbeaf12d4e4a0f52e30c5276db7a631bfdb528a4ddea1ebc4ba392191368d24a9c0acdc40ccfe8c6f683e7da57aab47142799dfc2551373ec599a4e635fa326ce74093b8961c71e49da43405611d1943d7846bc9b280e33870d39feb0c1a435a471229b484037627a65e2d45a43c348a5ec6bd75acff164f4002de34de5ea9d062ef6ca8a6231e32259c94d201e9b46b73994f63414274635e1dc31e1dc52e413b5afaa4efb789e447c3b592793226b6629f5b696150dfe70ed27c4674c8c46af6a23cc0a215c2848a6e83a86667d03a626a4b79d1894526f977026b0bfd2ca00e06f66274819cd7ce731b1676d91fdf0d91f7c9c2b700521b42a92de500b64f1b734d8808d89f98e419a32871cc86cc2d5c0369a980d47173ccf0cea9e8094b29b110fe7d0d9c113f06506b86603f71d53e34ad127b167079c571c2014b746353f5f8fa19f5c8b0441aa5960ebdf9e05fedbbe31326ed24d95a2c49e7c1349b3755483e4eebeb53cfd4f98f8749e2905d20906da83dc6865ae64cbd8ec4df62656092d60a6299ffb60caff8dc74e10a8069783a60eb8897233869217f8ea19cc6f0a53b3fa198666a4849741bf1585010cbbe69c6cd467bbc61033f536350daa0e6ec3f397fa9a04144879cbbea89624978f097e320eda28810ee5e21d715e07909dd5299cc70a42b91c9af36d58e5aabeca4b3c122478c9a2fc2aa3234511ecc644e7d06147ebf192661217c844ae3227f07a7fcda1ab4fd3b3a9cd3790acab70192d5a1aa2b8c35d413aaa7a942ada3fcb391ff8465791f2e70f4e8928d755b96cd4ab386283d1ec5ccdaf93c961ca8e4481ad8a93da0979a1f2e59c3c3a42d0b85e9cab27051812dcaf42808d97a90de444ea4464825568520ea5b350bd8d8c9c5479e89791f716f8db0f5b8e6e2696927d4ee0c8eab6cb68ee768112f59621f7e984c55440b2c46a76d98a47b3705e6cdd82127e7194c8809794337dcb7ee315f1702404bcf2fce005f5b0cf82be85b637e65fb59bf83e98720ec707fdae1b2687e66cb2d943e2f74fcce7052bc9b049380cbe143d10fae56148afd70350ed5c3803d7197b9590866c5d51b8b24db6035e3b061841ae70369a79cc316fd44987f6d8859845bc653636b8f59d718c5b124ced373e33ca15f0952eb2a3726df5f320e971df66f0d9ab5d84f13d81ca20296e518bac05fc382139c53d9fad929be908af23a4f74ee8c10a79f41d19d21df49bca53312102773908f37c82c97df31ad2163edf8b54079a25e70fd2196fee59e06db2d07c40ba50253fe44591aff4ca4affd8612769b182172ed5c723f4ae5ec9871845563bcb1da284d54a936a1b935bbfabcb1c537df61144c4e697b508baad159a1d281d71fe967376043336bdfdfa9f072c54bec635b0320a60c98c4e72254b1064a53522b3590fd7744d5c6d66255aa2d715bce63c16978f477955a90e8b0e0cf5d57444b0202f68b8386e71d235a1303dbd40fc18862357d06d725432bb7998f3e9f0fd265affc119f4bd45b3209ad0c2a6a244ddc21d2c469847eab2705ccd02fe649527007fba67d3e131c57d5d44f6421e04bb7edea01ec6bfc07b8bb8312fd5ae627f3d02cc87cd36ed7b0276fc997854fefd7f545dbb4ac555d04844eeb180504f34c9b1cb7edb5953af0a66f25866ca7eef1e80bdb768cccbbff3ebcff708f1e6bf7806f06982ae287a1122532b2465f1e13cf8bfdb824876dbdc4c1a966ed9f76bab512722b8ddee7c8892f9b55bf40ae7da66440e4633768278392a17c578c3725b37be83cf51c9f64f13b92e6ea581f204079ae00e8966545b38e8c8bcdfbec4d32d5694feb871662ef6778f658e556bdc2c5e8dc6cc189d6c050bf7ab45e39543012c015e863ca50aff19221e25a96d2bbcaa84548061cb0e0e2a3b0b838e14ab52aeaff6712dae57e63aafecc8293f10b094df8ab0f4442ef69fcc6399f3ebe613645a8ff61b8315d9b8d9843708b8fe96fa54256752291f123dfae1271b74f20c237471d3e7e106f80e710a1581a28b10a3890b38c3d5f2efc93719f473b1d25996cb498bbdbb3fd87fc4076af5523452abd588b7643c76ca59690a6bf385fee914a99ee56aa0fc0110417a2393fa9aa645d994fe45605a166d562819c6b58200c860126dc38fc77050e445918d767eebc77a9929948820fae36ee93190a192b52193cef214e34a16f94d27e6e59d0a120a285cdad6fc43d0c04b71083ed8b075a3950106ac40d756537aabbbc34ffca01475d1b63c5cc3a8ba95ff9964e21c7ef4bd4c8268cb8d8b746fcb8d5dc47a301f72abe5f7e19bc39f3b1d0132e9bd6548895fff610054804867893b71e275eede526496aa2801188822f404ee1a549115dac05d9cf432f66b40548a4c9bcf1c25416079e33e1cd2d7b9d8870aef54bd1bc6f440cce48722d73f05a1658f9491cd748243aa1ee5cf111dd013c4ab787abaaec28e2b49782b586d038d837d0e34fdea3a2d1cda39237aadc85952cfa9aa979d5cf25ddf6fb0dd1477be763e8986b056a376180faf93884d4ea9a83eb906961f8e5c655c0b4191367f88614f39169a24bbdcfc73b51b2ba8be79c65bf49a1b18e67fa6b8b6e1c1ef09a4902cd10a51ac3327728f9868d82d65e497657c44a8460452fd47728b9e2648ace3fef89ccfe8869f48ed8a5f72b1712866c30838bc78d090e10fa6aac002b4a2c55d0a9b4152449064626c94f8ce6f27d81d907e0ea266389b0da79f86fc4869b52728e622b398cdba3137d40a86c0e782672e721355f00d875e963e3e8ddb4b5eb308f5ad12172a5d24099c3bb29a120a378044d35579166b7c8e511fca757140db8e509a0eb20197e68d9f324b4277d014967610410a1fdbffa61416743d6ee9a59c152b3ea6ae193c28439f967987389e9461be81f937ad9a33b75d46afb5bc8c60768d78cf1339dd9724fbb5aad874a010b0c70e70307e3cc7e0ea8dea1e4ca3bc3987188415380f02901c3aeba6e88c38e224ab94234693f026a6535f6540214d3675979a1da549330a99a6c64d18e9e6cc42a2ade9fc59599776a370abd11002dd9919af97c67aa8c8f70eb0d0299ccb15b93acea24e27ee321cba83f3cf2336adbbc5177617bd6e35b4371655f53bf4b95059523260aef86eda2384a63bf1aabe130238aed38f306f10e558a7a5bb1d875a0e80bfa3a3a2b2f2151c106e1dd9388cf00bb994e4d7cab74149b3e81d64a355072435bdb72bdaee8cbfe0d12e91da4ec4d613fe93044b3187720bb00c1704e61ee3c2765a39b9bbf92fac2b110074a3cad7be790b372d225223668178b4f71a85485ac5370b9193874d6b7289c86d1e41cbbf0446f760604c131e57a14c9a053a4d34dda00b9cb844e2f9371c093d1f53c59f0b627005d5168cb0102bdd629fd4324b56532787ffb79296a128d4f49ed8a6ea5666f1f32e9d2f40731f839500d010de605094f229ad4667f039d6b24eb004b307088435653dd112baffbdd1e6d860f009b633f110422cf499d1a42d06cfdb0fa483528a096d5a12b74760838bd52bc88fcc868e9f7eeea8f02b762e825ed59533dbe5e46ac65e4d83043471caa13672c6b943449d01d8555cc1d01373852fe56b0b5ee987a7d676bf60cc3198b9b23d8c8cf285488c61ad2286174fdbb2981eed09a675d1cc7262644877afc84b843c4e929f36c1761bcabf2ab6e6bd866c658e9e4d16bd911e8c2386c4ab256f24c59c289c2e4357f8a7cede16f21cb2ca65ba69238f27c1919257fca139d57383f4cf7c7bba3066bea4815879204868f1cadba9e156871f46b684db9e3f108b1429bcebf41ced68cc06ab2239337b3b1ec5d9914015ef18c05d69f881fa283f598676f31907872d6e7455730f80cc7ef98ba442892ce03cdd939ea30179e175d87229e9592533e1b37bec0c63716b8739d6d1b9542ded71c686c8a4152012f76918f9e4219196cb1e0bf5ebc6032f67bc6249477a5f2c0b3707573b0e27a0b124e9f795835de1387df955c35e4774facf71015c513d7a29050cd6465519c4380b162bf0310eb99cfe7b3089fee44c63c148cbc352f48b123b30cef88056a033c7169fe64e1bf337e0af48968093fd4b16b02f9cd5e8d1679c3235b2299127b961268ca3f9711fcb1114fe19fca2cc8a03755b9a1c2f2a6725726eeec65253d2ebc48af048f2e21fd90ed4e43b722001ceea1b722814639fed77db5db174aa9c7734d8c061f12173ca668acb13f9edcfc364eb6fcc968dc2da6bfc914aa817c797fbaa0fa6b7fa0963c8c524f941773b65bb0d9852e61f80bb9d20dd34f10f63d86697848c02907b3244482391e759d825ccbcdc94ede790b5fea64778260944490404908d81a379c6ceb90ebe9deddbe7f9643dd1c3cd5e6da5cebc6b7440673f28aef1b65900227cbe009d12a8f2a9f4a7f1e0a63966f2f04b17d13bd9d01de8683edbe2a3b522f1512aee786290f9bebe297d9a5055103bd031c4f2aa19f32521ed2044eb203b0e26e3ec48b9a32b2c2d1c9d38c080b016b480a0f70e758974f0362af5b3c8e38bc17f1500ae5dac7b649c986c721239469e289a14803ee73d3aa1947bee0627610b95e54ad8317a24d9ce4eeb064b2715d9b00f375129ee20a58ebabe8583f4c3d0207616df142c2a84c5f5a04e2410e6d233ce4523e7f6259d2390c3d57e7941404202ef010b117c622c91e9d1c322e29b1f7b9c6f1ecbc3d03a889c2fec47fc6e825c06900643eaf505e2fb49bd96aa6b8645f500b177e4b2697360575c564dd4ac94db7116c71dc277e1562525b0cf1cff40cc7652b1cefa148ba706b822dccd3750a39ee414cad2a61832087343dcac150c92c56ec5f523951a0268ca81c11a3d973ea5849fff31a467e94834e3f7bc4ba455b3605137ec6a78189f9be56f5e0218c42892392073690d1d708e217d14a989e75df839fbd88341a83e9b24eba211235e5b9f6e772ae502c7be918f641ddc93dab2519dffaf8e085fce9a0b51d1e1e18589f32be27117fbd6a65b641065ea1a31f6fab857fb5feba64c9844d33d0f8a7290995242643e9afe4d09f6d71f7155f7416bfe498c66c0d5a9585c0a18248fe960be0cb4a512b6370e7a6eada0750e86fec8cf46f52fced349d2f0f8fcf0147b7f5f009cd913b136f9e1655be3c514ab6c009f260cfd3e3b8567522d6475249c8849b9ae29e5426b4f2c4d2306e9057f202a693b6e66c22529f1212f305865fea766e402c2b93f6a072ed2f8cb9f56428141c886f8100f5af698b5cd25f05dfb1f5d361955d1309b4da8a73118f5ba354b2b15934f72364f21844ef36bdc7be7c47d17869704d0ccfd1a5b4477de4f040d0c540359a5fc87d24d69580d2d67a781026f92c261b8e32c67221c976c2800abeee4eac24ea61ca651b3d17d1b62f0d49c27ba13d9dd2ea041cb9ac98b2024dcba32637991eee52ec66efa0d53b7324d5447684af99d0d5375361d84541788ac7bdaa2301317a06ea74fac76c9784b13b5b4b12dbc7b7bf4f5083ae1ebf2b6fcf5040fdf1044895f711b0d552711c15e4e03190563d8c565c27c7d1ff40f20fd8eec5c418396726daa95f9a9c0697cfe80e1961e633b283e977b98208dfeafb982c9de5553b6f7f93d833c571da7a6d29d013c4c62fb4b49716d54ebe4ae768a6a9e84de9ebe94d8a6ce4fa611be91525498c261435ab004e064a028586f23a1139feee40cbe9ed2e9165dc36663580771ca4eb84266d022289337a7225e0980a52b2c31323db7246033d23e1318300deca36ead2232740a362eb4d99f305180189a67d5057d2ffe5176258c03543e97477223236836fb5660325d551b72f74cfc47cca3655ff40661b854837211206b3e9c5ffca6ed9556faa9a055319c38648eae69b1aa2b08d4c27641e0d13a3962c250eda4defd548bb664f736bfaa169c2073b3dc4da143744b8e4bc442bd4fea4aa72387e680ac2cd948e2fc630db69acfad83429c0a14e7475d73339e41ec84730b2997ea3d8aa43aa8cccb90186a5b89e031c67b4130a12856db940dce7846ea0dc3c7f7e565111a43e26f29586bae6358668f3fd8ca0cfc4cb83433d21b59aef4dc9298a5c2c7bfe252d5b4655cb742f3ce898cd89b04b9099fc3e25fdc7c9ea4ff74819d71cbefb510901d24f46ecf83bfe24dca385a59e9a4d56992b27f57beee704d1485592cd9d15502835e1ebe4d158eb9f49552c497f8626866c5c9620880894a734c5fc8b4007f962b27e664546ac4640b3824892c6f4639843dd3b4808b485660e1ffc91a528b12b9cb3ba275c28068b9917e693401c2eabec2ab7baf833cca86c6119d4eeb9db45e6564138b61ff95bc4400b4715af9c649f0723d15df7678e1d89e95605dfa501d34fd9a0b6745bb8282bf6e5d6e746ce6c9de46cefc09b8975c6642873e8c72ea377537e7209af77a94c251b37a9e768d82b969c5979081bf3e5b8f60d5610b6b6e8f2997c408208da88891920049b653b3600b102da89b96bd24b8441d2cb523bee56a9c62df33695a8179fd0328fa8d7744834df371d704899b0a1566c38140126074e67532e1a01690d6c150536775c16cc92322115d79154f7930f59f5b56acc154dc655819bd609031149bc661cea0f27433523108754b03fe77585ff6bf3b1c1702af6bb5c139cbd96711e1045423810a1daf7ed453f62dcc8c8f454f78dc5879742bcc2ad16084bf4d5ce92beaecde9a718e295a27557853b2cab7bd94b9b0f2b4219d4fe2eb6f1e6418860ad8a35cb1a8bf685ab4f84212fe41bf93c0b1340a8055452fbac234f8a08d43558dd366927592f35be5a91f38bf928c4e5c0649fda753f0ee9bdaef5e81c612fab3f6f9c915228757446c54184824ef5622d025b62d970dc260adb38fe730c56890ef270a73ed66744dd05d6c8350df5173ae541212f3ceeee94a02ddb1a1966bf25395593225d5d4acb16766ff6ff500ace8367999e1abe21e90576954eba08f3898fd910a629d72b92dd6935874f1b226037ec2fbf6df56e9829cb1ff36631d2853ad2efca68c0bbf16c18f2e6f3344ca9bb46cbabaea84e18eb4e71df744a19b8a076b44acf11de96871850c3f42af483cb42317980d6b9c62f21c31990a044a37ffb0257122f5e04de682ef9e492d6fb22e97646bd120ce79a03c5c14623328f4a057dffbf9c5700c529b540095e6296924a12b6f803bb2f47ad620115d5a271c87b16e0053dbbdd99bd9f8ea18331d2f6ebc5ef1aa426c3930b91baeeda9516f666bf3df90d2ebe1dd02b518e96af6d139cb1546a2345200e75ccad46bb187b6833ad11316e83a06e6281f27c246d6f7842d8ce2ff1a53d8b91da3a8144760c336dbe8a0ce53f3beb90cdc8381d076aaa1275b681bafebe430460ada8700ed976e5f82a0a5b5af470c51ca2ba7e3ee19ba982aae970e5b93cfe9ed8d493b65782dcdc2450d92d8bcc97c71f0dc3d35374a015280d57f0949898f121a41b5f926070dc70779de621990cd5e3a691fc5e8a0d923e97d46539948ac5a0d3cb55290a728bec1e49d231434da59aabc7b06c66038dd4df987296567ed92da6ac37abd4bd99cab4b8d4ab43a059b8ee8b95b7801c97392f83f3b6339538d03dff137f6436644072ee0d5145dbf6ddc2205f71a4f12bcc3ef4498f3801b95d113b5159d75d3363f130d11370c9ae841452dd934ea0ca27c935c00cef67b857f624ca174dc5f32fd03aa70f4ea29a5ee4f179c32c6096e586060500ec72c0d398dba9aab05544e700c595d184eb9a1e5e2bdea97aae84f491572bf5e15c3e879384e1c4490207393bdc4bf6ac1cab605c4c1c87ab19eaa973640de8373db1d276abafa27b5cc47c6acac5ffc826b4a795f8a0b3dcb0cdaa3895a8a5a6b1481330fbfdf82bcb4ee73f401685605562be941f6a6e23d266d419996476cf8d810c07961e1c884982bd4af13a2466fe4bda19d9bafd51d670ea83684b19a0cfbee6fdbb49726fdf9cc0c81cb39e21cd3e4d4c9983463d79a9483f3f1f6060c1e0317cd32f3f5aecb721a0dfa1c011ad34f46bef452beabf2f5a1a78191464ff18f01cba355392f6f0cf17db347e4abec19fb9b8f24e12c6c171bed4cab2aa23a45d2b697af0cb003a835febb8ff1c7b1e1f823a36c35c287b293a499e4dbc6b0985b516ea47271046dcc4104c16f9840c5bb3ad32389294fcb696f9e6bd0f1a56fe8c5eff6e14837dc36fc351df6fb15eccd6bce4575f0ab702e3557982e4982631f1d75c07d1eebae67675090d206b088d6657e8207fb0dc1cda2697e826c0e2b0bcba034f17286ef42c0542c027c007c8979c8b43102626d2ba5192749eb8df2d921a7f37dd465257ac702be775baf041c1ede403c0f5834c7f04d65b8caa9ac94d9262dea6b0aaed542a58c544f2d24466e1dbc3a04fa0f327ba47556022ba6668e9115a270989d457aee96673c96a363a681804c13920fba0297dc680085e60c4124708eb1a5c045bde4debfb863e2faaaa30d4544e117b882af34fe233027acd46f2fd80f589939dade965e8defd8ca7e4a5932f2a50358c0ccde5a41d46b9b92716e0f00d47166a890411e3cc6f884ce8b71194e72de88c276c6dc234385a8a53b44f20dfbdd1a7f2717b475c9d482677c122cdbd233005343acfe044309fb9f2d345e557a4664bc764149a122bb1abb7011acf0c4ace0834aa8ff16298901f2bc3ccd0929576c4234c1895805abb162cdbfab397d75a125abd6d7fd4311c478cdc61deeb66a39f4440e2feb41504c9467a7dc377e2549501f608f51163b8eeb99f74adeaa4d163c19c5fcdba7ee2baf7542a8bbfe23ecdfa050c121615c226e2c272947b1f2568719f3dd5f3867f7b70f91e06fa0c2a3f11dee0f6bfe9c8e02677d30aa3af288c722d451e328c10bd13fae704d7688e0693d8194a58bfd4b129cf7dd1d6aef5ff012139f84b5e0d2c7a2c697255b670f6f33c2e037e49886e8b65839ca533cd77b65df5494c3556a879812631c98d506eda55bfcfe6baa4d35f4c59c6e8b5713e5df69791c546c77a404b64d28db059612a135e5cb2923f18d2a12f1db021d87314e9a6555c6e8c0d39ea727957db7ddd39b7a7de2b1d701e8a2f92f7638c4c1624f10b62eea190a70d301a7831a59cf658c887e7ce090402381d023b9b505140232c1e9cae22ab6f7335f516575774cc40c40fc456489477dbc650ab2dd01ec9fe0c43effd8deed8a7724bd83b0b2e5308f6340de1822e11aa81cf7820bc36048de231ed77d205d720bda8479d3b325aba2f8495c31d7cd840b7f8ee00075e71453b8ef125d0f219633f7a8b6a1dca97c47dd08c732c03224cbc9fb94a4ab959b28747fa3ff742a05bbc086fec0ab07ae5303829cbe2409cd2036cffc1053a57b369e352fdecb2b73341dfde92f0fa392e703654c2df0ae326401ca8af22228d47ed84391234225b3ca80d2610c1f2738f2fc3fbbf80d276b63757b4336aff9cc7a0191593f62c442a152f128aafa47fac5ea6ac1f6f8af754b73f8410870080d77429e48484b18bcc46d9a50f9c1d1f2bceff7ac630716ae2043e533225bff4200ddd7d52ea38e64ab9f3fc03ce5cd7fed3b72bdbc3d8e5dc7a97fd869e8329d5f89c4abbfacc8a1454652b61045b5c4ee86f9a66ab2f5289c875588df9b755427784e29762b9b7b9e6ff440c1dc3a29ad4d54123ccc089b9c81daabdc6e211fcf48f1e01eff76220c1d25c1f0f037d84e46e71db0e584a81f771d3fa2b234bc6087f0194aa18b68f08bb2738efca74128bc1cd8101f2e221ec674563746da6c3483da4d8eee2f4209a6f7df1cd66f96c0c5aac75d6962400ba0169597391673bd9083aa9ae5804a51fb2b6648c7e050e686536206143a213df138b73138999b660856951946b14070e7d7bccd9085a8d3f02e9774673542e59c8208b654eeb27e944b2263393884bbc4d067b094cc1f63b2494667161e09990f4eeb3410500735a0249eff6ee89ec64efb44c1a08ff6534a69c41c90123e6a6a7b558db672f1a5f97a06fa56fcb78396f40375134fb79e6d11594d7da88c8fc8564f2b6a7b48f7f81c8d02644f1df429aca60a5533aad9337bc2f5f1b2059d61657716792cea84beb61a617f4fc36851ec0602df8c61adbb0aaff11d809b4c8ac27f50d508381d5a2fc7b718adbe5284c74ee24b982d5939f446668651a8d6d513ad83cad1964ded4763957b73c9a377e47e5029c1a647a12d9efc116967dad9015a014b4beebe1afbf1d754fec2be67ace975867b26b3607e45ec714478d6569173106ea42da254d327df91342bf87f71b7b678114e5ad67abbd5b1876031c7aefb8bf93ac52cdceef08e038caaa681bd64b8106f81447d7df56cf8a607d296bc87d110b22a2584b0eb216f9c89c30c1b3fb8790c6496fb3bdbf3c296384885fac0ff627d557f882ce9cbf673361b2cd6ec313d936576876ac98425a92485f42ae584721344090d39722c0472c40d0763e3d5e4c0300154fc122dda45df2326f146fdcff2782715aa41911e2969dfda53eecf74563b4ef8f378d7285ae241a901ca61628775a106a9c40857abfe989d09af5d78993b840263b606f23487e77a646a8616c6457e09489d4434b08278ec9417b59a08adfd282cf75f660290009bb6b4e88f06f6f0d669234c483697f1278ff03a066b4f7dcb575b053542a1e4178f49ceb43b1755c9d6e2fed99d033edf0d5509044fa372b64e79c03a62c16fd2e95fb32f2d3f3b3fe016674cbd317eed3435c69561734e5a1bd8fcfb1eddaa7cc03f0940bd8685a34334dc2d1bae69ea55d3c903c647e7e8e57492736a450495c8c84474970a94ac1662f5184dac8fd0fd489599b9f1ed0a3c406000612d1860a3ea3c27de6c81a466fa4bd1758227fbcb00990a5d02721f599eb67b432e0923ab2cd9da8d506fccdc0b0e46002971dd6f394b397252763790e891c284c26820ef2710ca56e1e22c2ad8606400c1a515b5878e5a3a7c953f2deba43bb8cbaf19de09364c9e4a3165355793e3150a78bd175368af655fb6099f673324960c475795139be3323efc4b05b2207548eb241af557074f339ed0288090bcad59ae1f79c81b7a11ff70be722bca820218ef130e0f231abb7421c1d02090753674bddb91bc0b3749b01c68de01afd4e2e6c307eec29c753826922e2c88029426f0a9864edf60ed9cd87d0b8767294532f7bade9b06770b26b2e17dda7f79683ce5c6dd2c19ac4efd8a549970c73359b56f49e308f9c230ccc2c1d68720f748510fcc28021814d126d368ad6e1e7afef13f8bac81955c345ebb2838d182764be1ef57a25475d22d96b3006be563e26d6846bd8e65e37845288ed39ce6ab63d0dfe1687049cab42f1e856b69193201de15cfc39b2bce1eaa23abbf2f9d891ddda22ee22c29799bd1f8ac78b0e0caa205ab5ba233d7abaa456b2fb190e3d57b46ef810efebc32fdac6d9ffec4c69d446e20aa04253e5a4b6f4a53af28ca0543ab6f12173f060464b8670e56c5f54499969219b75dc2c58518e051858e91fcb43e20738fb81207ed5594f4dc95768607e58fe02e2b619eb04390aa42fb7017fd627949e968efa7cab1e5e6711d9a472a6a4203fe0a3afa76cabda9b8ce064bc3136313fcdcfbccc07092b7fc919d8850731f9b620befa65974869bff23a5779f203bbf86182018aaaeecfee4d4db6efbec06cdb8bb80f52b5d7be8f275eccd9e377817a70ea7e4f44caa8f97df1d1c6395fcbd02ad87de93ab8fa1b0536619bfbfb3ea62aabf9b209178350740dc91f2fee4f16682ed768f97b7ad1d25120b798324681f67e6774513f55d28a58327c662f0c2af9187cfedc0f1e05eff152a322bd45f2c79b9d58bfc6575747b0e2b32439d0bc9133584d0d5b4c1618905e5c88719f53786f9beb095cbd16799851b3dde7cb048e562713189fba653f0705a0cad21d9d6edb8728625250002fc398d21702372e2d616415cd9682627ecadd343dc9152ffa1fb4b721f03294c93c92fcd45ee52f08de4c26fa63e7f16d40c99a43e8c2e908f498eefa1c00acc1e419e649f89e7f2f46d4cde9820cc19c3ebbadc6fa136c896f54d0945feb8111aed99bc62601c083c51b2f257dd4a71cc59a78cd878eecc131be0e33838ea2ab9ab10e09d5f557969a6c34510b8f620403ce8cad8392b23f793fc31b4911d2ef5d1d78fd9b750307a6e94474aec066dff27329cc2ff8d3a6e6902beb22f4cb6a8f554ba3b86e03a12964dbc1066a064e1600080a4e209e21b819de803bd2a5ebc2e66097a8f6249bd6dab3c67e82e5427b156e7e35e65cde088621fa9f1384763a1bb579c76d834bfb9cdb2101c674f010be6b5a72516143463d46a8437fe9867637c328f19a67574bb7edea540e8ad2226bdc312a0edb40b96ace156c6dbf8860e815c86fd46a942d6a0d3fb79d189668be25e4eafcb0c557e2e340fd1cd16df91a64d2ff5be1419b20479daf3634284c0b1c56473f460fd49c322689e4e226a14bcaeb36a251a81fd830c0356f8fcd38599aeeb22b5598afa1a03d7a64f518bfa56df8bf76dc73e31df9b8642231d2358e5c17e3dbb8342f2943fca11e65cab60687221347c4881f7868a6cd8e1d098a8901c0aa3e0cee5cdc0d5372cd901e59cc7ad41e9a370a3182c10a9740c85aa7cbecde459360ef68c5ed57bb2ba81c199050c600df2347c0fabba632b1b6f2068545bbb0dc77b8f4a63e686079221051cc5bea837fe2f3fa7aae68db37c042a13643e4a91e132d1082ec15cb36ca98b458423e3728f1fed440f49c9be5d8182be8262e8ecbab1d55046b3e2c257e834e73b54249a126654be366a9ef7c364a577eaa9e1c3de5f3ba9b41ffee25e2de173799b51a9bb8aa1e0ca2403204df012ef4465a11a34023d2543fa02512fcf7da6a0f672ab2eb57f08db28310ab8aca4224ce2acc753465a8eba4e72fae5a7212dd298d530b835c96dd779cc659ae4f14d01da6d198d3d90ea7eb70866619139eb8c69fe7670fdc704fbee599b4b27c4d216ffcc7a3cda2161c4e91c4029efc4a47f816cac1f230617abb305301ac79b0b4e749c200775f0bff3284ea49e25a22429b12bfa5c60aef555c8b96b565c443af654d209264a6423bcce827eb11eeb2fd5fa363d2d937c676480843b9a4179046e2a93dc29f9e482bdc8369dc8c661d83c13ab99593e3eb4e3044f8afb169a3085770c6a8808e223ae1a1a4fe38decff90e7c851d107e36fa8cee389833c6f94e0301fe3d35c6b355b57ffa53cea221ffe15c88aad391d67cc2e45a65a5cfe670e3dda07b3c45916eb291ab9e1d6e9a0c86f79e9d840924f94c8531d96e16e6ecdfcc4db906e070856cf8d80bd4d2065f97bb930f0309b1dfc14c5747979fea625bbf9a88170984f209a4b2496d15b3c32f5b24bac3f952163dd69484b9705785541105c0a40d307ff6f41d858e6b46eaa4bf16d0947c24e13c3c42a92f40b864246d8824e3c89253748d89fda97a39f831f43336a45b94da9b9f091ab0aeda811f6e377a8248f7034b3c292e5f0eba09fc96ce928dd663cb587d70a34980cb1c572e284d69b45730c511b7d03819a06e0d3aa6d64d39d3d4649b82ca5219bee484eca05b7a9b1a25f58d3f4f7e2108e407b04485993373a75eefd06e36ceaac1114d4d6043b25415112713e452922aa8d346eee42aac4756dba65033005d213a6c04497a4a277bd1c5c1e9b68753fc69025bc59944acdd8e3c4757623bb2321a043088bda10ce90252807dab367b4126ca8645652caa11268794358cc2030ac76011c25301ef4dc3cfb0747cba4f3eb87cc123b7d24bbf47c178adf1e0c5ba53c52c984e749444d3e6fac50d854f32ee4d408efc276aba9ef3311f9281bc003bc04b06f9a7020fc13b27d7f72a6990cd8b6526a25819ea9773363d54e2ae712ced7a0920958fa5ea706a198194a392e4b9dc819dd2d6a8e43d5d306fede8812970048133028ec7a433fc605ddf51d3d3b68e1d081032f29b9a7d6e9da1d635a57a7a7bc66ede0e3724866dc75e1281ff128553370b1887b598ca55169921d74d8696250c1174d48729e511eaa4a7d7aa1a68fc0748a4e766b3d37b3962d3edd18d6d5c6d7fa768be6d38e0d49f23c5254daf2ea0003e49fcac5854859a12d63bdf40488dc4f4fb0a30c89ad719d54ced775e9e491381904f90425e6cce54b4bfbba52e737b0d916f8848ecb1d9658803258c90d7c649b0f5e2e297dd3b414b922960c6ce01f25ce1d079f281d2971b7811b8628016cf5386e495d83440c5404ade8c4b04e7fd4fc29788a9ec52757b5708733c67255209951b2c9bb23b36af1b6847de33a1a37a2104b5f5a9e6560d67d1c588e25caa3d581998a192dada2fd1a34a2304919f131190b2986353c9cfe128c0f4f259746fec180f6b640c79331f434d3a2690f34cc6ed5fefb200b00de0e3efdfeec304f40c5c0141bdf10982b2eb47f41f76f87144ddb61dce3b0a3ddbccc0e1be12b5491d3036447f35a5054eb5a5078911121e9a8e3eb2114130ec861850e0aeec19cb3bdcbf0479dbe386ff6bd611142b1cd7c1ac64d8ca49c917e73ec0570775211367ceabb3f8d154df928eb4e4cc17069953cf19d7ca8b8b84d4a37709bb8debb51e708712bc47fbd07ace40bdc9051e8f0aae7f69c4057c2b29ad13c76d18cd8c60f216dd43ce0b2983d1c90d535bdee1248bc656da0712ea44ed8a1a4fe26761be30415ae145e238830fd50d91000fafc789c2810467c5aaf659fbebbe69ba6ddefd57b9749e5b69ab1638c0133c9189393423def8ef82d7d5b2d0eba797d3fcb508b59bb06a3bb50f81e635f15ae33afd2813f15cde4d68f34bda92b9b812a0100735108c88c2a736e73c31048a6e524c1bf9753e245964bb93c5ba4b201469415ab88fb50d7bb8c859b2339058de4373375f98f2fa69c9c97ce7f79497dd535bfa88cb7907e0bc1e5430c6713a3f94694b031d35793f6cb1b4abae50f1582c510b089a112c2cdb3dd0b73308a0d8aad849345f3ef1d8955f1b97500ff11ebf22761d1fe87a613727f07e50fdb53af1d82d3e0f2b4591ed1e4db3733d05ac6507f28b14c4c6f9f8fa24181ccf7e0dff49a3914704ce18140f0a2c316aefe907bb7cb984c832a49ea1734c3f68e86274dfb2464bce351ad54f3b40be2d5afcf358fe090fe942673c5b7531d441ef7efcc780427783bcb047c59edf4d4db45df9b3d53010459758e2f978063d3094d597720e033cbce74a3cb97dbe848683855d1e3e4c7243870afe39d9505a4fe8322ff2fd1e094cef4b715dd4038518e26f0c955a69ef0e7303219eadf19caa5f9215ed285512d9afaf0be86944d15505318100c82d2b629166ff3eb9d372915f44db36dbd159238b6f924ed81af4e8024f0aaaab2494e23437f83e68521c0791b55a94ecf1718165d06a8e265d394f3a3f4852b46da677d20118e904040cf2efbc6e70324dbfcf3a970a9d0d3bf60dffe2dfc64a2f06470d84c87e3eaec63d484cc50cabb0a7188566411b6127ff25708f06857fe85ec4dd9c876c3938b12ff8b700d01a557f6c6a69b2d6b9c618219b72ae066e566b8e2578eb50ec075e9c94678aaba87a72a2ade2bfb76a110682ca44a7068b7c95441c92db8d0eab4e6ead3cbc7261b935aa1c7612d728270217f8ea98f342735cc236ae356c7fd8c7ae9b16e1cbeaf3475f52eae836c56536344d78e9742bdb67917b9cb4526dbd6e0bd4408d1d0a4463b9264a2400968fb9dbe05c06eab54697b13f927af88d72a5b6f55fc7255f091c3a1befcd30fb66d9f2543a1c5d39d4b7fefb2b2214d3bdb953e9013029d9dacae5116c509fe74943d7edde585e89b66e46d2e04452cc2bec18805b4dade4708a4ea487950c18731328fc7d0574a25446a31fd0606b71ecb2cd7a8a97e65a250f21bb649e81c0ba1929917055fff68b5e803717c2d8d737f637ed17e7e83f2196d7ec5650d6bce6b8d1e87e6904462e9c94823919289da1e25c7bd64055e92dfe1da5e586f57d2a957c7f489466268d340536c815bd5dfbab4da0bacc9086e52be316e7487e9cf347477e135ecf9a1e1759ded4dfecf040e51933fcd636588f7c1da6651a0b5aeaabeacf572f01c8803248b0880f3fd3c26dde042014eb67616d554bb57a0530f40e49378ac549f6d00607764fe4cbc70df03a9e0690edce2f75e87675de94ac46234a3da608142f51d844a6df1a24bfe2d60e949ca4dc10974b5476652df7a542cf3202de23f8796167abad1b708e13439746990cfdc903dd17fee3afde7a779a497b5a6a6fb2f1c60dda404ebfbcfd94579792fe9e9ee63f226aa68c7f53ca2d39f0d2029adbe27c2afa7bd737f8ca570521281b1e99476d5ac04f999ef8172a5e49998591084aa3c9a3a77aed438c5382c2a47e6f35d026285c4fac9877f3f9b97b0b29d08286e3ef40ff19f84ae58da923824f19607a9874ae9fc145489944e54ac336154ed8d2b0b72ae39ab76f47809b4210064a9a2ac650694ad38efafc3ef298536f254f73364a676aeb6f1211a6cca29aeb52b356c08a7dc5778a7038cef303687d92ff079f21dd3d60c03dc6fa6b48e3b73d440a50eb8f095fbdab7b8d2e579d4e89f90887b5e95a548f1167389cd8256d256b5d8a32d72e4a01d269d9660a3872b03421aa41e4fac01d033cec9029f7359b119d8c53b0707a8c45d612165af52d946452f5f03d7af4fb48c17714e60b6a48b07c4fe81d8648a3da1647c06bb20b1a92e74b9caca4c7c29eddb4ba6a669afb3ab5af07b1275d27fe66f08b18996918764f83a7505ac94d717791e363308a4d048968a61663bc4b458b815b3ff227162a334592c2d47cd113fc8aac832d5745fb39b07638ad4e8f7d6b1891ea79f17ff7a61b33928bde90e8d98d0faad04bf93d5f806d6b512cfa372b17cc92f813b16cd1d1360a2b61ae29ff952331674bac52c33627a272a93f6f25ff946ea97e4cc53656b4c89da03e703560c61bff237ac4aa694769f6dfff952fc47ec4e6036e118d74fa292d2db37c05d9b30611af0bf55b2d00ce31a5883406e6f5a74e3033bfd4b9e5cb25fd8f0cec0d2c199b7a4cb863a1c533c3724cf9f28d68e9c27cf5933bd54d8a57e587899b975e466c2d6daaa730e6af77b85b505dc2c31a63016ecc47acde46a9c7ebf82670b8bade6126fa40209c7e552f1b33cf90cb099900fb60ab2dba1c4da2c735ef9d271b59c1622e96a0628dbe25c11c403f5c0208838d0364482c9bb649561f5a722efabd14fa102cd57b0332c2e3250b48c0307b75630caec3e4019d3d0ec8480bb812020793d0df5eb7e4a3584c945eb5adbc35ccf611fb8236ceea02c3f3cffdbf88698541f3db9f9c70047a8f53e73570ee828764ce0c938e00db0231ac0791b01b619c79056a8271d3b39ba7d8a93025ab09b35eb8d73e05e8cdd3285ab5958786c39d6af32088b1864cfe956ac72ecfb7bf632defa9a371348a6fb550827432ac9bc4a4f3e241810e824cf4fda48d61d49bc4fd6cd70ac265edb6687d862507ed1a0f9befdeb5dd0b829b4407f62d73042c22af203774585b366b99990311b2febb2f5b384e1a69ec92ee167baf22f862ed002476bd752b86e1fcdc198d5825a64b92b8ffc57d9eb95823ea9ddb5b3dffdd01cfa290a843c79aedf81cdf90c19ec2b2bcf2402b132991ddd40ee63f64441938f8135cc9b9d0383a962ffcb5652177c1d90cdd02b75d55e381829f3a325af1997ddda653cef00d7aee774e79eb3dded2d9b5c2144ae4fd842567caaaeb4a27bd1dd374186efa7c3fc85630fb2841a00e4ac4b3c5256902b90f209043d73985507d0c1d0823df365c4587ab9c4960f653248a723afc925f62d0ca85f8115b3c96148bfc595922313868b596803b0df8a3ca293b172fa1fcefd846285e5463810300dcd966edb3b9dbef515d6e3a513b414f1c5066757934ea2eaf7f7335613989e925a56346281a74830b6ee80cf16a0f4ad04498b3b1b3451065917a97d52b701dd29d9e5f8000be480b89a8b98bac3b229b0b1bf8a54891c852f13164ea6eb9512d42c2838084b95548e599ac4b21176283aec74595ee3840a53ad7fbfe5aad77f445efd73ac19cfa51aace1611653ea17a7c4789e5fae66a754cce28c8ac3d4533a901dfd42ec9797374f9f81e20f77d911263af7008a941ff5dd08cc334d19e4ca61eca2e79f1dc33046c28b76073b0826691e78eeb5b0a0ac09962522291adadba4442ff92d693145b5996a1739ce18a7f2c15b5a8e178d2433bbacbe123f46a86494c29b7024bf2d8c94da6d7c0cafea213e257a18edc5bbcc35190f40ee36413c038c34f7a3b48f005ce103aa6a0c598fce72069f9f6f057a871145923b1c0bfd9c56f0eaa6b088d8d2cecb539887f19a6f4fbb5e718447828f4631561df33ca5bac93d037fa02885c331970d7094c44a504bdc42dba8fc12f05cc99448ed60a62308508a9264f40c47d9a145780177793104acb1da9d5452e903a9f3160dd77ba18e9e716aec66c62e5304ad76b6c5540c10f5687c5db04194248d2518f75491ffcf6376be0968e2fa564030d86dfa2b9a6d846a36662770a7c7d8ea300d5f79b1111e5631f6ddc62734ba7fef98788e97b7aaf8e2f9057d2f2b11a269c1d47399791313af8b1939dca939284cdd35badc58bda26e1d466ae5846068cef06244a912c8489f7b635842856a3bac5f3b5aa5e895618bf61fbf0c1915ca02010e5957cb88905d657a6815035e884a5f2895a1a70fc4fabc767a03a122036b6dad402216e3f5a9880d6c2e3807648b7aab6baacf8d2b41d565d5ed779ccf0c6b6d719ce12c7b03f5428f19b4be014a540d6733097518d4d2bbd527a73ecc894c4c5fba12faad1e833062fe27c05b4837a597d3e2ae8615107ab066b7e566d5599fe25c89a74bea9964e3641bacab83c363e37f98265aae425afcb31e155f88e18dee51fcd4f5d3bc605aa19f07736fa746a450d14edb19f345b4ac3e72d6f363929745a7e3f63e251702f181b81359e5e696ec3b9c437406d909ae2948f155dafd695dda7c165f35738391b38cf8752beb18f31a085ade99e907dad00507d246711078f8087ee966d03f5d7eb34d5e5393b7ffe38f1f0847fc041ed0b9620eec684a4b5639ad4d747e6bb6a34149f633300e210db48bb489cde5a4c89d9399c5ab32dab9807e68df42f8cab2ae03088fa8f4dc05fa584866c92f5f1d894d791cdbd5fdee963be3a4d54f7bba56e00f0af4fd1b226be1b5a194eb8af07bd9845dec18fa02f4c1b0df64de6dfafa85fdcfa14696ee140b58d38457657417166da9a4ef583e5ab75a5c0e6d195e3e67ebaf52939bff86448e59246a70fc220894f021e385e14cce10a0694cd05b29db0e4b3bbf50cc57912bc2b324f6bf06fb15cf50e8e3fd901aacb3fe4e1649a7c5622aa87abc919fa60a88a3b5b3e95231a6c777865782b1751817c893cb540ad0ce6274593d8a7b17591f1a6bd663372521eeac4d7fb77ff3d59db920f61e8531cdc4923143ff8e916b2a7475b4ca0c59f68c5b9d14f58d668499364d3455b007d17288c965dc7b292d59bf79eb13cab7cbd2c3ba2e4fb2f4aaa396fc21d2073fef1fefd632746cb77be53df4cec29085fc1f7a37625789bb2d8ca6a83a7d6b0b8c26f44c4d188e4303d7e66a50f620e0d51de5a7f6d2f084236e283c02909ff00e1ddafab7ff93826a39e36fcb3760cfb263c1a9ac748c8bfdb04fbd7ef4cc5adf8c767a65b72e62e23714745fb5dbe559109d3ace3d444f33c844c84c28fdc62bf6b26e67029aa56aa18b69c17d3e713e685d5e8327c279e4d7a5d9d86ea47b3d590a1257a014925db10f36af66db3a873bb915a51af6d222010169872413d15a141215d1e95cab14189af56e8229d8fc90bba488099245d6fc5dd3d5b3040a7a03eecc1e943980b827db0c046c418b41d545297d348ef36cd8b215be0412bc4675d8b16636f2868db9b50b043d63b3f4978981f53ea55ac91b6c57e73d3ec83bb79d2dfe5bd50950d8be5f35b36fcda3b28f8298c0d788205cec446d566ae3d3b020bab608b323717cc1d661e76d04ab8fb27eb16ae660ba43073a4846e1ae1244d64f409562cc46af3df106fa05526729231cbbafd9eec562863ca09acd60e8ef4924fbb23af5803ae304ce45072f5c44657bfe177bccb3b1a4c694d2c0fc317f96b8d23799b575a11b2ff6fae5d32e59cfad8013dc95c889936143302c1c5865ee37fc0b5f87168280171a823409999d0d7cb826cf6dd9fcf70f1799eb46cec71826b2347293a0a35f82fba72f88b59da07cc6c46b9264e16988aab7b7dbf419ed5d47803b0e5ae93db33e5f34eb47e1597a29078b01789cf53fc7d0b3881f9fdb5f2bc928a4ee174e9f3fd8442ee22ae22a29b84e2d18af9c444ba4e053c21c63f0b91e28f63cdcfeef6aca87bf09f9353571d297c01851d3e7ff41a93307859c06f0633226a7ca6cd5c40dc61fbe13228896ad8d7399ef0239abf158374e68ad0a601dbe73381eeaa8870ef012031bce00818c1cf098390750e62c9e3f5a8a5664ff384ae831d77f5f04389560503d2f61f32773c64f81b06782300f472c0e6859a793d613d07865cc2e2f6e2409779196ab1c8571459d44c886daa33acf49483c63e168d95b0e8bb37f98d3ae5cfe1350d5d9886b5120fb8111613762967970eae5668a4bd84f13ac163043f532c92be5c8c97852fab381f7a4ed0a599038ee9ac0a3c798dfb6d5d2452ee092066d1e8c50f4951ce59c7738ac4f409630dae4fc34a631088114db4cf14df058cef289e0ed46e05c46f5f22b40c86385717f06a6bc42de0532c1e3996c4d5d6f589bdbb9d904a29787f5fa8a7768dc2ef1071fd19fd2663f24d0e0b6bf6d3605b9fad851b89de6a692b70cabcb9a7e70cacd8c6c356e67d092c779107e472c91ae4317bb3b4ffa2e753d99185c37fcc52c71bf78a6d0017fd83f8945994716a21d1685a72e83f2224e2885011bf26ea1f4d6bdb64e345b0861739db9d9496d3f06a6e17ea1d02559b778a96f2c09d153c6e3a1a614801c7dc73979f7e611d1d9949a3dc533e4ae2f1de6515302d983856d6b17d6520ff2adb32094e309d782cb309783d1b358ec40b6d6be5dff62cb3e8342e17e2c370fa8485597be12b2e768aafaeed38eb602280dc6a5abaae24f809c5e5675676da64136d9e1f70c800ba1ea028d2ccb6558554ef4ae3fbe449b6b8377de66259e73b6bf0e06d0aab71b2ec62dc311875b18f3cb8cf64c9ab2a1fb3caa776816cf315449ff4bcb92c2e955d661ca8635b1b2d9690e8d51e76b344ddd3433d5fea721373fe02cf4f8a66c3a79242ba213215894e3a4f90071d78c776c80b87caad989166742d7ed2d313eba5be44724909883751e4eb1fe830bc5f45d54be21a7a53321ef07aed60d581ce12e2ad15d9fae6cbbf91aa368580d5f9a5c3d954860faeae9cbb5c9fa784a6ae86a53526635d193c44e97af3153aebf216de79c5576b0006437f57256406facc74155a4a2606a3c5413692a4c283dad40a969cbfb6a77cbef6222a7e82fa40e5523e8789bbe12ede6cf825ca42738d45115482afbfce82377ae959bd52034def57466d0234524f216dcd902eb3ecd468d523e582fcd0be699f6ec35593d4f0cdf5e9b85cf99a5d910136dcc2f7a3bf5d02fbc05ee264775eacbfa8f89e04bb0e5d0b8c2b97193f617f28ce04b0d193bf95a5b78b5056b343aa52978e9bb9984df20ae879bb302e01a53c2561c301853c9b47b092bb2f80df65e11f3ca2c1c6693e42227325673f2573565b98c5724dd6309a2a8699049ee9299b5b681eb93d839e96bcaf534f1cb1146f18c7195985e532f2b55a0f98a460ac963ac657edb808d6aeaec1f554dcf531b6ac33229dfbe22cf63a5003bc78c66077073a51afda86beb67dc0155cfa3c652ad46468b01c885551b44fd135db85ffc1fdfe8dc93bf04522b3eabff3bb4940665727a7d8fad11d2566dd84d58c8076ceb75c3837dd69c29b689e03ef3b15e4a699710eaca8185c4de748246173d873edaf1f6159d23dc2b538a61e5340d40a2c37ba79b35bda21a013cc8888a9be91230c0851e5281af73e1e07f0b517ee3a533d5f51775bb95e006c1d1cb24edf9aec587956853efb8fb917d05f8a581a74108d35427003dee658b90f0f3423e2fde689a2165cf0d504a9f2b6bcfd2d3d2475d941ad330e727d9184abeee6c20a9915b857da40fa2126d61055f8373f9d84351afc4e424c0c29b2c258b8451293c539387a70d601805a682f99abc0fb9aec193abf22f4eb5ebd00b6a1c79c8728e5daa126bbda5fe426c834d0bb9a39f2e7bc0853031b316b5e89ef8ba95771744eb8b4d53b7e4853fa22e1745584f798d1d582d91cba717df29e1c09176f0b93b8fc5926dd2ee04962319c44cd044dbde98cb08192f3551307c4f475c353effdaa70ff0db8b99756a0cca92acc4059ca92bfb6ff8317f3e5ef52727654be3106b3f0fa937a923f5595cb315dbb95a578c9cbe2f96a634c085ea398ed444391a85eb8bb5c3d4e647fbd6b4b6f11e36ecf15388086a787f9a33887cbd4e1ffdcf8e7d543e6f9d0c52ef437d2fb367e1fcd547f92ea799f8f6fc32a08ae26d94e34696d522a884c19f8170c010e2f0677099eb47cbb60bdbdda73255de479c408f616ef575fd034cdcd894e23ee7a830f2e695b07b828c10c2e36910386eeb0611356620fc91b4805bb7fe6b6ec8b61c72a70ec2ce8eb2b9f8911cad295b018d2a762e92b25e024893cb2352ae6e994ea206373dc9e58182379e0669d658268b9e049ccf4745154d9e907de5dd3c79f27b87fd6a65d1c36b1b67455e797489beb7c778f59cf2d978babed0fd2ef6a5fdb62810400f66abb0a5b6d5cce04c2677c39ae3f84891548fc94bc2d0a9f47ae05499fe6127dd9017b84aa83bed3026f692491136164ca720d0fbdbb8aaaf2393177dbe2581bfcd4b9ded8eb7148ed6d2f89c80b285a076027b8162156092fa3a06c1c3ebe6df03d67f1e843ce0ae8e855177f347468195b1982e9f554f227ce3edff1ed2942559e7f0490fa46ec91e09f800bf85c248234f97347f34cf75086bdaf65391ba2672736392c749f05da23ccee44476927b85671175aa238afaecd0a209da7cfd923504af2837f52be5cf50ba9ca79e055be7543ee273e7028c70875ad7780cf66da2499234a17ef9ce1f1523ce0b7f9d4729da7d6f456f09dadb49dedef207b21943a72c4d6c12c8d0646a78b6f89192f36986d0916908461fb83b4d33b06a303107e8808133ed1a84af79a8a7a9494d65938c5d505423b727bec38a405b59e7328ed0f6f0bfe80355978faa9462d2eec995fdeee4cd052aa72faabd9fa10f3dfb2c7fdcf36c38aec042a5f7ad1971c7173abf72eca0eec2df947fd0fb8a80110f3868e0d3b841d8e3c8b10b0e082edf80b62c7a27ae6178981836f305188032b65c44981404945359c6d7e775f31a71b56e09bdca878d9863fc24197a1bb1e5e51276fa4bc1a085683f64921943ed6af68951945ff6fd4035df6279337f1e63fb7142e48a97fbe683295d937c31d4eef7487f7e340919e449d0800d51675b5846f2b8955539988617360f795ead6bd393450f9d5d9436cd5c6835166ae5416663e54c322cf2b184a7b8a354b7bb6c244c066938453e7c952e637496d22deca066aff242a32cc7983ec24839c46c449f9d8fd437cc7e0aac5b73c4d4ef80500fd60a0a320c73598a2cccdec123b60a3822ddd0aafa3171d6824a62dc9c3afc3174e17dd3adacf0b8148c708a02c46a94836cf24340a0d4abbb0fe5fc9eb9c674da8349d78c9936944ccbf2b55793ee8f3d446cd6441ea6165dbea5492a11f9c0e3490257c4cedd248a5beb6631e7c7b6302089e048a655d71327201876b653eae6e51b506eb56d81e282a74811eba0ce04aea5fe150dec01498de185619c8f42c1f45c211c4efb5818799b065914374b5b03b0717fb40f7cd81a5eef276f7406b194600edfba4c6c3ad05dd3c61483dad1b7cba172dd8f8417a4337a25e261ecff0c11c0e0d1e31292e272e301f0f8d7d44c7d9797fb34282b76f58cab41369c7dd0bec38b3af2b3e19fc5f03686fbd741cdaa8181ac3eccae299a50e5bad034ca64d60744d7474df3533cb6c425f720daced2316f34aaa7aab30936cc0b0ac270f83e60af7de2f376405176f8f5b62c853f169f9ba21646da2e17712c8de2cdd1b11a068e3fe4483124f877da4c712a63a814631effdd0743176777b9c8d0cddd00f91623c65b087a0963800a42b77dafd0253b5c24c59f1af25f85320d90ae9f7d8a0e2d8278800af66960ac10ec3b73ea2e8b07267695fd9b481997b28a52971d8d556eb27a111c567267641a29af07b32cd8b067d3c475b724766b00000ce4c9f6b1c582edb1d63cb4ea639d7bd768318f4ce7c413ddd0f5b4230de851253fa05bced6c57f690fd4a4c2856f39e657fcae36e96c82e246343e6c17057b5ac6682cbe420b1ed856951ecd7a50c2c34d32f787e453afad70a75fe0d795895c960ab1747014c7ae5726996c06a563893d8dda55c75bd7670e8f4dd1d25d204f35099248e5e9f13285f263044bad3c05273c4d5ba0964d111e2c3bd757a46a4bcd545b762d1343bd19dc5f7d2688f13b796c48778f44b9fe41f39b4d26c1a4de36f7a19e54b3bc3d659e25c69e95b3e244adcf2c466f0e57773529d221e95689d3675d46ee8b8bb36bbbdf4f5e0aa6e33e960b1e184541961cb946ca162da43e38909453ae2811b5d30e608d40c6eddd925c50989b95d60b48fd75db8edc9be9a7cbb59491b5d1ed24602279eebfcba4853f5209da119e5a6e210570b5be4fe45d6bd03796417a97568efe118b97f9bee7f65c82103215fb58ab0b394cd38d80a8896d5204704a07ae2e46d66f2220e5578d1e3a00e32bb96ca514a9af8d8083d335f55b46daca21edb8ea4df901a8875550790acf5c663440cf8a77ebbb24a5e24264b051a4dfdfd09a127e6397544923c0dbf277c5a15f82da181edf8b78121c4396119f05f5cfdfcc3478b6e6118079a9d092cb80130231eeb45c726083eb627a8e21b3c8399288c26bcbc23a1eb608d3d4b2005075391feafbb7d968dac561d1f9e836039a5f90cf310c2e9c402bcce802bad4a2b2f3cb24d6f3ea2b122885139b53a088727dd15c22548f6d0d522c8fd13a3cbcd574889d1c9d8179a111c60172b36ad9c3cc4986acc29bbe524ff1c7f264a38c65d3eedc1a84f37316704404353adf90013d79fcdcf46228a68005135f8dbe63df7da8fd563a95a322abb18424816d937955104386929b44b6f107f5b9b0eadb79b08497fe974e46ef198b3b5605947fb4afeea617c4ff1172e6aecd88ea1da1e801df86b4363f4d5f8824fa55a558231f77718564b6b9d75055fa2728ff67f56ad655ca40f772ba60b882bb602651f8c2ef11bf761be059056936ee06d4021509edd0f0d07ba65033de05068eaf3be8d1ed536f48a70271af00ce2b72f3f2db86d3d7d10cab1bc79622e4e7f24d27dc4f5b0444ea94c6562f2eed46270a384f3c6e388a40327ef32621741829702cd832b6c70d2d1e933996b8f3dbf56c8debb674fdf641fed79bc86b4708ac6cb5a5e890f8635e074ee9cf9ab7a4f58ebadbc0f1b42885c395e2ed0615ea2cfa564e4f0fd675091428af89829715388b73782dc5068908789b7ea5e973fe55b438e9d36b0263f68d79a20a525d9a68a1c9b394249bac5ed76831dc8265ac749c0d7d997351b5d3c1ae0c2aa2f09c81b22b52f914510b21e14d2df431b03029969cf83f09e7d50e8044aed03bd9bf97c4f61230f2c4188458af5ddaca09ee89e9d88321a1a597619fd4f6799d91f6bf81962ac0ed26779a445bc30d1712a81fc18aaba2c6ec3084783282fa7aa96540b835b0df8a613e126fed56a1541d33bfb087298096a73e4467976c6939b8348fdafee0201e9091dce943231db1ce628c297b4bdb41abaafa4edf4f6a741fbc1d7dec5a5f57c5fc3c84038a47d4824161043f3a248d658ffb11d5fb3d7a9db891dd29b17eb4215533582ce44b548ada6bfdc3a3e755db72241b8f3bf0cdaf6ed95603fc95083a151041bef0673f342114358a89f305f7721ed8db39b1a5c5e77aeb664636fbcd6f485c6dc34595a8be22afa9353d20273d4339be56e96c42b399c3f4fa9389ce924c8e66256cca92d0b0d46a9870bb4ceb5433fa7fde4150d571a69fb27d81b4d437fd239abb489ce29ff06e9017fff91f6240b15ed27582cd7184dd06350727c6af94517ac2ebb6ac38b268dfd5aa00fc40dc0eec581048dfb0ee67497012f2612c84c1d6ce83376016b064cd4c8f50c70409053f474d2e063a9b8c4306ef4cd530bd70150955c08db3e1a1bcd7112667b3cc18a50219f33f87e50d5f07bc709ab1799b7a8e9bfbedc421fa2f79ac88fb48ad3e5828986716944993da51ee7edf01b2e10f77ff25970db0da05fe6d193ec93bc7637ea0229321eba9eb00cbb77327a77b9bee2469697fb3cce99275317cb7865b8ade3d0307210f55f070f100b5615b7e990c1d06bc49b28d3ec6795046cfa3093f542b834fbea12e12f5cdbb3705163069e3f44f95ef18b8023745db2e412c6103f75fd9832dae17fdc425ec228be26c20532576f79db7fb71ff342a3208bd9fbc227fec3333e9788b1b8fae7ad80f1bb788201f0cc241e66440bae7a12578edd18f6515a35927138af9f7fed24eafaf7cb6cad2384ed03bb110dbbeadb282a92575a0b1a8e8e35ff5811dcf242d1bc3097ffc3466345fdf0c7ab159e3bf84c63b4ef2852001bb81526f146dbf246cb51fc00310045b4e4b351be9af3fec2a371f18d3f014b0f1c405b4c53990cb026328e71e2f94b9e2995d7d46a5ab76a76ad8d7ecec0c69eaafc8f16aaf3ac201e947bd0409c658c21d25291df8fd4d30d986059ee3271ab2a3a11613b8baf9cf7932a55b3a6da97b8a980dd87fdc4c7ca06f07794ff1f7770cff1724548b819841389942d0dc04feacffd973bb16604b309340c52e16320a22d876d5908bacca5a39d4c86271301db4798102b053cfdc069c56fc4566fd6138f5f1b1ce1b2899d273f472ff536d1109aa3d29172d41747d383b4d32cbc218a0c18d17582dd67cbd5d93b9e3bff4f7b7f70ba2dd115ddea431351b4c1fceeb1649e3bc473ed8ae5bfdc1890755c04e6821cfbbf1891ee631d19d7c590e8b3dc8427a27bf7b3eae4d7195c47741257ae527f5d561b506dd796847bb106a3994dd4bcc346d843bbfd89af3bd804b184437d1bfe6ca205a187f0a8ad76a9c051a9fb37192648592693b1c1cbea806e8594a4614f8164982710dcbc302c6219d1e247be24542ab32f6111dfe827abdd9c6288d04a88b687bc4e614b222d834aa91936a2824a42198f6ca8647bb1fda5c82c2e11ba82867f073ffe8695371161d8af45dd95cef1a98bb2bab3e8dcbd777e5452fd53d560848e3ca18dd620e9bccc28e2bb3eb669ab2dee486e311f506a929082c89c1e3af6ffd97cdca543be8c219dbc53c2692fae1b836128ad361aeb4f0fdd98f225658a1aefdeeb1ddc58b7e0f047be233f36925eaa5511b550053c1bdc36d47618694bb67573adceabf568b4bf6731d900dd8b0d5bf186f8a9230b27936de301590b6039d511de11852a6191801d5588abc0cc223efb0bc5b282de7f09b9e58e50f76534a86a50f3a5e04a6e3062271c7d9e3973c60d6f4266ad168525b45d011b2afc46a291707d6f14956be3dc8dcbd33e7edd2f5bcf33ab30f0eb2452114ea1018e77fc5fc65a988ad6ce07fefc638d4c0ab564d0fde5cfce3588a6effd556e3fb6c0e08c2df1f1f6714232cf5a7d8963f2d80d319a84496169e3594a3cc4d7b6705209e2b39db14d4d685963231900fe8a4cbe893ba21b4ab8b7418b5d55e944fcbc2203ea435df443ba708f1b329c14ba0f615b9e411c6c948ea8822ee2b72954a851166b4b78bde6e5152ad85bda3a0a28450a44faaa32039a0b73d43e84e009dfee6f2ab9f123a55ece90c463cfb521f3d4f1ff374861be5826312e5e019868a472937346179ab0d069d2655ea7d8bdbabee57206dd467a7df70b970f0857de97d9edfa922c55accd2d0003bdfcae3e29ea52d1383ba9a87edb1aa5f0a6893b4408753073ebd98e1a552673b8630c3c8a809091f1d97ff18423f4f87b4659d6d36eb2f1fc27d19ed7ab87ad31ec31de5a2636ff89334d94b5c6056ba86f3a36d87f52f49f2d9fe856f519b683cc38f982554c4d0b1d39fde1b048f6bb0a3bdb3626114dcc58edc1346a6910247540f691288eb753806068441c947a871c4959d77f65bcf64452030e5891685fb82aef50b7dbc6b5d59cee1f99677bea14eb8b57c15dfa7af6587aad6b91c7fe79b2755f7557a0149d0bb434dc530692824fd6ca954a062b7b88c36c80e7e50efd12d7082bfc1b9972e273dafd5206c849eac253b2c646945dbf7da0c3dee18ca34bc4d9ae49ef7d812037b71bb1e1d486d3770f4b2e1bc5280f8771925559d84f7dc14f372bdc712e158b74cb4097997c49bba996a9e688d83771b08b4f212fe06a3bc84afe4c83d7d8816ddb07a07addbd1fc731cc704d8cb87661854ee8969f04f00089b31abc5d5d435234d0aff57bbd91dcfc59782cbfd20fa2bed3383cf17e0327e26c1790b3e07da03592dd2755c44e67e9424c030fe8dbc4875c0178a3e77faaa77bce6e498337cc4f0e98a7f16525656f39568854439cc1711c0f9cb5967d1923df91d1853e7e99425e721fd5d001faea629767dfdc01f68c41f8214b9cb670115a036e42d4dcd0136994b0ed3a0cee6f88a437ffd7c26acb274efbe7cf2a507fd8a3879d90f3980568c0ce29fd3faddf6537a2683e0377aef5af5745c0e1d76db4dce7a5f0543e90910c44027ff5279bcc954bfc961430cdfadbdb6bdea2867111b29825ded4ff809c54201039f0f23d12599ce6bd1fdbf263c87571a9ce2b41ff64f9c0fccf1cebfcff0935204a44894fe6c89954bbc972dd91f00b766497b5be60fc92aac216f4cb1fd765961f1d9b9a09f5b072d48b82cee91aba955b4a1b21b077e87d12c42210912c26d3a7bccd66b5263e8b53aeecfec7f28303cb44c8037d1239ca6e49428c101a036964698fd83f6f26844da266645d29b561b0b68712b2f95c7ee7f157e668bcc3a871ebd6a6a6f11355106f3a1c28efced254324796021a46b072359b7b0471abfe8f13a414fcaf8b6977a0aea6cea96d2dcbc72d5174f96d7413458bc798aeef468df15572e6298291495c63957e98882fdc08a2338c2a202a078ee2fa81cc2783a4ed7110979137bc1d5a491f72947043d406784f8f276167e745c68ce64a42007230d53cf463eff4082c8f7eccb1a1d097048d11b5bb2c9f2b2c5f77fcab51afeb9c874c93e26f2d532770855bc9dd7c96031e0ad4c6b8af065b2c49fdeab5b17d01e5695d488ca5e919b1613143f5a5dff68d87494a97871488fb22819505de6eea777085a575c363e42dcaa9ec30dc8b0f8968884e93038cb2bda32cb002d963f002889a739cb26a21fbb9012c31353dc3123c25f9b66ef5b3c52373a2fcae8e24bc0b0200b4dd86f088d11fe280d93452633f5c2a54eb205d987d4b7e1d4530dadf0484f59bc65ba760434eeb330110d2dc02595ded947c1fe2695ef308c942fcf1cf6bee6fa00d2089d35df78b1908b63ab2b3c94bf9a8a0b937b92ca1c55655ec820fc2a24d18a001c85985d752a012672764f075838b845ef53fe713fbd3c8c13f098cf8f742dc4a05c30a01bb93e99f2e2c6eb46e02054b70e7d792708ab8c4767e334a4d0135e455d954e534f052340d5afeaf56e52c3e7a53e4dc179ab1973f887ea7819e0fd4ecea6cd81b2d4f08708c720dfac7c84b64e3eea404d97f0161eb439493b4f72ee611653b913476e05c7f19da7ccb969dbf9d9610077be51e2cd6c9cb60fe3b490a1086cff80e5d2067c75e709a20dac6864015aae532a827fd6b441db08045cbf632b78e3c8959101d2f87590dbcb68333b888ccc9ebc4c707acf8a2c3cc52a91edc32a65ca5fe7b6cecd3520c9141dcc1255467e4a0e7cfad9638e2df7287eaaa1080a2b6b4d53689fc0065379bf300736dde9a6128b6324fe8598f1479fdfcd5ef4f408ae95191c07ab3fa2375768158d9b6c58e54757c196b2a76e237b78fcefa397d6a318bef1a0582661b5c9732c1ff7d7917afb308ebd9aed9f134696263038f795306f908668dc7a8767025fbff05bc84db551cb5d50355178972d2b6cc329ece9de761c6a3a54ea4be3f62147401ff1e0a78b8b55b4ebeda49e404eed87f5a1dc3b4fb6a23e3b9c8e4397119b632697a855ffd33914e0901fcbd6b92a200b15f67433f4416fd8a713e1ac6cd9c72d15098c938dca6b3b3c9162464af638f19da232783309ad0a998de7f0d1dfd5fb57bc6cc5a62ff229a984190182f36ca560e9e83a111e14b0f177918bc553bdc16070831e8f75b5acc168224b09a8e757da7218fdf1ce822aa6c114bc2f3b686ac4b58043be1979af22632a03281c2ac55adf397b7a67840f9e478aa0dc24199e2e3ef4ec2ba729e29ff22d5d4d0b939a836d4d8c181512e6381f5af4fce075cfdc6aa02cd36277adbf614f058ba48c84cff048bc7adbdbf0c3b0f622a3c900bd1e2b415b39086a3a3c0919db17db81bf0718dea012990b6804a2f03b499b4270da2ae0ebe095ffec8662d558cc46fd46cc03539d981bb431b490ad2c4add26ea0b51102bd22928e23eeae6c5a8949308122d67e788445e1c009d4b2367da4c5a3d8eaaa11ceeb0f9bbd249864d877bd29f9b7e249f3d66db07c47b2544294ff513fbe56b4f15f0b48886a9348187f50f916c8de361bd1a89600a5db5e8c4433cc246e55601e743bbe4fab3b7aec4e4c7d5a87d8d965bd03e9176a2d540b5f6cd937513004adcb62540d2942e62e53bbcb16b0923c9f55a6799db8ddd9b47a1a864efe98248cc5acbc54ec263d0471ffb3a594344c8239fcc677389542fd71f1928def97a9b282da8cc1486704ed2deacdf64478b27bc2d0560496a7c9325e87262dc5241e949962063bdeb5ed553296cdce7cd38bd681bd3dd74a54843434212aef3a0555bccd553cf38ffe285741b00c8079b8e244b8639aa2302402008861839eb9bd41efcede7040185b71e360ce2086d04f93db17b8a8974922ded1b5fc844e6a1b6e451e0b92f1c96c58704399359490756aad92f73ad21fd94d38e5966a552975260ff4ca52defc9014a56b3684aaea83d53c8b9183ffb6195dd0139084527664ace8bc72611e0d7d416dd311ebf339a2ce22ac5abdd3163457d5fdccba372b697688072f4a2c8daf83c46a73270ee5cd7464dec7516711d9a0524b0844f39bae8f224b4c8da38a0ef338bc0cb67b65cb9341eac47efc9189a2f36f10c70a426de2f4c16836889ad9915d2ac9f86bdbd28a143245a616d7cf4964f420e3f80b4046930a3732fbee4001fded02eff75d6d4493f152cd8360f143bf555438a0ac295ed5f511dc538f31c35104869dac88f9a2ca3ce9db506cf6c0100afea3211f0238cb950fcf53af4325bf754b6729c25070b56ff93cdec281230b5b960ad60d4591c794ae53d39f1743d712336f8aa01b7efe799b5e9221c9bf66ebf9d84821bdaf553e66251fd29b1a85a7e00efa8aafba3f63e6d44d7d1f8e1d476a2d6cbb32175a5260f96984e340fd05a15aebb01d935a2df3210c7732b66a432b4d4bbdb057cab6d46a826157d07fe5aaee13f487779118a46f1a84bb05e09f1a743aa2d09f1d23dbad45fd502cb81aaa208e0a79c860692718067751b8386cd61fdc5024c3b14075a9efb131cb859d698f7fa369e37838717c92bae8ef86d971c7063c74dc85f93c03de32705d26582d4555ab37daa7daddccd370aa6bee98e031d9b1a0e891ae4d41a3463223e781e881d396d96c5a4e26b01919b0240948a261a38d35d74aa929555126adcc6f5fcb41f9a1c51ac342e72ce7a53d2c8a007d791f6435901130616404ba1e058066dce80ee9ad34cdb62ba0fc1a8930794e60a3f125916aea10f8de1c7de228755ba86350cbb86d4ac896f96d2ccda918cd677f524698a7baac0f474cfc5b8086334678d74b999362807f1a6ab790d5e1bb1b81b55d38c6f548b23e719798a37639f16a4eafe5fab4bac4f7a90a2e14bac48f428d4c4a4117600775db28cdf90afb07bf1592f832cb9257fbd9791ea977e6b2b2badbf01ef81bc20aca9610236c7783bb90cbf88f9ea7a8310053ca7662d1d8a45e3bc971b8355e6df98558f6fdfdc6d9932881716788f668873d75342c848d62de003d9a8295cadb4f36f88250482c400b71abfc968d88d2ed0f937957b6d5caa3211f132e7c17c1e2b77f167c8cbcab267c0bf1e9bb71c7d24ce492fc907891c9d26b5531b36b5c49f978be686d057466b6f7763372f6ce63028c6c650b9b3f4735eb82f504c2280640643783265c5a90725a5ac34df444fd780cc7128717f384c5c12e9867d2ee2d0490499b6a20199b862a950ed7e74905525252dc5169900faf4702b9456f4abe311fae5e7861a2b96c7476ac3448cf54f23175873798cfbde67fea38c651f5275caa79f049375460b003359d5ed11bfabbb9a395f86ddeb88037f8041b88f823a51f02ec5897f26dc85d940c25731a3396644762d079224a04fcaf9ccfd7a9a9679db9afcae42da7cea7e877ede084930b0fb9b236bde1126166dc62e9d562c3e48d813c92afcc5b7a5aa5838f0fece767ff09ae05cb6b003e6d2a5f8522e7ea9104d6b54727b20a4a25a224c892b42bf844846a5269811e8e8ced60a282b0c57de819724d8d077ec04351a498ac42b71e6add72c2708da7626bda060d3f604ded35431fca5922865e029b5051aa801646e3f6915d9d7c6c68ebecaecd857c8d069b3f94a62cae7f2dc9230c48594b069c4617cd82e82213066c32286f7da706fb7864f2e236e3dc100e40867515919a97f987553459d4e6bec30fae3b79e452a2f9cbb01112404c91a6e82b5695190c818dd1ce0ae9f41ff58f0a5aa2e5e47f47817667f7af0eda6878824b0aac25fe806e11dfdd8fd0600879eff8743a8a86554259e701c59fac14ab53af52b53426d914787864ef2381dea64e35f0fc94faeacfa56ee7e7a04c2b5f7523505a1ea9a202b4f7ed8198a00a7bdb288234b565848a6a3318a86365618a3c0b77c2d7afe1631ce64bf6a42fce805d217cc1e8ddabede40f4b425904b325c1befd1380da97d063f21aff4b909cf4a2d3e9f65f5fa9dc10b616e3e65f361e5be1d16e02e3a0d5f427aff44dc178a2eaeb4eb984501e96b8694a5b8ddd9c892480ac5ca5dba5d775c1e6341aea9861cc838d84167b95b6f57ad90ed74cce0f01cc40ffa914547aa753c3fc9c4c4bb44ee1846c2fc451b6bc86c159679303b3de2770bd8b185bde058ac2d74c72f79ff1cd1c8bdd4c2be794767753490a964ce045436791afdaf8236a76b52f6004f4c4db7448a78b7d9b9e7c41f283010276d327d041f1c21b916231037aaa17ce81702b806b0a05972f1de91cad426b8a3eedf12676e7d44a6ed984c5c4baf43f4d5c12267e9bd9f0d30059f358810070014c1437f79f451220eefac763819d273c59486494270233b5e5bf23814e35a9ed15472efafaf560d14eb64a5136c4d0ce7869e0bf011d4cadc82c7006566401da558697c05cedc4ade2d14b7f24ba332462a4eb53a297916b1402099b5b5f9968e4fa0f022e51511b5bfde0dfdebf74cb714ae7b58f804093f00e52a67be54db96009073417ed5020a8e15be7b966423965d28e96cf0b6cd3c4f958ce9c7d19d89f716c764955b9e12e86ea9d229063a6123a53cf5370e515f5f880896e619d2af02ee1c051aefca4402cf982f955bee4706c7d1cb298cfd3987bd7ad788c5b0e3b52c76dd72488f2c74a0969a61f471e2ad37a494d4465eefc03c287deb721e4de7ddbe7ff7cb3a29babc0b79266ffc533f0440d57b4b919b2a07f29fa32c6f7233a4178473948b5fb5e4608312c8ce08eb09b13214d1b3d48597c5131920c00e786effa4a94d9b9528c926a366f7e11a60724350ca3ab238b9f26cf907a4614c5a3c4fdeccde9cbc3da3062ef8df5a29554027927bea25c34e4820a7fd8e0b97bef239b106e5b4a3fd5cfd77b28d1d1802ec48973ae32a91f4227bc8dfddd5365240c203f3fd76aedb49a3c511c419bea50ff195ce3b57ccfd28b74865aa2f4a039258ae23e2d1702a6f823da6dc2a02ff23fde632613eb1de6c4e549a714e82841793ae9b4f74c1a25cbd9599f5a447d44cc3f313292a09b24d67815eaacf3aced7312254c4f42adf9488e12170e9546f35f36a55b4f3f9b6e7abf8e4ab86229ad5e0a1bb0846a3e28538100dbf9c0f1bfd05198f2126c2ce146f4a34e4b0b0e9436a4f308a42188e11e6653e855783ab330f018a0fd5d2fffc4076aa5e0a0f7dca4aef878558af2ca6eb732a04eda8f3e5005bc5efd0bbf336558d759a7cf65ddad9b18a01a18de8e6bcdd5bdaa04d4cbab4ecf428238a78785c4219b16139551278e47499eb9c51e4da84ee11e64a141d4ccb7d09fc8b01d7b3abeecc955b73b3759f77178909dde4e68c157f81f82c7db9f2f9897ce823980b779eefd0a58b25a3a938dfde2362bd1507956e296478988449b9df051d1992e7cfab080397b48bbd364e280de89eb985fa49a4fc6263d81b973fdff83c411c9b44e65bac73c96829dac60832adb11439ed9da96a0258be44abc02e0fc49d8fa207f9376cf8ea53ad03a24f13659e01b707136c67374a607d75a948b8e653577c05dffc3fe2f8bc1ea0552f3964cd43956bc22b04ec2df56013174e248f2e91636966722c141cc1320448b8c3e18c576c5bd2b9c7b47a7161ed5da9e6a01a3e8e55b46e4b9f400fe51b5923853d892af86001076088af3368d182805ab0ab02d1c5bfcc77d794dc26f557ec2aa25a6467b4210287cdcdc02f9bc0f732169777cacdd4b211ec0be9c72087bc7fc8f53e1bc697c634f4d89068620744e12a370af8a511299531db93cb102225107838ef59f2de1a79985bebfa56778dba800b6d5645eafffc6501009c8fd429bce0629db514023f96dee1d3f76be9a1072733986f97a7062cbfbb48cda73f9bff0f98ed29e7260b37517d060bb26777685bf659f8b8e591d67f0cc662d87641fb93611832d7fe5dda0b430480cab07e35923f775f071152a6243b61c83d82d688d52b93594155dbcd64cc2f2c2cb5c5c8691b5cf039f07992f854f9f16be1d0e3d1c44809d289fa519bb6f9cf0b1fead8ef3c5c6b2d1a35a604a5d7521202feb0a3e16193200a65c147221dc199c6db4f9b798335857a6eafdc42349f26caec6eba9a9dbf77dea4d56c35ecefee3b6f47804cf681c7d0e68fbf83ca893f122899b6ec395a252023631297ac99733fc0158f52e216242f9b2adf817deffc264108a0366c87987c762643f7033ac24a649322cffe8fa77f7b27699c0c23d747aa5893bc69dd0cb5017d2084748d09056d05bd279d4037fc700717f7d74a732a6162122700877e20e2228075e9b6b8d03a559666491754b5b442a1fab402a778690bdb03bef029262e50318353dad03d1f8089b9fa244d0e766b6ed0d2b3a94c17ff8640abc2e7c683cf976a55be8f93cf6802f3d3a93c3ed18e3ce7ae548eb829143eb291284c10e7e7c46c0047474ca774c80974c9e4d2154e507219830440b56b7491d5aa8eabf03397cda0db1d97ba9ee596d64843285d6aeaea492a8c5cde9204fadd19e6504cc0b3589a0924d6b8ea7458c4f77df570bc61890327bbfb6331f56b0d4e324c870b5cd854f6a3219f61d6b1d3a79bc001d344cdc99bf0fbf90ee14b4abe83022fc13da03872cfc23a02a227f9d257642db153fb24e7d5b273794760172f4f673f4a5a04bbbef9b77b4fb6e4a75341925567f79af66d5968be5502573cfce184e18c0dde7a595a349766ea3682d03e4c46801af4ddab63daab65be3ffebef9f879750c0a6d5eee1efac63e109abd856bac7cea98ac3b63b98c8c990ee85238740723ede918536e900b36e38841150b1bdcd617dbeac9f0299b1cfa1b237b8b3dd1d06be2e46c765abf680b576b2a775ae7489f526c233842357749a25faa85ad65d728dd72a4ef7df85c575c9b36b07c044f0459f4d18793e7897b434daf13b1e85e0dc60c2880b0bb8c8719678521aa44202a8feaf8649a47f23384a347f14cb29cdd1021ff49de31655034865fff5e9a97a5581f64c54d9ed2e84abadbbc133db6c02fcff086a67aab63f87217eae82fc7775aa0fa9d9462a19a64b2e490b064a3af1d84ffa0da4eb931d19ad97cec0d309e009d07c8cc837d97002ebf9760f4a9cdd6a60b3f0073bb77d41e2287e211d8cafa5a0b5c4584d1473499ec2474854ca6db546f5dd4f95b6b3424fae99534b8e3bb3a0605c5634f317602057dd3adc62db32ea5f2c4568237ceb45e54a5203d9efacb6f7b045a8f342483db9b777e702a66344cd0734013899e2f73640519d4a831f24b4b2e61db88fe3797f6ec51a35e00b21a693663da1d484961057d2fbe59211910456490f120a7c9c26dd4eaf963143c92e49ae307a3909c083c29cba3c74c794a7d3b46c7f22785946165e205aa36bc8671dcb0ebb3e2cadff6f87ea2857a6c3d8b3b2397136c04f120e966d473808dca186fa7d874cb2ca9df2d30cff935d2e0ca61d4c3b8c466eab93d4adc000ea991697a8359197d12dd9fe28c666a4c84fbc29dad59f56419be8852602f4782e25d3957a321a04f94eb8280d2256b54db46745514397be42f93c0b02d09c680348659169a63323bf3b7e349e485492243ce42e0b933bec14c7b8feee51401331dab94f89177df5f89ef5ccfb1d27352dffc5e0ca6a696da19786d3bba0426c70aaa7ef47efbe07c4a1e4679ed589cc43f0b1099115be8f450879cd8775437ecd7e1a13521d72e7ad4a2a1051324871925bf05bc10b8e13654f7dd9c7651d85a58da5c99d7cdede20fe44100be06063a55afe39d0d8f1ead5686e76fd7c17f47f5a0533442882e62606a965050e7a3d27f7d8cd9120d59c9140e31cf12c567f2596cb0cbb43f864d65be43abe88ae5cf32b84f00e69a5ade1cbe494ed7c70a1c30598f5a797545194a2f631da958d3e5de560118f2b58da5a726021138a55e8aec51d98ee25f74dfa6e209864dcfb40aec012e18020ce9b800965e532b4f4885ab73824c2ef747def0cf801a12a783fbdd98136ecb63eb197f8a73e05898f3591d82982838cc75f77a8969dfe9d9a1b688a8c17a481c07ac0d3b049a38f9d3d9823cceeffb12a83206f5e532050ccbfe54c3c911e34c2bb84378f9f8806a14d9b9a5381ee88a5639e255bb42a80e0fdb94ed33fa6ed00f6a0d688d82e5ae25b35991c368002f74d89d4a53945177e017e88fff74f4c48851e4dd565625a6ebc3df077b33d0d7b48347a06f7d3085e7c1b0a155af4a4a6de3975135a66983c3ebeb53271b2d39a9d03d1d60abebce56ae97fdb44e82898cf7a5b67c38f4003e108dcae16ec9e592f88fa58b9db772f4f32012ceeb1f4fc34923b6a6e41debf19a9da038af96429d5bee5a2826bdc2e76c5060779326c283fa330d3d1aa59c3cf82f5f3014f8931bf92a9a10eecfa7a0395e7a2046f201080d1412a75a4c461f1a0bc9f108c58e87f949f8bcf17434db3c7b746d78e2b4f7ebafa102b9c81ba9f536433ba91b173f2622a18dfe3822d79ca5f93093d4d17fe0ccf7366200c087039912bd4b03176b6c8db9b739573358448d76cf736e2ebb9bc133fe13998a68c0a00060f3de960c6029a75d99afb2e964880c32e8389cd1b2be8bb89cd680ce2e043916dcaf81e8b6a75f936a0d8c2cef973ed4388a9e621ca7707a64c91be2528c723ef627bfedc8ae2152cdbdb6ac1c7c8a239a4003a8b57824ede4cc3589b5f1b4d1628c8dbc164a39a93376ecd7e6d85f5cbb4ce83bbd4485472956b9c6e3c84d8cf7674529c0eab1cbe68ae6348c6a274c3d0cf89459b80edb5612ebfaef7ec602c00df1ef0ef817f581759be7114a4ce43374e61a1135546f2055691eed65ed1b7ceb6151e9efab8172ab36e4bd417956a70b792c517975ec50d73a7b77d143b91dacadfd5f17a9007fc4c4ef9a317c8615b6a38fe8d84fa345c586b82943636f492a3d8322bb81793ef1cdb2f78381faed4d848b8b056ed86f1dd04625a72b10dc32d7740a0033979bc158afe9b19916140f475b5367f814d55ece5b90a0cc706417519c8f4312ef563b0fe1c5d7ba5c63d13dc03b05282790873db613e6fb45a410cfda4f0cb6228240802ba859b09e2713b438a3fdfda94e633f535baeea281171beca8e8adc2e0846b77fef97aa8b9fd349c8d08e4162d0292818992cccd758a7f3d2b1a871d3225aa4cfbfb360c7823ddf4d69af06bd6c6dd034021dff8cd610961dc2ab16a3e07a0e7b7e63f6c476215b1afb363f428b59c6ab50612cd81e9cc3d0ef63cdf2a63c0adf58d717f49a40d6da779618dc2c41f8fe3fadecbc773b16fa0beda894acc02e16bbadfd6afd66f5efaf20286a5f7db2781143c7fa5c2775d90cffa5027dfb02c5dbf74a1c24567cfbdce2a735cb9b0aea0bc09b4afaf0db9a8bce0c61784b5d032f4ef7d3887f63c41b0d46822add1101f5f8c80db564011a477dfe89525faf10f2f4b6aa168ffa90ebea48a5c703698a9ee6a263a33c08fea21a1026926c7886a310935badc5e1bdb06b09c307720f0b56a209da0fa3a356223415b014b6572985eaa3eba323e4a7343b0691dd988720737233f6f88459e7f2f57b248762794e20451f604163fcf5216a114153b6aafecce6fba28fb4cc785edbdc06efa25708bec02cd342c30ef087a65a0808e4c53341054a2abddb38db5bef9f5a23290af3ef4f321458a121d65c04588bad75da9431865fbf9a3e8ac0fc9cd92550ee9d8505bc8a24a7e41e1bf855cfd587a71bd5022123c8f74dbb37fe0fc11774492f439839d84d483868fd9514826cf09c03d59ce8b703d509625e3d2562f6e455c003040d6543c815c836d5d83d74d6ae9926eeb7f364ec3397d03ceb6c89c6a905d2e63e44bca69f89643b6353e03b0ac49d68ae86f72552a17aa2b244d2985ca388a3602438d84bc157b034b618b39ebe0480bce56e20810db3b7aeb083a75d946ee160062d475e10b500b7468ee95c11362d404a0eaf1620efeb25d0d5991ab68f0c704ab04e336d2a5bf508421ac59fee6fb158715914c7f650323405e475b273d2f4c4c93f5aba08d4e4495a98c5aba3c55e405dd86978381b124106540dc222a790cd03de7f363ee53cf57e48e9e589b539662a9b07a6c02708393889d0001d42347a77e4280f86e9df9ddc25d6e4c347b3ec8645c1b16371cc922d76e74e061e4478887d3ca020a1d3fa5bbb1b9e5f8aa8aa4a2c008bdc9d8c7df1e4e8f74dfacc35c0ca5e2a8fb0c9dad1ac51ba630e5f63d5db4560fd707cb48ced609a065ac84cbc601b0a9d1f38d0a92f86deae699151f8907e0e9354f603ee81a70978047162f0c8dfb977da0118d48cfc44d4b38b57bcbf233847d72b22c5e40624b283a7ec0ffdc528b630af7c8da13b3f720958666e8d2e2ea02c66bd502e32d78ca7668f50f12e30f7573638ee606c7bb80dfbed3b55da1439a8d6e19ac90bcd0e0248045a072d322ee553160b4e0827f564c068512737ee6f46f71821f7bb7a296d18bea49abe2acc587359da06d84809379753dd448c7412247be46f4a178c16817cba3c75d8bdb2902724fcb441ec95b37e2592a38aee0504cf76cb2a55e5671fa5335a5a400c464f3369fc9be7f78c89021842704f6781cc29cb995316b508e91324067c375901c021a4a28f96c9916ec7d2f78736f434062d7fe454bb5003c66fce0c04f5caa9ed6edc697c84f13e9c274c7ffd7cf2a3611510f3100bd5dc6269bbd48ff0cf994840b549dddea6f74fdf70aa98b3660b2e7b5db04129c5bcabafdf4caefcf2a623ad8b8f45a9cf644ac964bd5201e868e3a29348f0dc216455f4db08643128d33797e5a4daebb51e50ab6e56da35c1999b5ddf2efe26b32c570089ceaca6dc512113b346d431c44daa12e1d512ac311595798d849f287ee66cd8414cf1fe5268e06024df0358e33da74092c292fcf777b1c277adda59a16e93f19d6b94c116585974166a47ab53f91727b5e7248090f2f692e1b70a10114890758db86a8590d1d30d588a15571b01d07425f146a2d1ae0ff325206054ee53689709a13beb04647c62493cf92364222ab2781bab03d06cc84ad9625e0a7204e9f7cae5446888ffe25498e00e866239826d6625ba6c34ea96a140c25d66a60a201ab870b6e0b1e47a3d1b3088fb9f6bba7759100d906d220d6f409e08b4688ce0e7fde93512164d1c430c082abbc204b7303c66f49c671967fb24c0df6c6d65424604d159ced5ae844327e4d0b22caae2e3d1aaf0a1b3ad0450e605b59e40d31fbab0923005c9bcd34b6c4ae7d50b0ba8342f5738bf48e2e4b7c9ea33a27c31981ca912ba9f102c0da18862f0bbb58bfa9a233e3729492b2fd082855321b1188f5f88616c2a19d799582871018f68acceefa5800bfb748cad2836b6e56db4472b6237e6ae4f580dc480b2f5434acb149c09964908d934c34f74ff680d28593c58d5db145d224fbe3ed931b77548c73ee168656033636649a449dd03c334c24545300fea49389bb59b1b6c23672295b49ff47842c87754b9647324515104cf27e4b44fc5b894204dd3d3690180dd16b7dbc655dbb54585fee9d0dc79d9285a1c6d84dde9e26296d1c2c8f09e396ad9ea1e09eb231c2f48cab1c6a9edcd3c441cdbc375e172800055a9f4b24ea072575bf5e08ae9d57b2df6de3e2068751985df6dd09d380bd8dfd1ab0d3443d67857bd726c64bde9dbd1d9b639ac9d6d5dbdf027847090c18b2351519ea79091457f5aa8ce909518baa71c23ee677c234302864e8a1fce9cef243c07ae49669ba67679324c05f9545ea837337a86575dc7dd8df0c39a44195438239cf9f35d517d3367ac77fca744e016ae992334c6b21fbd845abc0f11a8a09c08c6bbf116241d497f396ea892c2e9beff2a280e790c8147349f2d33872fbed3ae9c05542f1cfa54b2fc2340985d49597a611e94cb2c3657731895867685d1ad801aa7481550a7e2129fc0b9f8d854005bdce5346126068f5adfd70b46f8459168a0a735186f2561e2cf0bc49ba91bde23dd27d9afe0eac52652f71860c8a7c953734e9b973a810371bde9d04f7b697ea5cb670df49817c32f96d53d2ca94ee72d3bbbfe2b07c2a6e49ee742ecac986efeaea32dad5b4f04ef120a6ee145d879d7830e5f2c402817a42cd80fd2d03415bfb684346a8f8b880ff0c5e0ceb872affa3d22f700e2bb4eb524806a9e2c6deb72b41f10626af2d8e49fc8585612f7a2a73bd24d4aff2a87d6c65245033c57820b9e459e0c781dca745364c6b6d9d36251b7d4d6013cf8387b583ec0a12d01fe0c1064b2b319b0af74839275f284c7fa1bcc22a65f53f9611fa388d3726354f0e6c6f5c6bd003efc376b183a90016b0a6bfaf0f77e9225ad9474bbc9c2f5c7bd9724833c46b581879388bb4bb32f8cb247a6378f715bb455061da15a042ce3f593ae32487114e3f4251e86c4096558e38397cb2cb0a28bdf71f15079c8089049ee90e9f3cbcc4df44ab51bf35dea5655787b06a19c72fefe3bcb4b27f8bb3d09e816fcd1f2e157573c981df32a7a33b7d960ff391868db495a88a8de052bd35465356e46ac2bb83489559f3d71c758da683b53900736cb8b438850652c7471673b01c4879dd50c94183fab232cf65ae830647df4fa22ffd779314c1bf96406a9f23562b9c3def15b62efd664351e32eb9d18c0fe18842fb20a6c1f1650feae6f61ae7ec1e49b677c66bfe75318cd4432f5f69c03d74beae8d226369bd0f8e99a77f0a0bc3d33898f2f4e873b5282425ea083dd6e017931fbacd160cb003c9a72ae9fb230cf22936a5bff07f5c134ce1d4ba62b4714a36923f2e06091ef6ad973d307a061c9b73959397939e394c9f8e40a2c3a5cea729861df4e85250c559af54f968869c5954ffd8a284dc3d3a08b29a7457de15724f31e811857b8ca821bcfa25c7ff6a936bc0cdabbca05ce0823ee9f0573775b0bbd44c1be55164eac296a7c92dbcf5463c267a2a494a106c618a6bfe47be52e0d76e014c6c356b64232ebd010198564bd9641459bfc92f1933ebcdc3e4306a72259af0f3983e7e4e725593fd548e8c7384655d71be878c59c6846eed4b7a800da2a2bd52402887ef22aef5f8211360b9aec6061e40402e101c8afbe4a94676ed457f2f7193705b4ec1f02e7df3f0db5013b6117770d1cc856156456a777b9794863a5d9abadc8e214ee230d55e797675a4e47609d1d1789248bc5afd88dbb8f7ea59306f5a28c6edeab6e58731288e4ead52245e1162f6e69b0f7642cc878e0555ad1591432c74d6f8f21b66a03ca84c989fa0311152d3c14f348cfd9a5af0dbdff90efa8909ea223b1f9ce91040e2f62f1cc0fc1f212e38a12457f768c58043d04da0c585b7862089ef0a3e1c88b5af57973bcc7dd0272245aba0b7fb06ec26ec5f62c4bf2757e60ba4acc795ca13516dd6132c0fbd453aeab3bf5d58dbdb87dde3356afe56f3723741f211ba52f9a2bd77b0b8a1e0d56ea59e9c23bf0b4eeadc6cab0357242487a45c53dad72d613f12d7b27c88822c00bd38a8cda8b850cd3bf111c930d7d594587cbffba1f72def706a0db62c24bcd3b1b43ddbd2508f6d8144b270103c7c197136c1cefc17c6d218ea59e027d402f15176a39564c31af3ba8ad64bd1f8bee34b89ef49eea34bf26ec34b79788a3dc2ba65499f79f13d98cd28086876fbcf01a79380c3ef7ab78138d5040d6d70652818d15da5aabb234f29065172509c691f5a2c0ebe8f11d8ac7b6710cb494bb981032817c1a4c283fa60327e8e0eaf704a9ade5bfdc0135e5756f528ded44e0da4423fe0938abde28763cac7e05e702c2cceebbbcfa6b1aa124b49deb80fe5e087f75ec1d97c5e01f435de5ea66a05555281c30264a2377c232ffffe631accd1494ecad617f8483238306ce67d6966cfb3e9e57e274e9c5bfcd1e52ec066262a24cf698c01e66e95dc015df17b14367acbeb288a549ff98c4795bd80ccbc16d82e47460b802b6224e16b489fd514674d5115f641b3865b5a88deecf268e6f0d508d59baf1a055ece3b9b495099aa0e65dcf323cebd9ab258ab5c8c49c2396088eeb0e4f3cd1144f0c6d437dba6f0306be49da7d196654a6fb71a8696ea17b081d97b33ba04f9aa1140ce31f513a2becdd00810e133dc10dc39580ec62e2259f939d24b1763ddcaeb704bc6610684c012b2b04c0a260b69135c72e013a070fcefdfed779629c28039fd63ab456eb8fe9d236f5492cf4690c5a05821c86f2e9fecd2f877090d7bda1a098fe38a1b69ff55a3d6c70cbc524a31b1c9199e7cb76a05599a931df38f3a407741788b2454368db95aa4261c17270013261c3dd7c67d9ef17bf916c70f18ac427950427120c23fe448d9b2c79e313123cdcfc47355403257b6aa28e13fb3078836502bb21128c8084cf4d46a35a49c4881781fee8bfdf71095c506d1945bfeaed82b46775c0be60441da63c9af0e91f35ea759fcd2cb909f90be4485f5f07a6df72424598b1590ea7f52c15dc62f87cb6d34acae040e2def944eff8242d171967149fd1179c7bef825dd22e8b149ba054329e885efa4479aa7e71568c6d02e1a7befad4044fc14ae261edd9ed9e2552a9c922914cf4f3a61f083d1321d4249153afea9d73be562f846ab9feecf193e425993f66f0baefde6102e7f74db15e9b36c99fb8fd1f6d445c5c3bd69704199b8c02640f56309242ed2acca0e02a0ec7ada2c6954017a8810f8cb88c9f15a34175404a51cc0052863376f4999004c7fb2ae4eace5cf253b2788029a0db7b77cccd2ca2d66627d0ae197adf2874f80a03b2f677d777f6764d278552f0c40648545257deaf7b82617b77a8832a899f844a6002b1f23bbc0f9ddd0c6cc1faee634e196c1dee2678868b99590a2b2dea869ee03c3a38539c126711e48465bd56e67209ea69e2c6aa02c9c0a61244a728d34af4870d4af12bf84a6e3308dc745a045d5e29cab77ebd8664a114855d9da3e70df35ff41f4bc9eece5352117a2ab1dc9326b2dab3b5281ebfe40d42249bc458d1451ae1f5193801a8516977990cdd6a54c7e2ec66981ed3eea62d356993cef627e873c1fb194ec420b1e4ba5feceb7f2777e6e379937f3a29f75997a25826c228ee6fa4de8caf5da2ecb63ee9c1c7e208c3fb4995dcded9425dae69f8222bf05bfd854da4b69bf54b306b77882517faa6090b5812e896999e5036739c0d9f8215cd208f9b61c9e534e207fa53d5ada10cfd8ce9c3f4edb45cbc774b2d999f33c7ae6c06555a53376e16c194f1358d0dd928c1962a7d4ada5482cd3b8b2b2eb889ff7512b1f3b44118197bdb67ca74dc0364346fd4c8776d6bf69101d58b489b5bb897201180d67ce9e81cd2c2bd02bc0d61970e7e5bfa1d1e6219bb396591335ae139a8a3da29d853b6b29e835f64406824e505c8827443b8827c89ecc888758718e06f93dfd6280e6a606d269f96d1d183d9d2f8cb1ab27b0733b1cda954a4851088f59beff780271e24f0849636d05caab7df508a38f92e147e434c611923100c58142671e681f2a168247ffce153db986045eb56c43deec4a29a9a2be2e8de689d41994c97fb06f667481faf272573a3b949038a0423d0a2f4328fa7195910111bfbfe6e076b50d35d2a613308d52ff062d4bbc54373ec2fb0aa725a92822b07a8406f49b829a0238a3be02d061483fcbd53336a957a965fa5f28a2c1f074be21d33fe513c6082850311f3e7c6aac51644ccc321aa45291cb2f96ae8bb2d074a97320c7fd30d25ca593c26d042b6f5e22c0ccc54b43698c20c5dedbf45e1437efe15ebd117b7f87a08a69bf9850921d7643d44b35ca212a15ee747c5c39d1dceeb55c4c7b98d5250b9d38e9800d724c2d34fadb0c6666d7faee8d4733f8ece21d4448de84c23c0ba56c421896703ae41f3f59f04a5aeb1131bb106232d5d8b7087ae9f3b24f9ec5aee4ba1c824c4af49b52d5d285e52248b65b8789864790549ff783ece3e7ff2876319a3f8576bd8a1ba8f47dd408955074c3c3973a9d1764d7d20e7334454abcdf1cfbadf6683e665d9de19a5c8dc4c1bb45f35bec348d9b2f928d2fe90d82a687fde06e71d9c2283f3910447f28fb151a5142966286432c1e1de6d485366cacdbffe809f606bac411d52994fa0ece3a5135b93b931f7a9592601f02fb7c055222686d1386a23bcb513a7908b0f2958077a9ef6ece7cc786a9a2e5454238cc0c1a7f25f099e20734b034c038f5bd727dd443c1c9759929a7ba1303e5b85e176b94e5d7e0ce85059449ee6a5321311bb7e0c7d2936848394262e7d406eae4ecb4d9cbcd85eadf62e31831af30fddd09eb79f8a13aacc9c9096108af8bbbfd53c73bb20c4c34ca94c7bf332f5052c2c1a4ade2c67b60753830f95cb4bf1a6bd4f558353a51f2fb565dfd9dc8db7035fd86b63576884121410677278d1c7aa457cd4498669fe673d23ea20abaf0dcddb094100d2441dfb116785086d1e76078e460086f7f60de6c0b881595ffab13ad1b26b068674f4b81a65e9eec7393a9b47eba142ba8e9e33e5445cb13090933dd87978623a695c757ed424d319fad4a2fc2cb160095428c7a88b86398bc76609bc2d6d86c85e0d6446cc5ba7426c0b8604b6e0a9d090c5e35e59df7c019220298e6544241474a32b3ed1c82056ae26b474330fb355dab30917a89a666a0e7ea3781267b84f341fde918a398621da7390a0c5d58e2aecd4f65173bf7865c17e2f009739e4575f648102928fce2cb4c46efaa2fd4fd4ea2d372600aa52bf5ce378a8fb8d982f0fbb27f325410f51d5ed9f67d2065ccfc26a4536428a8770d3b4742c252d10e74f5098d049d63a3eee478e59b2a28ddb83593b1af0e3c430b5bdc5bc84549a77deb287952f6e2f8a9530ea3d9a3727baeb6a121fcc0d49d8710c31f69634355e0a57606d9b3a1523ca89e617446195c7ce36747da0b2a90932752282aa72f8fea4159bb750000d3ff5368554d46e011bb0f51a6a7054eadd670f4193d3a54f1b81aaa7ed7d982e23c0a2f32b46e0b315a606c2a169c4e28c17b513b0a5da04e54336652d8b71d75b8338f721bbef468c9b6ba65234acf54b1d86b57d49131b7830bc2a6aa50d49406eeb6977ac05a9f3184a9785590717dd62669a7cc8c378c525a55f86de8097dbc6afc69e4540ef3b046d96689aa322880c7b63e9f45462383e3ca4d7c0f5e794cbfdc5053ff3f74231c3081d3da6827822b0c33328a1c4a05f328ad7c229ff30d1530ac66825dd0830dd76eb850626b9203574bd2e7c4f2c911cdd021eee1f0731b2dc0eb51037108172ddfecd0fc74825fd38d31b2960781cf07a292aaa39837c1ff959faa32e943bdb8d09ebe18100293ef5116de1bb06568cbcb084d5664f34226bbdd501607e3c979e75e5f0ba46cdf5cfa9d8501a9f682f6e8af97ebbbef2497aec0d008d4628c8eb024d8255c8cd5f68315e188067a6fe57e259c7635392a6a6b997ab1f26e5b5a7f4d5353ab212811afbb54c69bdb18056c42a65127a5a4db52be5c17c49b6467b304b762d25b5a2531e07f5c3194723033d5c21bfd4a5d8cc77783326e9cb8b36f14b0af6dd04ccf6a6e091ba8990b5ecb60759b5aec6e3a172cca9ad74ef3611efbd89d34bc34ed19cce8f4e86b2eb5f968a7ff273278fb34a84fe7639ee1c7b1f57de448f553c1fd587d4c234b1ddbf553099d445c603989d7cc04a2f88b77c6e24c91e1ba4ed1fc09d39d659e4886ea405bf180734e6583ac8b1b8d4f3a7a7137d5df2f1bb7cbaa4cb0ace89ef4b454a5ca1297930e7df22db484a457b2c97646482c3b90a4932fc7e88505f9b6a9dd1fdffbd44d636739e263185f351ae0047f6740f203c75ebdff6680fc3fb8ee839092ec7070b08426f31c9bdb0853654a41c42234854693fbb6453428e0643d934fe9aafc29016017dc7a3b9ecd8f2ec807650c23da04f61b3cc64f1111d791bb0863ee93ea71e97863405225767b4d1980bd51ca67a97527700ad0f4d12049dad252dcc48a561d9ac9d6bc64c317405a4215e1629b074e769f3b265dbe61c66ac100dc5d6522358e87503afba0dca67a44a49dc4c845ddb57d3ec4be7cade01241f97d377162f3bbcb930ff600d7525df8d4bccbdf48cf52bc54735a8f5c876d4e91959a339502eb81608a89f0dd6b8635febaa3cde823d53e1a409388941cfece9f436c4a0d95d9b63d6ea3614757a42f4042aad13813fc30f5850c9080f324aa61d3d19551e832ad698aba5107bc1d46347bffe4c1161d4afab2a88dedf8a51c3a0e8c427f9000b3481930fcb3794b8f0b6cae9107c45628c5a1eb00dc0c225181972a686461035d47903d51b9ae861c62a0dbb615142e47816b0a074a8883cc2392cd65467fccea698d64602ba746372cf6ea702e4e36f1803cefaf1eea9938e4732df39c06f83a1aff41fd4520fd162a31562a0cd81d7bb3a0a1157cefa08f69c6610643cd0a4debfb4ae8a41a05dbdfacbee0465e356f847a53451e9ce965826c17e2bc6abaf757f0fd82507a116f5fdb6eac34cf47f94ab68a95ac2017719e6ed925cd134fe93daa15e384b2ddf9e265dc579e68859ad5b514a53418f6ebc723b3c32d1cebea46f7f17f457a30c27c5fca3cdd932e2813f0a299834bcc32a73d92231c0aa7f6710d5f6779ad4c6347e32e9e290256927b436b250c9cea0d4286773269a73e1ac435b1eb525c60dbac9ef9accbb2a6f850b8d202f1657e579e2d11d86e10a4311c49857fb2f0187e971c120e2cda6015c07c148119207a8038683a1451955787501f5a3209173748d1666148a7dadc1c3633e08c6981debfd0ee1c0194d16de259c9acc424be07685abb41e2d4526098b4ab3e0baeb5945ef0819b6ae89de5c98ed291c55fddffd82f8d21a88e600e00fdd6a8a7c2d8981021542c30b5d062d6a7e3bac076f71877618bcf981e279b3bab17787c4669c76346bc937d460bb6dce8db2f018091a1182fc1620ff016f010a4eae014584519a7e54314581ab1ce1ee8ddf6acd5b965cafe75c38e9cdc36933b16f7d83d56b1045ad1f3d015a675fd8a572ae1c971aea1af14fb3c715d6d7baca432bda8e7c38a568bc12620f33d36c2f3e221e1223cabe84c170d26d02b604ff43e226473bfc1f0a60394e6fbed07c3ac92663c7d4a88ae2a84a5dad385dd4a8550dc67d6e60812d923a2ca24b1a9b09b28c37d3edb4009c897cd86ea032dc913415370f641ad5ff579da2d78ca37492e08434fa4544211c2a2167904d25017b4d112fc2f92baec2d2f81abe20e6770089742d21df57091d9a403ab7047eb79a68c9edbd35c8054a2e4ca00c1f995e32fe5641d285614a2d892874c90f33eb182452600d6f027c82dff51dfbb41dbd395497c6bb01edab32ed08557122323a913ecefb22060ad3e732e495c7d13213ded28bda2fd60ebae2f80ed7a04e52e395b11e4251adf4130b2bd95d32e3c1f91938fd98a392ba3d496278ad0609e0b02a9c8b91504159ea14afdbd400a2d6f19bca636332b3627e47f958cf9d3df55147fdaf37bc313d6a3a09304b7cee1309453cc42a1a1184f6108e0c541f740f0a5a3e32bf967916a24cb57108f2bc3700af5c56623de3fef6aa278f6c8b04965ac38d1317c227e4102c7fa874321280e0a29b5f1ca14e5f8f728a35b2a45b424e5709a08187779f564ef1726a141003f59e5bebd5f08677f3970e80d8724f9c5adeb849301279873b3d13a0a1ee114ced55144ba1f97eed809ec6010dbf83fc5f9458f810f24909791778d2f21fd9cf862e4588b22d299e79dcf595f59adec62cacf397989ddd46850c9d06ee88c9097bc33732cc2348f881ffd65d4ad80ccf0cc84ef045ea557e00e29716df0e365498639231ae14cfb0f486b2407e8e16195a83220f2f95ebd349741595e41abdcddb91ba8b735fd272274445e8c5980368b8e725cf2abf66402f2ea8992e18b5dd1a830740bf06086dcee176731216904ca1f7e8450f6b27a6a2936658d4fba8294ba4c4a8b330d8dec659605942e9f0a52a277f220d02be4edcb8d99ef0b8e40dc012040af3a5fd06b37ba236e03aa8c67ddef94667d0135359c63b90dec611ebc42041385355b615fc6a0c7f44f469af49fbb226a58523b0b27b063b81a4e3dad3d5d28081d183040120924dd4451012974d67b3e5b4410a60a9d1e17953d49d486c2e3e8fd07b7573e692cc6421a00ec91a21e32757f56017b6811112d8f0d3d7d2f9c142ac28fc25de6ff07fbf5ddc4199f21835496035a11bc6de67bcb5d2422bee7e13e2f971d979727e641af1d0167880fa58ca37e5a6e5e7e41b8cfba9b84e8a9ceb964438e901039a20719a777b565c7c39ff4c3c3737e8df5958f3291544955e240cd48910102f5213c776aa0f4a2f2ff170a57e7bc6eefb953833fcb6cd700ab6300a3ee51780e97663d4df085ab4170589236d5d5c0834c2c2d221f6db3b343f0f909f0ed921d25252971d4305839bee19bf06ad55355a109697685c14c59d5a5c9638b1ed589859e0ad44a2baa94cb9d54d08ab25c8a28ed331a9a1544d11b6eca5b6023f4935c42395a6d05e189ea3d6db6cbbcf13cd0d9e88e015291ae27f3a00487b8f0c843170ed1654cadacac8179470696f6a43c9a216769355d8fc0be48f14a4f018d3ede31eeb264b237a99f838d8d4b26bcc3a8b56414c50ceb1042c69b4144bf7cbcbdc0d9a7e2125ea4fbe2c7f1659a3a75c8a3f20c4c4dea21677dac5cbda492be57ddfed2d00bfab9b100a6e28ba772f5ed4d130140f5b7fcad20c05b1ad22032f2dcef91c92f538787bccfb633e685a7823b3ff21c4a5772a683487b928aa81ca170e46a5f23df080f088bbb6e49bb52033db3dce2aa592e40d51fbd883157660acc1e33c32983e1c41b63845bdbfb2524650cf3ebb054fd6b67e12a549ed0da31dfe5abfd6790287a705c4506e356308a1f34450a74c3a51ffd57152d9a38446e2dfa4cf578871203fcbd2dbb191fa440d5e13eb4fba67f3a4dccf7ce87bbf5e35afa342a2230b5688f6b2b6d46ab24457cc1871cafcf0a108861ffefdbe0f159714cb3d96c06136e69a95543877e97ed01b44b863b2ea0eb1c3eaf2f44395164b5deab98913c50023083207d3fb9728cdd66e0f18bc7637c9a53eec7c45fbf0d6cf4a66c40a17ffe9728305af8a73ce2851bc9a1d60de719f0bcb39998889d08610947c02995596482d63f069115c6fbdd40fed573cdef624e4172922d3c3b7e79528f2f54fa3c1366f015eb237ec38eedd6b24e2088e4c6ffef11044b211564f2dec97c1cad81a0baf7e53d20d47a920684cf78c2a8e9c396a7102fd902818538fa6257fe8e82a018a277a200b7d623c4a4e11571565ca62683b03a21e32ea500474e2bcd6a1fbba3c154c8b5c226d2ea3b5d5fb7e36126aebe1c65905e968e318f697f7eb94cbb9ab346a1483f0006956a51e61613e13f8a782f7a0f025ec716236c226522f54e7fbf5cb51428d2b629819d829cdb96c57b50740c4e396eaa0536d7a32814d4910bb267fecded0d6118bf765258c277ff4bdab746be09ab3dc201f6f7689d3986498b83c22ad25c173eac39cdc930a77e1bfa3680bdd7b61d3b44058442e1478f6ac762d45efdaf30ac9d2b408489881565d103a700b8133f9b456ba60d0107c4054ebd73a0485245b5c0208188dfcee2abd981f3a6f93340596c0778328dcd09d94f856fce5c4376fd9529aafaa1d115efb500d5cc46e3072c0a1a3d9d1a923ce17e5a617f810cd5361ed6523fe09d99329ced89eb5987effc41a4683a454fa76b3df0c2e26595498fd6b23fe7c79908edf89de1c63981730a1c44516e065029e813b5b7552a0afd3ac74cf81070d90bb5bf78cc63828beca13a0761b26e7583289e61a3714e5cb1e7ff97e1c8b357ea733eca3de145cc35b01ebe28d4489eb81f6dc7051c6f3ed95c2af2bf7f4ec7585943e697dbef511afe88dbe5ed0e9dac1e39aecb5e5fba8da3f90026df4aa6faa13bb8d0dac61ba5c67131af28f013588b1ce4ae9bf0296928672668b8d3f6a120fd4ffd019162e37ce5b56a1b5057654efcd9d85718dece6491549967b7d1cd89dac7358704eb6ddc57e8e3aea45fd05d48e3bb9341c0e03cd25b58263de6bc11061fa0dfb5c772c038da97b27d10a5e462847c4ff50ef9a5f18647bd68a7fb202af9cabee0c7fc60b248a9a62ebe987751aaa7e73db0a60eae3026024700a36f95e8fc1bfcef1950a06debf069ea38d01e2d7cf1984338f8bf25843fe4d52f994114232c9a86741b42764873fedf3e1b397c435f4f02b6443d9dceae8e58c4f52523bb84226efbc3be61b83137c43d5c79e0d425afd639fb0a862517fb667aed9116f5c87bef9b75657b4b74ea91622c3a3711c28bf9c06dde8772685ecf12fe0e97effab6eaa2a52e5be1b3d6fba65de6d5bfc3c937de21992b14cc973ab1198cc1dd5657f9cd036a99c1d3590f69dfd4471af49cb5580ec94a859d8512f36b101a3e540b13da8fd34da1df2d933af38eed018efd65fb10ac3a1177fb4caa5e992b28a26494475d4bfb1f0be89d4e2f380deac2c02dc4a672be6061d5e55062e8ac3f533b79e3f72e6b75e701b7464889652f41e2342a54e3ba1d9c659cb294aa336a8e0d177d07b714ba42f0d63c37c96820f0ed19a1f9336b2f538217b7f2953a2fc74df8f38f8d33cf31462fa744d21b42dccfec998c9d83ef5999e169fa746f39cd0e7374ad55346ef91ed514fc5dbaa15641236f69531fd280db498c3f0ba02088108b21d7c0e5a30acb17b10e61c86a017d4bfa2070e1b111533bc4872514e1328c5968b754b7fc407daa5d86040723baece7008068161e6c56db4325b418f66328e24b51ecff4abc11d6cab9e9fa76913fb9e1ccd5cb312c8cbf2a5ac0809fbddf70b7d6d1f925a1a9230f05873af3f45d185d079af4db42a0d086ddafe89a2905b600ac5eced39f0378bf4c11fede792187e9fa694411e06251b77fb6a427f7eddd148da10a4250acebe673dadf81d2b110f0618ee7656eed502568affc88aa0103b65a8d531ef31b2a8dda86f6db5fad00c0579d52798357acabfb5dddd0dfbb2d955757643da1edbd04e341e6e13dbedd8a227d92cd2fea14d0d4182bf2a343c3f50e60cfa2130351696cadd45b110cfa3661156a2262071a1bdf43d2f537a38234f245ca56ff7ebf2f609003c9d19403ede9f80676bd5e635f39e1b95d0baa9c692102ca965936a7b91cc78e6f8cb1ea41bb5eeb7ad4279f4f4a3550d3c28e9841c8926ca17da1c0af0404c0578c43eea32b8b4a8a9c6ae24aa1ebd8b0811c38335bd68ea8f533b017f927737e624dca2bdb167a2521d9f8a2391b4abf705c731e7913a5249042c2b0d2216a8a1a2371d597896094f5286061225a035a050066782169efdb5074209504ec5281ae5351e26ed8b277a83d3f74d7ceddcf0b9ba891909a90bb9fae5cf5fcc880c56341cd14a486c0d62735dc94d11153ca5a08f0d036bda582c3671e63690b17c6f77c4b86e75ff9bcaeba657b127379ed45f87015a25de815cd94580354589ef1383e25a3f80ba1228f05c55bb4c36b86305b7257fbd700889a4ed4ba156c4de03b83438ee10da8827c0786dd167710d9058522f9419551867a349442288fcd996f1b965b1f89f7b25aaafa5fd5a7f5e6830c27600558e76a0a71c67431eb69733c446c440ce052db5ad1a6f6e338e2a511abf48efa44f7950937a73f696b1b5da0b1465589a76e61fee3fe6f5e947fe8150aa78d04c54838773e6d90df28b6f9cca20233c946153dbf4e7fe89b35e0584009f9fd5a29161cb6a29b7c289efb01e5781da2feeb70f1ac468bbc3e12216b9e1f5a876247516936b2c0a307bd78e1bdbf5effbb1a7363db9501a6353c438d61b35eaee9a2012cdc25e54d79ea761df21620c6dad25365b3f9b18c974217e607da609bd4f56935972737b1c111b70ccb60009d66e186cc051d9017cd045d434020e64245107f18b5b7f12c64a4b59d9536b0a8b51361a68cef74aa72ffa6801aa3a77acc6b5d8ad39c22783c951320ea0d2b1669ae80d88988d5f974a0815cb9e6b149f551d227a6ca4536148469a093eff1c44a4c5f1068bdb1c3e09143d6aabc3c28d95d3b4a592b30c55889bdd7c2af626851c749efc3ae830b3532c8f090245c7c994d22050f4a733b7a81ca0ac6a5afc8f4f09e9398a6137b0727506a7520fbdea6c1ff795bdbc61c40f0d6bf40ae69f4b85345dbf092e3913008fa89931da854be7fea30109e5c21be32ceb406d946ca76dc909462cd8265d67978206cee59f8a316a8814baa72c4110a4a092e757fc129d7b06b8483d0136f6ee856af8db9b0ca2b554c227acb11de5154181b2741b0219dda1b36c2aa7e77c1c0713795b82788c9b32f49641297c385f12b527db9971f4f1360affd0a9de7e75cba7b6277638261d2108856b018e22c1126c501bdd5cfcea8144323d1d2ef36bc49cac36c5fb7aeb004dc23c3967457ad9f8c3cb0e4374d9eb1c4738150c95a3d1bf3117e836714a34885c8b907fa510b93fd67da4ace26dadff9e406253c43025ae8e69a49ede9f61735947d13212ced23a1699c7a1ed13d9919e812d01f6a22f5e6464a4afac20dc3841fe95411e3179761cb8d42d47fd542860f545b8b4bc4b95e29f5a280d4e72fa78551850a8547596a960c3e2a9ed8d4f361a4c3e31fbbb704ea1f68dbe34e64c6d856166f38f81cf1431c856e5d93345fee56cfd624feedfd8e083a460a01fb78c1fae704fd1916a0addeca1faaba17c42e10784b8aa6424c5430af8d8b43c3912a9f5bd517550459a93aae048bea91a2d946f0458411358df44ac5ac79f01c19fd44d839d9c8c5396a8ccb6c9df607b4aafeed3efa37fe642454c5a0a54ccb4a86450593c423517419b02c65df329ad1b9a5fec16136e0e71bf37d253811d6bac00c2bb8694e22d4cd4913a297cb4f27851ef6e3dae7463b756b45ccd39fcb34087778a9c52ddc226b5883f00a9d842c4bf17192d605e4ee6fc0fd7ccf7f54beca6de896d300a179cd2ad86e3b03bc6ebf878d419abe18957a802e698f54069d1c79de151831d01c27427c58c926f5e3c04fc2942b6d1a44454c5703d0993362890769d32b9016c37b680340b89d11c041e95c71f3f9a836d453f217076934d898df52f425c69d3b2dc421170c7c2c7049b0eff275089dd0ecd0fe884686a4008cd80ea44a51bcae0c4d21c8b4df759b6d780481cdf23bf280408777206d5131e6c068569dea875927ba31bb7e77e8159dd7ebceab484081539ddc7e65251bf636ce39eb118da0e3c5e1857b6abdc22daa68a981e418fea6d097a916b4a69b01c549a35eaf55d62c35ca21e9f84f9d2812f17c0e03a926f446259d59af1e0489165982d3489ea549e36bd3fd89ffe5c7ab539388831d0f2a6e0c35763458f5a397797ce1c904a5941cd729125cd2c3e20da2eacba6e46a2774c9d1d5d3ff6afb6b62146468692fbdde5f36cc423962c258a03e91cccf4029327adf22d6042849fe05b23a055085771f4a62e987e378b82d70462935edb6c468bf2e7fd71360a19b781da2fa51e9e22cac6b766a14ff73b2f15ac1219dd8916cea0cdfb74ace541c16f4469084397f1404ced2419fabdf194a80200c9b8dad5abd7bbf1e7c0ae6c9d2c1b1791241cb53e67395329046243cef4c1210e54330bea068ee9de71712a70a382d385869869a93c096a58aa1a02a8d5ed1f30a63aa8b23264e1997730253aa87a103fa990cf26f84c566d6d5133bf00ab94f3aaed0f1ffb20908d605eca1eee6eb06cd1175ac181f905f07526e37767c5acd0e7196dad5eea39108764c5e5cea82e467c576dcc5088047f0debc60b1129582d38e3589aacac941c4b1f4a41a2d957675d0feda1e677278c8da7afda496a0a7dc743d1580195bcdc16602fa3ea95925b59101b1375c64a52c394cc66c03d0b685a847fa2f49a38ef186bd9aaccd4b7194ebfc5a6157b1d1631a1bda60bfe565c4ac64edad5f00e984b78f4751d737bf2a097c6febf1ffe03c5f0759721290f2ae17c52bbf094e94f5436ff5dd3b164b337a4d0fede40e73c9659ee3778e17a8d14effc8dd9c34a630343a5eccbb25600b9088f8efded6be4f8e6a758e66c97c8fbb8a55dad82d193bfb5e910a900a925ae791311853dc441c255fee8dcb438aebb4a60e887178eedf59421dffe05d0e466c194ebf0c8e945e6994aac3cc8b9c00f6622432e8d6642ba5dbe652a9195fd4d3d7db52b2cc408b059d580df593d4609a43a6c12358bfd2081e860f15ae175b35fc9eb0389650fb77099a2e73d7e7417f116466120fb407fe3e9d6797711e2ef2db641c706576d5b6bd6930af02f16ee0b5bb4c1a5187faa5e68e73fe5e22f2abfbfe81c4af512030e7395580eef4b6a0747d5c8e14d02853d70576f02eab5f7c4ef692803ddda8acf2fd974dac14a27cc6d91ec32c114a1c14ea46d159b8984fe7bead76367285e5aa9d7cdbb283a0f96bbfbeabc19ba889f3ca770c4e25df0c1b4f5019d3629700b2b7428b9be93d673cc3b32e4532d447fd813b59b4e6c12784326a06dd20be27751d8bb046aa6fac992fcd1d7a5940d7e715b478cc52ae520b948058d54bd79e37c312f6e1c1f8ece4d99e404749dbcad21bf586ad0cf70cc6cd1664125002dcb13ba05ec23b9d80fd0822b9e55f2588fb89ac96d72e7291faabf72ccc7edec5d95b868180b7891ea1597cf1b29bd6e9f7532e9b1917a5fd52f48f4f8b6358efb7b5ecab2090d45132c65d3b738a734c8928d6ab6ef8a823afee0ba9351ab22bdefb08f1a054a4c136697caf26e094feb9e2735e1b1e5c6b1758810ecd6f458ed48e5cad820c490c4e4fdd60e9b3e005da88207fa798edb2ec5610d5a736d7f8962db1215df0740d66cf3c092c549e26637e4d8ad0e453fbca80000b55c8c97c4bc9faf1fd100784e8d163ec99442dac218c7860994e703f977fef2dbfe627f856f1fa7e9d776e90216911e44c139d224f8cba10354b3a99584b2112d380497e6ec77b088f4c2671ede6726549a87eaafca321df416a619d7c8274f0fa6e66fb6938cb7b7a81d5fa1da8ccd919ab63f7b09273b170c82ac18e6a93b6b019404f811cc6554e72835e184adfe560c7c1616bc684f0c42369a457383662f8b37af62974f18e19497a90391f73142be703b00531cd958afad23619bc702ca88dd95dfd7bdc60498146784f44deeb8c77ba82d52cb03e3ca4647010c41c7b3d0758b0dde9ba1077acb16b33ca8d0605d210e87cbdfd678b475d6ce33e02ae266eb74c0adf59958973473343596b6a4b527b36aa9243a48ed525106d142305529e3197db688970ae006097ede218e34fd433926c9147d67a01803fccf2eec8d8cf3e80016dadd5328718f5eb4899a825bc13981e5023e4e7bd07894791ad40e68679fb6aae2030c09fd0272448db247075459325164655716b491c8f11babe2d2ddf443f63aed3450bdf8af14461afc6a8a666781c86845ed01172506da9417105963fecff0666d01aaf1dcd9c61f62598e5240684730b9e748ff4e4c586f3c8b8ee470e770657de64bd014fefa8efa512858519f70c8be716c3f7890b815e8e48905d9162e64cfaf19f5487780668ed53671246205823e24e351ed934b97330ff501ab85f012f0a428f0bbed75f4a6ca0b4fb55807408afdf16a891b8bbf3b7a97b00d9bf64f12e51229843277a714f26166f431e67bd629e6a6404ed784a150deea59c051698c3735580f06426d696bdc43470ed6f6ba5b8f5d9663111509c47fc19ef328c3576ca62e9c2ebd6d7d49424a05ba9826b2b47c2c25a26d409e698b401e2acb4b934c8a14bfde5b4eeb36e82ffcb7432b9f3dbe371bae16f48ba679bdd182bb99001ba277de6b17ef922afa0ceb1bcd8245ef2c97b9277c4f832c0caae148212b86a43cbeb75479bf152c9790093569becb4a7be4d59966a03a8611af2cc64501ade0e794d7976f5f5c28f1fe7c0803353d82906459b82a3be9b35d62c6e509be252286f7235ae53c11d6e2eee39b590d0d31f054c1ee95b5ced7894374a67d7c40d7a80853dd594424aaad59cd0f8c018ea4307ca13e6e84aaf24ccb315d03d973da528472c6ef8748dcd173408a1eed78b4c13a1d6e9f5c41b07f41adc7a800d952845d60b4aef5737b32f233f1c18d672b86ff70866f02dd431dbec9ee9f2aaf2432e05026f1d26292f7d2137568de5cbf013266a25462cc54d3311cd90192d9f18a834ab238b8125ce07d42eb701b3a9a1d5ae10c46e8ddfaf292710d45e15caaab72aa8be2354888f71393f8f2cd4a72c58b57afa20be7cad45347e51104e2ac25b49f59761f0b90db12d00b7633b832dfe252d006ef43f301184faf5e7f3977f8c2ad5d89b8241a0d0434289df613345f2aef21abc44d68b435f2a1fdc2d4e6e05d2bea6fa3a56ff5ccf27f9174d1047b9f60202761e62080f1c99d06634809204cc628377f5f009758acde35bac8c13c206ac0b007f562378a23c0c6cad17c1bd9fc30a1976af719c0fce36cfbb6fef8fae76d3304e11f5c1b82c51fc6ca6079d2a26e1de7a1dfa6cbee0dfc85bb3e6902465ad8181961b0141e3c813138d699e65f58f7ef235841a18493d518dafc6c80061bda1d8cc4b7856086e53b4010905b2f76359fd4d894bb310fd3177c44b31f3e98bd7331093cc468ad7ce9adf4fc38e0b7e280d986e5ce5ff92a4becd9ceea184b4a1181ab26058803eff06b4c6d29981bbc0a20199716e9f22ab9dbf2760d52ccdb07f8914157c79661a00e841d8a4da47ca7d6ccfba1a1df00b0de7ef61abcb2e65197c9b00f6073a6324cfc445a4ba0417aee695d9bcbd09c44ebff0b3bbcc1caa06552372040b642e07fea4afdb23fc186a1577230a0bff562e0f2bbc4cd674ca8e9fb98ff8b6f340324c9860df67a4d08e9c0b0b7cd173b0bfcc7d77cfb8c4f17b251abc41755a2d75e586ecf5d49dd4604c931eaf111bd7e646b3be468b1ad06cf97e7d4ef44dfbe732e532f84279626f7cf168e22de95ee9f5d03c0cb5b412857d78f0fd1976b6d4ed81196dbed8f0309c7a2b9699e5db51b5c3d4cecd78069e3fab8aeb143de17fc89544b24f7cc28439579eb41317344a699d5572945420e0ffbcb2f34cedf852f5f4b8ef896a056f78bb7901ccca3b4f586c2def5f48281fd1a5075360fa1edbe2bfc0742e0fd5936ebd235de653b59202bb19553194c3bd13410fc44c3779d23dc8bbcc2bb68ab5876798b60f1c7cb240cb4405fc30e243616ed6d23ee8ac4fb65232b8b4e83697818dc9cee6719a660a7b69cc53111cfd7ecee14470db91e8a4852de85b071c8f6981a11caa34887187ab719486fc7bee7c267b15c1a22e3349d1049ecf28d3cf35fc115a70acf0ff5990aa23d4131a79a0b39fec15dff1705b8c26ecf76263cd84a8b9c2e08f93783dd26310d66974ba93f8ebe0811b3592a58e07e92934071cf90d9ea7c583dc3b294bf0ab7234226d2899e8878d139367668213f6c8d999948b79315a222439a56d12cf94b260382568347a18be26779de4f52d9eadb5f20769661e824e3a084a73f641870aeac0e7a00932470409c29fdd131646ac05eb2d06d71e37b269e12c28e0459df0fd04206ed79678a82d37f917ffa48a12c96aa90083c6f9313828626693515e57f5d0ec93876f7ef8d44d9fcb6797915f9783a37f2f8214dc6383437fd5dc7e533ff7a13d4b5dce917f17bc434d644a46b4b9e07bbb4f6fe79591dbe15f430d4cf49d839bc00cc79f8d30a1334687bfc0d5c83874c3ba2ee83d78667c2e505bb241cfb1418a4c5cf4c97b1371f457c20d41003034e48c6db1de8e441905f7f393fc8e88264f436d369fc6223d060b9e992023b0a99af9f7300e7c04252f10efec5dfffe4b03814a09e5f00ed60663983c6a097eebd6ff0ddcd432cc17953c951a8bd519feb108903c701e44e8caaa0eca8175c4798e052314cc2eafafe4e068b1c27699b3ea9cd00ae0113259ed954789872cf71f36fd7fb6f97bd8ed989219aee9d2bc77b4c674e402f4ff4f342614fce34bc093f9ecabc362d2000093a3e7f1e3edc9299279bd68e225c610de4dbee64e29c73532dc569519756382d016ed0ce4088743f502237a1400eb72c95ada2ae33e55746b3b4ea9e02e504af72a137837e4e6362603e451898256287f9c910dcec54204f035f9c76a96692780657f1ae8a045ef1382686a0bf8977061516b0f293267cbb1effd0c21642f83d7a134f9342b5e691bd62b43420372997a9764d137f39dff39ffe4aebc1e7ad55e34c386ee955806c9b1b3d305511550bbec18610f50bff8fd17ac28f4d1f0c21f397a5f82bd834096d84c6318cd9f789f09851555a77597be3386612f366d80430952435a570113ccd92a4cd0ebbb38f7684731ba4a5ab29f0e856f2e48fb3f12452578ebe82a7212be66110603b4d7fec0008f28ba478dbc31b27f6b191a57cc990724505248b2b71523dd197de31c2569401943d5087f2971b6126f3059c87a4adbf304f92d63cd1ceaad4e57bec78f668a10e790c645634ec0198cedce6796bab1dca4849a6d647a2cfbe6611b0bbd35e003863e8dee82813f80619a2b80820417d8d469d9c54a88707d6b4e1d46a1fff88757e56870a116da940201aab8feea564a773e25d39442b5f95dec8528fce3e7bd28597382f13416f5e681439f94e3c6df3b730128b7fd41b0fad01484b11886a6624548a820ef815d0e51144372a2cc44aefc5e73fe445311f6407775a8714bf0bc3a65d9c28595c367ec3904b0672ab224ce18fba86decc6d66dbc722543c3566e6035545adf1780dda7444e07ae7f97affb3be56cdcfca4167b9c15474005a39d704584b725a9327d12e6d70704f951c80acae05edca3f231430131cf7c0140f8f151166316149f7fbbb7b00ec7e00280bf39bfaace1f04d60a710e0fcc1192e236c702b8ab82baa33c1c3666951c9f3376747674376d9f3db982e949ddc75e0277d28149eb53c130bcbfb230636163bb9cf77485dfea84fc1218698187b080ac655309078ce77658a88c0cbfec8219ca5367342a30d3f87a121572af0c9050f7de1432bb50046beb76a13fd401bb966e19d8a8d4d94365edbcb8364d2d9750acb2660c115040c80b687d8ea1300bdba83508cf2987e56b8456d10d07148b25435f34ac3fa309d7c87ef567419f7a5f650174ed72eea1fecb72aa884afe8ec596b44088bef4ec1efb397bacd290ff975a47f2a5bd2f643e559a819d9b8cca7c07df966c0bc13b5496da293b8cb6cd79f63357c83e4cf2917e6dcd6a3fa063be063fc73cdb2f7a9c9eb0c574c4bf57145d429267891ec6b3f47cf750b0c715387492101631e2842e815c0dd4eb1fd8d5070e20cd0b8ac58f83ba02fe7f2c96f125f6e290d19cd1a92ba95e11c1dc303c210d499ede268721bea0c64af638762d2255cc4d7278c3b32e823842243e043000a20396bd5c3f1f38b8a14fedec7d4c272e3f2025a2da88c2a07c9e311eacc751362dd97988c3f41a136669fceedc190a1db97c7d98cd568b9f31995bfdbb67f147f7a0300c87a094e1a2532048ef7f0b140d92b218f290ce20cc043c20029ac308e891b0889f63bd1aa3d583b869c464976a4e744a2838435e16e8e7499e448ad0689d79a17b8233d0443778142a799d28126590773b2c245f1b6ed688db7418a5679758d85fca9db16eb08a57262dd36835389a5e0fc84fbd5928cad1c094fe8e65e46e939af68e8e28f7b0eb3ef86e768624090e69811cff2ddb082a75274965cc38ad12af58c8ae9afa2a4f6bf7335f94d2a832734c16d1fd7b34552a2ce6094345f9876a489382d6bb79ed11c202ed0550bac016f5996dba58a928119b3fa73c2b8d4ccc899ef760be9651fa02d79de7d04d4976037366fcfdedde86d1d9e2982718ba01d13e918787de8dfbc39f04a0ae8bc070d4a11d0c1b6092c676ebadd0e65bd6c9888b712c08100933304eb55099e241af5dc7e454aaeb6e2657a4c2b029dec6cdfbcf54d950ad0ca9ce57902c99820158732058a1fbf5322dae7b9ec808d6a0bcc64f1a5cf043528e39508d75d59c3fda9eedd9959992e92cadd490cad6181efeeb9fb8fe695b676c84223fc1b043e823251ad74f38b06734f0295103158ca0507dd4985780c8e275ef6575d1f05f0f96f40f6d74dfde3f116c9860ebb2f43158f088fc2c960adfd8d4d6e35c9c41665a223155fac733694e211ca837ba3f4732f93ba8c3e45e556927e6f9c3a6c0f1a7e8dcb84b72d7b890817bb5da6f73b3648c283ede1c68c207aaf3843dcb95cdaadb6a6b9fb4aee34f2ab381af69509c46a6cadc5e17d4c30aa9c9185d7e5e25d4ac8ca21698408509dcbee8027baa617dd6b2b260e4eef6b5a8f01619c960bff67f6ec93d374cb9c1a77f4ec7b2ed4091be1931c61b75ae515a4dcab5ee5c316886a862272711e3d10e4d67f371854f75345868272b610d23a61431d947286c326fe89cf63f2bd6a38fb6be024d430bce84cf2ea0035dd0f7e9d0e54798ae34a2ec5260a258b7f79e02606590fe35279f099687b6d0a91ba68b6e10f8c5939017f68613d1c73ecc36971e34149e637d9b63edf5a3f8cec30d416dd3c10318ea770508e05129e7460c9f3f1a527acdc9039109462a1ae1bc9710dab478ddbbe7b479ff699b164bc430a9443f9834c2caaabc8f6e8dfb4655f43f3e06c4d0173cbc3fb31970abdc6c2c913cb89395586bdf2f9c4d1ee71f2db21a2ee5be09c89b6932bff9d1003fe3db86e3c0cab062ea02ad0c189e118c7c6383b1693997feff1dc7da5291e515d1c1c892a483ea0b43520a4b337759d5c03863d264f0ee611c8b70529986d683c244f2ebc6fdbbcb002d95d913786a471d75fecefc69b1c5f48b0a4db9d9f2980ad3f5f92206fbf54d709a709e89804fa72ec05b4afe033879348e51445379d311fedf0b9646b4fb6689dedcded4fd64d49a7c9ffb43fc56e7674a51a2a1e8cbda8f07ac24b44e5d8f732364b53f714faade14eb4a35fc705121d9184da20a85e2df09554ddf466cd359bbeba56fdced4513d2b3285ac7274229e6ca41e6d4474e62b6ae13777d4786c5e70b44b73e6366f3269e3ccd4355094492fbd7a189ec757f38d45a74f04d4933723adb090617dae8cce9d074bbaca7ae2ed12659bb401cf2d3479600170b66395af0a7c2e4b340eeb729731c8f80c2622951d8552b2d3cdd3aaa75896107ea218d1b7af4a238d6442a6e7f4c65d300d357468cffbe4936c7329485f1b4b26e919d9617588df02625927ce9704fe877b3d97e8783c4651f9afef113c21ed9fd52231ca60ad7b4d574caa3a4f05db2b75cdf1d38b1d949fd5bbb3e5123d397c2c71add5f8231a07630baf4eff9137f8126e59435f8af99ddbe26006b989033dbb7aaf435378b4a415d8a7d33032fc72aa1d502952d246fa6002263bf822dedbe462183762df5ba40ccb6fdf27b82b4972cb9e67f12bbaf2440bd4b4f5a8555aa2850bfb8dad09138051112195b66756f8dcbf0d202b589854838cd013c13d6d69f080ad58ddffcb3fa9dc8f50028568aaa9190af050d4d3b605c011af950d95dbe7a7b9148db9132866ce76e9393031e7fe08d8987a09efffbd7ced07534c355861194c2a7f6b70a7f045169d38a217899cbbf4606c02a502ee0c4184ab0d7d8c87da2a039914e289020c5672a464dca0b8da245e4e92fa726ce58d34537e32e4392f9b77651edcdf2da941ce632641227a05d5a72d7d61f522e454328380b34e883c3f82928da281540c49d221a026997f1b1ff67a30e8428ede2afe7858bd233d7b151471c2ec612f817f3acd88d29958573eaf870b78a14080607df468cd1351fa031d78c9ef9cc8f6523f199a4f8309397c140e38640d5f795798bcff2cf873719940c33b32f300967533fda419578a40e096a912cb1723fddec31932fa068523ee398eafcb290ba8f33334b687dd49782c5b5a238cab5c9031f158c9bf671f91cea4bcb8f19bf0ccaf45bd4aa60aa7cff09b05a6d4a0ea276e794a3f6e650bfb99bd5379f4230a82bfa85b94739b7645bfe7f1a284454297a69ac4cac782c7b47eacfec10c6489f861ff16ceaf2f82e2ca304c97eec6f04edd05207c91860647fe1b89c29d61f23b8b23b8a0b3b805847b42a4adb3b70f6b8ccebb691b2d2f62e32d694acc579ba83d2b5197fef13076c0786e86bec2d21190c575ecf18cdda0135d32998da030b22b29b4a36fbca9de2b8482909efaff218eea34ecc9645d1ae77d1ebf8f9531fa5bacf7b259a107f7209d9df1c51a6f313528613cfa7833c5efd98d8d9d5471b6c9def751e67658179fc7ddfe6b7e90dbec00690814df95a95a577f17606599673b68d261781b9d9073b049451687e35fd12a3d506c96eee354b71cc29f58a2dfc9e76bdc00824857dc3b47220aab7e90145bd274a8145d771e3a2de5669b5f98d1ebe9a877d5c47de51424ec286a8a101eaaa2d49f7d33f0b462a0fba7e2f15948cd3880ea4ded7e0b6c90aaf8f5c6e2040b2a78241679d63f27076449c65031b1b886e1d36d9458ec9a1f5fdb6f02483c4a6ace842167f0a89619b1b85cc9b6287cc716acc2fc751192c9ddb63ea5ed8de86879d8c1ee540adf308a67ea7ba443673249e6ef606a1ccd6a358f23286cbbf0ad2a6aa3304edcabca1bd402d9bcb9a6c2a5cbbf6b7be00843a8d56299156197c68fd033020c0d060918ffca2b01170bb791e631befb321e77346e8e1914f40dc44e88e571266c3b334771670c38ed1612efef026d757c338facad3a5ed8f572bd1221893af013d632a83202edc33f861c7170f682a454eef75fd22e32d8b63249a84f155099ba571421553f91c9233edf00883e7941b36d7e53b96ff29c427d052a774f592115408cbff4688fa6ceb8ab318dbac29b7f9cda09d2d90f224faf133d7ba6321ab6e06f89cf15d4b78c8f93c57669430489db22dc92dd908349ad985ded39673a12a439ddd68f1e705ab9ee414159d307321952e2ef578ffcd9383d3be777bbbf94b886625cca82be1972d693c290c079582cca6b966eb835793aaf2524f4a6a1ed83998abc49e6c49d83acc49ded4a4e44afcf07efcdcde3f0d120892b1bd29303ded8a0a8f887de06d792be48b42645a7a6091e28a524b6e1c1c37a9e2c9ed833d64d6b728975f628caef20569cfb63f7f70317cb422795a8735355fa779be3c9b0275bfbab122ef6c1ef247577e840ae11af149e42d3303d628d65622f11ccd8dd374d291397d327d80192d92dfd1c372fdf3ad4d33adfe333b1a642447b8fe0e574da02ff90a89f4f57c2fdaebdce9551cd87cebc01cbcc2b058fdda90624e9ab7738d5c25a783bb069b7f0a1a27c105d943cef67e271fae552ec160a175bfdefffa90589acfe1408723ea214992766cbb4d541732b73098165d00d71c2932fb06f7f70680b1e68d5f124df2582055c099293f9f23eaeb3728f8d57b4533d92e14ba777b9546699892a7defa9b1ababe5f59208d50fb1284526c3ed62c929cefdbfce227b36eb166ba3f2081670099c6e8b75519aa9769564027a21db56b0400d1812f382b56a0dd9a72e07937b32cf3a629a35e8a97575f5005125bce19fcb28483d4c24a9c32b99c64c1cf482f90e616fe6814bc1fe866583b900498401c1e9669c95f63dbceee4535f6b72aa5a22520a1dfce337eb7c0aee43fe7a0dab94e12c5c746370182f61e503bf7a0cb6f55e70c81296639bc7aa2bfce67181a13dfe777ac1b09700ec07d607ce8e35da9ff440b271ce34450f3e27d69ca8954b71b3cb461d4cd9b78b51c71cc037a5de0d7fffeb7cd9ac92398c532ae6d4347422dccd724238b1a20c981a9de44b50ce5f729467f44e5f90149325427ca1fc1cee8ac6b27a9e709de0c37760cd5520224032cc1f4218f8eb00c495f4ae81c020606b27eed6b0b9a26ccf48a03fbf700bc06dd7d03426b582353ca0522fb607a639e9ffff0c3c03c266933cecac7e928e820dc9402901e63f7613c963d33a8b61230e87071d821b668c9bc72e266a4b6d40011a18e4fc714b58be93f264bdbca875e9974820399feb6a78a1c1fafe52e1ee10274a9eff51886d4a0869cded3b5400612e9ce3d38487342043927561d4261355ae0d067a1363f1eb35bd30705f72fc7ced702f8c907ddb756d566a94f3d60c215665366457f0d0edab6dc979b3c9919c9c5bdf00e0a6edfaa3d0846c421261f92538e7d1555b2896008e97a9762b9a83a5372b9c6448ea05ab248c8f4b7b50a708fa57f655a48ffa4ce334fe19968cdd94d7c3605e390c94103c0ce40901429a4ee4b63e36afd010ad1060e2c527baa49d45a52ee759c2f2a23a17326e79cc8e2dbe0680e8a1e081c3d3102ad3e046d81694c00a6a59500af402115ade0b69e23c35c513f29236e6345127825136880ef83ddf7627cd5108ddcab672b26b1812f7c5d324ed87d91b297108458474b3bc62176271b1961986f8bfd453b7e284a13f34655c06c4bf57cf4bb9de60f1058016cebd46719a270c23bc5b8fac2b6b0fd793f1948066596dd8a54beb120fd14ffaf9d6f820c62e01040ac99ec71c548be8e0723730596088f77cad02770e144399bd9fa2516765a87f3912173122e9d77d286c6dff8e473e67a42db5a2b18d7fa0f698e8e5d119d04104870985886d2408417d606c92e36d657147945aae6ffb61fe18399456c53639425b54559726afdc128556076426ddb177874399f01df653fa79596b89177a19eeca89a57bbcb1b0709497f7a808e612569b750eada36f8fbc917f48d8b398d9390a416e02de6f228033b17529facccf92de699c43c8715534150160719bac35b373181d23b13f32b2bbd84f9f738c3688f6f6d9f1edb0856ec974ba797d892a78a66c115ada0ac34bc8e466d483fa18614f6f905524e62726a4fbb29367577d7b86cc20d324161d7eff933011722e4b258cb43fbf1847e4e43d4a77b5901734d9507e126415d56e0fb69d36dbcff527d63b9f9a54ceab8ab337f58c4f383e1a3b36fe2557f22d44932f57970394f90a9b6578060348458b5a76754bc3d3313f4d2d3b87050ada14337d8e840519edd4fdbda21e428e437e3255535d29048d7642ef7adf41427db7063d9ac2b418c26b3797b932a6ba6c9d8432ea43680e76d668503ac5923aa7e8fcbb832b3cff65398cde5bc342f371467bf73fd19acf3b3ace6613463d8b2fc7533c9f2d6c2ffea9b8b89109dd7fc27ba27d7eadc667adc1f430af611feb4558a162dc495912f2ad6d8f3b6dee62b861f8f3b6d4e2144f4e97ae0c8c5a461e056c0b97954a0c533bdda1410f77203dc48da5b9162ef8a3097e9df32fce0f5818530bb73bf8b05bcb566a4b41a09c7092b6c3f695d5bf41dc624c3d2762571f42c79b7bf819d1d0dbf36a9e092f3c6f5cca158f4aeb6e48f6e535ba45216f8d175376659e453b086b543364a3c6b00fd874b0f580acbd254c9255dfcd2329fe8f73e050921d4a3a826bb1fffdfa1a54619616c65718cc4013f26812f849f8e587002af74ecf998ca75b1f4a3a336c5575b465bc42d6204847b38e6072c67d343e2560bde0e17ef568632088305c32a336fa6f0f896c2e6128dcf5a6dc784d76d8e836ec695418b1d6d9dea7ea6a376deac8e5c6c8c6d865b80f8fb94b18f3b7df466b2ba00c1782073b3de126af9e4f7bd8f0096e0cb5b13ee2f1dfc72d82a3bdd71f614d95946193446c21d7f56945f8e7e2cc3fc7fdbdc53de814cdde3a714209f6e3ce2c9392182278c3a47b901812fed9b6d35975f09dd651d1ce870d0b24e7e54495230147b7a99ef767af11a2a0397c9215c6b08ed9af51cfae2607327a7316d4a78a592594a880e3b0c4ad4f1b322d1fc4c384bcd0aff71e4595f08b418f386a1e323c07593f80f540162eb023e85ea2f5268fc99d44d819e193aa3ed87226eea9f7963ab34ede1d8e58673af845e02a8f9616d79f18443fc2a3102683b6c98407653a68d4f368a0459c5f1e01304b12cd92ba0cf9d611c79db16cc0ebea8957909ef303f014cdf9c42042b34925f4eac0cbad2713002cc51a90c5a49bc1c563aca185d3c0fddce39df60a00fd6ffdb2cbc7742d17ce540c564292b585c518d459d31bab020c5edc167214f0efe56fd725da62707529d29a62169c497ca36ffaa9de0ff7f66b2fb2ab6df0ff16397b4c5a694fe15fadf33fc28f1934b8022d2041e31c0ce2e4db848a5c37920cd83fc2cb853ddf4ebca455ee214cec55ede007dcf3598090b5e852152d37a78e5ec4c729d0ef527ba47cddd4ba2a14d3c8542ac6e49500cb8073ab64fbab650096f87ffeafa2e967d4fe0c541de34497deed6e626aee2ee549f157c093ecbde705e86624da66af531ffd58c30b3c576974a85da587bc18348120db29c534746c147e874b27d61ec9114e5c69422255f5742d8f984c30bb91208072d183f709e0b67d6c78ba1cf91156498a0342eae04aa8c46dd9e435dfd7d7dddf9fdb625132b28e09807790debc28bc7941ea10265d7c25301af67d4e6a49cda0adb27ff17b6b82d391c950ad1edd47b949e48456875d6855a14ec304ad561a62cb5b97c473483241aab9a820cb715c8a32ddc24e039f9acfc7e3a7898b7c886df9f8c8c5f89f99f81e552d09213541948a904e3a71f3b6787920c310ef3f7188f1adf8358e3a0dfe1664d2babe79dd9cd1a252f956750464ba83bb564baa133a9aa282bbda62b2550b706ffb065b3399fc4b71979a3f1e1c1f8f857315d6a6b896d55b83268697a9501a6fd19e91bef3f2d10f5b089625e451052203468b81ccb5be8ad5e16410b186c2a20ff45729bb12d3a6d3d19de0a08f787517d9ebe8d080c121221cd33748257d7460b45e0988ac60e1d409a6c7242929135f5230589aaf6f25470d09b960fa59f1df54d1964c83e9947b29004338063415ce34745372b39b08b390834237a3fa6509896a8fed52f4db308c689bdd1f115cdf8a9217892d3ba812a1744483157987dad098b77743aadd1ef2fe5efe0262cf355f12b9357519f16ce3c3dca8179e6293e6b2ab0179310cb2ffce3ce650107afe4623841d145a99e91c9f124fc4678b67503cf3cd88d7e390bde898ad3fbc9bf5d2a9afe34acb9703b69ac31e0de046cad439880df70b75eb16095bfc187b095a1f5cb552890843934062e4d83543a9bcfe9544b7c52a43235bd2b95bbae5776b48eee008f76c42efebf8f6645376e2a99d95e744e1164293c09404a415778849ee4ee891f0d5ba5e0e8ad8f2d3cd32eb435ca5cab1a4fcaedda6adf1b03ab42ab8f39e7f8235613aa3c948cdf5234202c88072ca8cd42dc72e4e17f324cb6debcd351684f0f45c65c7e4cc044d3fd3f5c7b17d5840dd56819437e54e9fa014e75bbebcc73027243a3e02d0ac41890d021f6a89a28d061c77d37fd45862af93e514034fb66d8bbba2ec5223ff8e14d7fa73b517b0a7537281124c5c494356c5a9acd2aa45855f04d75db23f5cc4f3f853ab049a3db8fde7ea5df298b22c9df6555cf1d7498b0202273ed7eac8c5df07c0711217e95bbe912326b243fb9ac3236d62c5d7c14667414ae113223c4a2e4bbcb5d7f88222ea6698c0fcb83e1f1fc8b07f130e29b156cce8bda66cca5e181c2d2a90f439125b16ffe34d4f0fe0495b7c77cb54c179825275ec9af77de1507763c6ce3e85222b9ebe2b94803e17a1d58895e40c7a826772fbac381d7506ebba8146a27d24c03e301c9929a8fc9213d633244abe9063004c38df053ab3d590ca3aa7c92a494b9f2d7b105c57583240437ff7d71848e547c72e8a56f1e9aa091454aef8570199a5424ed98582636de481ffe266681a878b386c596d4363b4102e28612c074873f4ed4e20b6080bcd9a9ad73ee9cd9cfd8d4c5d1a2c171f777b0ba58c0f89b31543fc2f74e957fb6fe9e8b36997cfe0ad7966663e5419294ab0fce53e6f9396aff7c2464bd22fc385a02d6d442c128230a3d9ed5b571326da6cf99649bf018de69123967b31c5c3d4881e9fb3314db6d71ba929acb71b1aa5dcecc3c5ae71d953e5a13dc3bec7633762cd06205dcca52414b74d8ff8417380fffd938dcc2dad0324178e40a10c691a3280eef9fb0f0b4c0e8eaa061f987612600eb6c03b1870f5b8cbecf877df52ffe1d4452e705e98bce02d6837c2bb47561ae9f0417887629ff2013d53f23b0dfd2d4a46b4bc58ea918886cbecab40e51d32dc5e382b477f6a7d3bbf9cf8715b3ce387ad0422e4de1ecc8a6014099f9e8a39b659900f2f5831aec3348464f6e67a6e00a89ccafaff6aad04f6402a87ba9447620176e601c4c69c489da04962f589bac57f7d873552afac951327d4bc3e873e9c94e07b86986d0e8d13e6cde266ce5eab04ab9c203fddda8c752220dc0c8362778804bb0c585f307d2534929bc4c41d106cb19dad8314bc7c25b0eca9d1fc3fb168dcd2d0ad49223083c9aabdd477b3e8171a5a54032dfabaf9f4ddd19638e20a10a96aeed0ca50a007ca2755600af4e54df9d9b42032e733a132ee3321d45922b56de6d569f89f24edbe2d0f95313ad068626accc4e2cf3be4558e23a84093869d6fbb7562dc65f476139e7ab3b7257277eeb90f8e2e1e486e6c563adc3e7dff05bbd4891df1344078974f8588faa9ac8d3b05fcb8bf4bba472946a0a6677f44f4a007c3eda976a9e8ffd637876a6c8e456a53f1336da426226195eb83a6409b333098b849e8d5222563dff1350e73880f9fc267f8c9d8c0bc8e14fadfbabd18b3c3cdd52587798393c012a908fe221afa85e4d1339460d8f1501d6662b9af7ed97497b511d20e571e7592ecafea8e8e8ac72abe0246ad8e71e71279ad8842ef27a2409e320ba4b465c6ffae5a9115a51998630981882c31c9bb72bdab631bf1b171350ff9e55226915d1419674b503fe82c4634baba1ae119d574922a5baf7d8647d7927b09bbeb65c3cae68480516ec93d0f2e956e2573cc633c632ad3e913234f6146b5944c21a4eb16115a2f3379d0872d262febddb0a3389863ff0323096ff9dcc982976296b4a82ec65068f5b6ea4bfe3639abfb65c94953f4177566928a78268a1c962d5c603262f253d2ac55aed099fbd4dd6ee3d5311511008b0940f2420197f90a6e57a2a684dbe5260169f0ddaf621ce342e25caed26903f791e514943c777adffe5ea283e4902ab1fbd9e5417450f9d550f03d21ce09a240ec277fece7ac77f62fb1902dd43136d62512ac4c9be131fdc1d0d11228aee544f22b2d8d97dbd3a4f0c9676dc24c2d8af77b175366619659627a11aaff2d88b1411f68feb8c27d32cae391efe2d88bd795328f2fc0440e1dc39dfe382396c58a6295d28bf77de25baaad0ec2668c3559e17a8ebe69bcd92e2ddde8ad50daa6a83df3ebc3b0bdacd07dfffcdc4491c8c4d4df1d6136b0bda76320a61f063e8c36e36260f878e9d1b79a975b9492da095c330c377d5835f85836ae7749e2d6c034a109f0c2af5e5a28fb2a6b266c9c287b87bb8ffabdd1cb42e4f5200a95caeaf6697a957c243d06af47525b336da8040e715cb9c77c1e281c4e32a8e25f2a3ea28a9ed8c6ca73eccdb11dd33eb9b09919443edc544f9b76d1f8505478268e72b5b18ee8017fff75779e59752f94b32f2ff763961621c5da00e0c6be160fd19d7f45162cd6da8da39520ceb7d48a11f7fa6156af06abc1447b183d3b2bb39140a20adb75af19f65f844303445b2b1045b9cb8f632220328ec8423f6ac8df5703971a63ee3d0d3abe9525549fda4dcbbbbdc95a03ebe9fe78a3cd558724e681dd292a7313f02101668ce25a07c8ecd646eb6d3e82e6742ed07a0836e6187c5ba1f71f7fe18273bf1d214eff46818d13fc3dc5515d1b0d191bf005f21da42c0033f6fa26596df1fa55827eb2c7c538779e41a610f099ca8b4b5a4e52946858eb01ee08fa17709360d5cf26a1493903cdb698cf8be8f08dac8f4562c3762cc657b7f86cfaa80e9e9b299dc1e67ce425adf478560e0db50cb1f8ee0c81b158c3d06d06f69a860ef7204d748b0b2db4a62fd02b6592ee02eebaf7c886e9207cd496305b453185fc896006514d57cff7901b338e499f74f3f66acafa67220ccaf5b9f30f451682a7bc0ee125f7c20be2bd944693d36a682a47fdd0de6a79f3976b0bb08ceeeec0a1f37746530a93470e2d8a60bf0358464ba5007084fe2ae0de2249d01461d755c226102636d4e44ff5ec6672ac092da681dbfa8f2ca978aebca124a738486a58a685b0d39978b8017993c74888929a7bf7238c05d03a393ef58707386292de602a2c353d3dcc41345e4636629567ee4960a5e32c98cf4c116e9fb7364c77b5fdf2e66c5d56ff8939cb532f21a75c0b2770ff96cb63a29ea2b17c3cc487ceb265e6078dde0e18ac00cd5af0a3a98e001361e72edc45af9eab6cafa819f6b519a7b14a9b32013d3644d503d49a18f2d9ba6fd4fa3e03265da1f282a803508867fa41c97f04c0cf793635493c0c863184365cbcfd6b99acb1edb84ef8cba12307ac4a00b1c84e57d4cca64f4d1318dcddb2eddf052ab47118ccd95624f0c97fb645c3e3ab60a63a3b22c4cefa4c09e44f426aa741ff390f2f3d933c34cffb94983d6703aa38334b946890092ad5256d873f228fe387bc3cfb36c8fe234a40922d7634bf4703551fa587ee56ea6b4feb2ac7e523a71f0a5265137e8a4c2df263ab47f08b0c3be33ca3d7253a0699fb90a4ce880f49539f9990415fdb1d1b418a70a09f4707dea413af5c620b6f6ab40d5c476535445f3073fb693aaccc9ab13f8b070e9ef06bd5c7c387343045b8979cf98ed8de65992346918880b7d5df9aef8ef14c7d850ba6cce5c811e47eafd6628c26c3f70a03124418fbc88778d9a7fc31e52b9a0be0bd4e9a2dd0906fc2371eae4b0b5b186a4cfba4254bd88b1b58d76d62c3043204b16ef1dd2eb7b1049323fe97182f006f54b9fd1968854d3c80641b5cf0ccbf53904bad894967356657ad8d3c5952667e60bd17a2210d1055c7b34559cfb6c22959dd4fbe27497537067a17c8bed7354ddb3a18299d9fe76d2806ad0045ff2f7d839f19ae303514dfb4ba1ca83c9339aee32b8341a4bfb8be742434e678302952750b4327b05c65af8d5141be4db689a8ccf381f226734adace0532ef5306574a0be3fa595702a1584c6081746d7c608e9bd16b89b4033e0a2ec6e18707630c3b184bf392b8c2c78ac4261ab49ee727748b22fa67054f0e5ddeec6a3185532314deb5873fcd6c76f85b314004e58cd2cf8c74d22a73a7782900033ca50424c60e979dc567716a2c128ad7fcdeca70d138bf68508f7b44f114057ab1e78184960ff00ee249e5b8c67f5e0acdfbdcf33839fb65b9b2414305fd37e242293abde4638903e6e94c713194fa72dbe82bcfe6e04b1b44a00937cc7d3071b04232f3e885768c9b372ee191b513e1efcccc20bb536ed29da6bf49cb3161d8eaedb17c3060c1cc05e4f9326bfa72096e95598836dc5692d79870eceb318c6baf9ac145b19ce5c3ed1e7ba67f0a7694fd246e530f24a911e5f1cf1768ce4b9712e3b24435e854e84d3c695970a88f7cb5502dba8f14437917cdb11ccef387a7723196fe0fb7f391a6b0dc9f5d27c4bce49abb2bcf88b2f63dc991047e3101de8c1ec61b5004993485eacfec09762e4fd2a3c1aa186cb91e56370d8242e1ccd8ae6b5b0cdfb8936ac84ea24a41577b7b0e3aeb391017cd1ff6686771e781d0483e42b5bc40e423cf6a049a6387d56c752fcd6c5869be9bc4da8d05e9eff049f8108ff859e5b4fca8de4b5e1a4efe574b13c7798e408e80464353f1d9cdd147214fe58a263f2ac86444d57cb23677a52401f9c9096a9d0ff0046359403d1b2c14da51975e0ecfa2df05e8e2881f8e55d043db1c4a473c8f8f80f310e4a475312e71ed8d2c95669c81a035655c00fb78a317d7b1ddf5427bb9e3d40ba99ecfe01f0fc9a0ada55b3b28a8e03d26d7a8b3a70534ce2de9ea00db5fcbe66f09509b8339054706bcecf6e04c9887822781c019068b3b5b4170821c41915d86469714c43c82bd869684ba73eda7c2a8ce63ed3704d3c72a712f17140f8fd3a78413d3528e9c81dffd6a69bd808152f42d2b9b609d33c20105340a482133ce8d3a06b42ecfd5d665affd56dadf9801fdeddac08035cc81f29c355a8151d5781033bb66ba282044689bedca203b5d793fdcdd66d2ba86ded86bd038eeb53e20d7cc5bede2fdbe1593e99083eb43bdcb55a58529ebb2deb686cd5e8d148d40b7984fbb20962a55d96475363034d5e9ebbf6dd5bd1a6b3e6900c6397194c54054ec78732b894bc6e2b2b48bf6fd24fa313c8bfe661966355cc97b4eb5bacb45b0a9a2aa0f4fc805c93d3a92ee8fc8abd9afe0dc7f0661547a40ffe65ae386d6abd924e71dbf99cbe7554656a47f15b8849f9e2ef4b689ecaf0cf13c18acb0f6c536d5657fab066cdd8f17949a8ba91fb5376a39c753c0486c99d9ecd8f6efa2a53dc683afe7d0bc0d89edbdc345235c55e52c0d8e48fe3826a343a93abc617396cda7edfe9e8c071ebcef60088e559f307e0e9bf3813cf1d7d3eb9c10ae797605596b710ffb1b867b656abbc6dc7f2676f95b6533fc724e7c6f97c3e1a964e905a60d81591a89cbd57713734e5d552f3ab85ca3a0a18b8d9510642167fb562ecb1ea8afa856b8c89da049be64d8dc4bf65d58521f796261a774a228116f704431b22bb49cdbacd18b5c7fffc16e73a906394fd3ab242d1ebd588ece82342010374467c7288d59251a1c44c691237c5e283b2763d0afe361e36575c7109132f67eae3b85143e38ff09789d65f63686f902824ff130ae843aaa2d1f234662f2a6cb501fd37c362127db75a2d38350f45b9c6f1c9702aa4e4ca836880a6270087a8a3153b0500a1d4958dc8a52c1796e61e2525e388ee248e3f41842b0d890026748ae6b3ed2bf2a1c0a98a477ade8a8f423a0f4a3b25ab52caeaa0a9dcd98ac7938bc0ec31a463484c12fe180389a49f995b95fe16037460e753c6c629c1fa59cb0b809de953bab681049ed8dc6bcf6e1ce4793361d6e3032efe578655511d8b7a9c920b1820488eecd3c043eb0ead7e04c228bd784c375e845e2f2e33911c96b42ce39417431a52194bbf167c2341460efc7641fc119006fa4fc738e3facd7253cdfc4ead335b1de4582f31dbdfc16d77a67ffa8170f680bbd0c6612935be7e47f5f845d5a6e30fc821e6df3d69fa5201578649b5bec80224583deb6115715c9959c16369a77494632c39f8635f10f2dcee17a0476e303237606afbfce3766742affe2f576d455de012dd8a47784054b13b6c62ae69df4a98c1e3da2906a955592cb4ccca5e470f4dd96c4fb2dc8d948397837553d1b99c049f2f2375172703143724d5e342b7f9e7abee42e5bf0405ff9f546a6a1df1e7572135b02c0fa67a25670d87b01e567c5cff84554fab0cf2dedafea4d4b724758505b3945bfba40d10ac0aa7c41ef7abe9756fd4f76c8c71017d21984082cb69b30d73233e6260e5d41d967d0fafd3c2cd2d81142305afbbe5bbce973bb55b9a5e3c4d6a9fff3e043024226288a22142f41b5c83efd18d16d001b837dd996a8f03f9f1a028869d3a0f5aecd811ea28ec3edaa85d5a541b3b0348f71fbebb332f59c3a9b4a34845d3feab3e8473c1c3e69a066b17e656573e885e70f060f9d34b057f134d96ae81a575204ccaa3a0161d26af757e56d046d5e68e8128678ccf30d52e0c481c4a46c3ec4433552c458d4691e53712563f87016b5a47445eaf4d525723325e892c4a0857a66c3885ad807f2d2ea0584dfaec3f9296642943f00f0ba2082b65355b289b7d1a1ea0ccf49ee5c634fa1daed33b83ce2250f85e45d06e7c93b0823446aafefcdec45c5cb412442eee5c4eb8e0cb2c7e7940cbb5ca1d82e5e24727eca7c8491921068b04a26c70558ac4c1a36e197fb9eb40c42bc70d27fd5e0b03005e148afec488e8a1c85b6d7c80df8894ff8006154ffedee2dc37bc0a693d79c4997841ac874d6cd63ba393027c78883b580a7b0b92034556e6f42ae4e8928a19b1b44abb9d6dae317e74354a7db291339ace92a613ecf74500a77bccff4a6d637a880067465d5998537d0b83999103c1f9a636344d053e1381597892ebc73050b485e0f54acac2acc7910d215ad89c89f11ec7c3e09254aa07c3e49f45447d639b9293ad73b8c7c5f0468f83648e4b1670c7e5190cf53f995665a32aeb09ac1991f402b1eaaea8186ec6c92db53ed571711d9285d4534153a51779a6d3951fd34874a6771231f08dc8c69a008b6ec43c35de26f2ee1eb2e458144e847def5b1ca36775db98ae89fd25b181b03a656ff75e2105a6f167aee7dc0a6f0d42fdd104dd29abf052a88b62b129454d710a39aa90b6e75ef8dfd7a426cf4a2b18cbefc644d63ef05f351687e21b6afd40dfddcd05cf50064ee379405d43422015e96d6e9ad0643924fd40fb87a5d8f0d63bcb9cdda5c44e5349230cbe77c8ae763e3a86b9e17072482f91697a45754d819db947b46903f6e9d23d9ae36c3aa0d12915badd0696e5a96c4192f22e69d5223d913242769c75f1b7deb5c3dc5ec20fc20d32530301476e18bfc69291ac6d90cc0b40ba56d8cfd613b5f9604d87d5152e5dd7ee4be4fde12d3317fa980c090c43829e433a097f07ca1abb20fbe8cd56d5e9a16249b5c9dd4260e82a1d816de6de57f570daa80c4f8bdef1a9bdc19666d0d9281d6c244dffb9bb2d4c6cdbcef45717a807beaff689bdc8dcb2a6a68c1e14d1c0407568e7849d4c8ae7bf1d83198080b72f33486b1ac4601d7687098268a08bd1b270ce95ebaaa059b156c6ed9c22ad8afff4733d8ce93d3e353bb52db20511bf3201f7b2b68e929fb7320085c4b08d2b1fa46dbf8660d3daabd0fb9a93741dcf03996f89cf9aa0282e63b23f602df586599c0ca9195e629781943fe5d24a22629c51a6314a349bdb6636c1c263b0630ab450ca008bf4a14af9a0e2348fd37d4e7a5c02b1ab7fdbc740f37d0300a9d7dd74808e024a4846145a3b755c0a5bf5d2aa26c73d6c68d026b1129f49a9f0c6333c682e3930ac0b64482e75b9deaf15e2485c2519852b2209fbbdc6bafa8d04e7b85d75963f527084a824c3fbdae1d28bb11c55d510a0faae8000d82ffc437045f8d8ce4896d63fb0748de2e80272b330bc5912e199ee3ff703b31fbdd0a0e7a048ef73e5ddab294c180df274c291a7ae268e22da71ed66a39435b6ce09b631d841b36fc1cea2f0cd2e0f6820a844d9d43763ee1dced787195d4511e6249e1b969b80225650af2162972f9f2a76f3580e44706c2383f6e72ac46210a04495d4536b030e6bd7019e6c53240dc94082b83af1cc2d763284ba2f9919c2928f9a110dec1d80f0bb3029c3e03a32c47dc8ec944ea5e8ba6419cafcda6886b75b5986b4b9a805c1cf024fe3ce57205b8857c0b76ff42956fd6ad79cb136c1857bb4a9fd81ec75620885cb8fd890d9ae559a54aca76647bd1a5f9786afc3c5a7d1e6a3cd7875f3b2b65a49cd330e49e4e242462fb78cbcf01335cb2775f67e659cd54210d4b6564b5d37fe8a4d50f8679f35bd1784f2a43daa5735090ade57ea3a5c34631d652a21beb96bf69afcc8f6ff53352e44675aa5a3bca48a9af651cd7989756701925661b8647b9affe76663d6442c39661a4492346aec48bbdb318b2e0764cece175c75ccb2c95192ce3967582b8ae17da0811fbbed95f5044e69db9432ce438049740e445dfe8db362bb7aaa7213fa675e0fd4c654bb1e99c03f4b269fa65de56f871a08d6fdf41734b77f75c47c3b7a6107aa56ff3fc98321c0428405575d97c625ddf7f5df1269928287989ccfa6f7f336a37cc155f76f9128deb6eb3e21b80d050d7070ed27d22d97ed0ae51452a74609458f1833cee348e138edc6925083cf2271f7701aeccf94b7ba2353476f6ed5a2d033e1045408eca8be842c30ef997a1a2336e4c3bac78a66218ca3a7f15ad10a557eadcdfff7a4a1993d3c57bbb91d44df454150c4ab6f3ae60c8c05c7ffb913e1488686b93f39b0570ec58470d18d6e325631809e925ffae29b932ae5dafed9a21137a55b879ae3c21dcbc8226b5cf17ef3874f85e8574522adeed2d8ffd9a2108924eb1fe6e73328eb686062c09ee775e07417b931b9f479bced4cb281c5b1c7846aa9c47bbc212613992c2bd14aa70e148b4fa04476221b9232b86ecb4bc6fc36daf764bc3bd818ac09bcf6a530183ce09d393455c45b547358a3cd927ab145d919ca517af2a00bb4a742937a6a66edbc0f42e7ad4ac4208e600a19b9db3da6367d46a8900823cec8d01251d0e7668a6b79896ddcb9a3d4680b01b16eae9851143d10ed679a836d81b7c298936b5742f03508f3bd090c84ea278ede03e970e4b15e68005548f217f969df0dacfeaa3590cbea877ff6c02f23a841ec3ad1ca415fdee6072c5345ba2f4b095e6b956406ca8f1bd06765c4e0596c9cd407bb3171b68534b4cd8bea84ad6b60775fe54f196c198e1720d3da5e278cf460741f57e2ac3fb291691aa2142e90f07c59b3579658fd0c0d86a08d1e64e49db81e05531b5ba1c3f2a6549da985d5fb3051f53f94e5f945d695d222cf2655cde4645f9be510bfa983101076b206a97d2da0e72972aed0c60ec8ca41fea3aa047a7f78e2239e202a0ac5f31e7f1e06166dc25b3f6b44b42243a78012538d62b74257f1d3bcacde97a6f2f3db3e6d356d91a5da2350c5df217c1c3988049384234f794e78d083e9a16208ac0ef1e49b30992c62c0a36db8df202d816440fd66fd48926d56b18952dbabcfa9ddb7cff81bbba3787239cf09dcf14d0b9687d252b19f7270ad9c4c7413113d419a527436888fac34f318860143a1eb8d5fa0e3a4a3496369500fa8d53c147d7d39e0bd288372f0774ceab3f48c5e01b32613f71df936c0b60914d23fe50512adfb257d02e842a40a615993ca1e62be51ac4112f4f0ab3bdcb1c739742627c9e723bfdcc32be2f3b6bd44a783361648a3e7988aab0375ad5a272c5ae38c3e846eb1d36d64b5b770c0b299beecdba068db3222ecacb4c842d4b01c252cb22085f1b22fb9dbe84ae9aeed6cb6e3de01d48bfe09010463806a761748c92620924811f058dae1ce975f933c4ff23c654cb0e388fc098d00eedaa292769339d0fd82c34b4edae059ee19383e2bead1507c69a9c88d1226907f67c7a8343d17b8f939d457164b71caafa26703c75c82f2cd8ca3ad25f3000b5155e1411298f9f7619f13360fdb5a3e33d8cebfff485174f5eb1ffe50431163fef7e87cccc577e52029e052106e36e2c1b549a25ee15371acaf2ca87e4be213b5fc2165802aeae29393a82a8934522b8783010c47d1ec83d21745f3522ebd070f54f40bc470dad377d91ef7dfb47ebf52c2f3f359da4be3c7e594bc9689a774372f6d5b837dabc2ae30bebe1595c28eb85c4a3f3ad84f8d78d5461da64f13cb34d5ecf1a15e3c615526499270cb1b048c4a5f77bf17ab34511439d3a45d1d9e8310c4026487057e205afa6b637f5ad489b73758f12bc8b615e1d8eb94264fa067c331be7d8e41db10db0d0c1422d3a6fb534f7e99c1d8587df349ff713ba948cc258af26f5ee8f41cd81f5ba5797339c2ea079de7ecee0da91c2daa87674c6eb49c403d77148922b7c8be0f2e69ff8781738ce538e9fc14f86c5ff1eab830b198ac4810041074970a6c74fa021e6fd30f433864a03dce0ec64310554f9f2dfbeb6c093a1ff7d71a9ac5aebbe805070903d3b606cf9b1d560e36c64f727c51e722f1b247b6bace7e83f7929d43f315518e0efc224f02ab1345320c53103e1afbb0ae42578e63f392ce99e58addb7ada53bd480e694db4f09b1354e4ebdf0d570c945b9139f523682284144a979904c9be29bccda6d83709201bdfd0660f00b6b7ca447e53c4a23ee4c0c65665ffde4ad96c3a15453dc40c2c7325d7dc6c75d3e3df0746f3a015d729a26cb8da59b8779943f892c58bc52ccbba26b6b3d461b9f2baa4d7f97c25397afffd70ae355159475e0920d9b8cd1ac3a0dcf781a72bfc4b7575a7106d9b2ecc057a3a6fe7005af4e3cad9aa1aed89916ce986ca4cf83c94dfc0e4e22ec1b111b3fd3802064a4f2be91d0e3cef9a52ba7696dbdaaa08f950fb1cdf6f9d5197315c2a6954ed7e922afe7184ead62dfb41ae225aa9af2538af7fa1c55e3cf9550529dc041c3b76d9f5a29fed2154e4a6261461bceacd1ceca356ea2957b1660d0b93305834534fc1d1ca9284c8517dad273da7a0d6784c13e51feaf0a446eb2e9eecdcc033dd463d475ec5292e98605d32d50b983ca940f04aab8c9bd969ab69b3d7d4d158439b5ba1ba8983245a57aa5817f54d9c5e5fe800822911b02d5fbc46cc40d5d0358112f43125ed6dabd4ca4c57f7a2ec17a5db8d5fc65533c8a2cee496c426a72628071f85ba3ee004ce05fb7903d59e5b710724b029d60b13d7f3fad4152062ccce80eb7104b86fc023330176e50e343163c6d817bd33e20d9279e37be2916fa4e7f9581fe097d59f99b071de12f96ca745bf5ceb3fbaf9589f23f6520703086b4d0168b2a72aad455fcd8551c925b44f0c558db849e97fbb90f73e7416301da6875d37be5c59b1bc28eea1057d72e5bddf69c0ba9cbb919edeb9dfd22d853d1b1fa413cba2033ceed6eb534397b8fd0a3ac7fb664896a29198be8aaf1d8c135602b72dd589ae472cb06ec2ea84678f5413bd61e29b09c4c5a5eff722df884d25fad908cf2852fbaa28485aeb14e2113c1ecafc88e8cd4891952aaf975abe461c2e52551b085037003b0d65cb26b94266145fbc6714a660eeff57054e7e15c112b9c1d8135cdcb1aa50739a24a91dd9fc120fe5c2fc1196fc75deb3dba4205ba24db139840f64f089ba31ac24e823bd2c07bf6e8fa8fa897b025304a44bb533abeaf9ff2e9eb9d261b969420c2cb2cda52aa6cbf84ac2e4533a892466199659d19f00e045bf12745916d7dcb2a734011e90315bde1562370cdd64d9b134e73afa61f672f3df20dd7e4b4b93ffb67056d0bf1c5c51dc7f7256050f7167ea6645d1ef8dd2d36dce613f3c35be726d119cf66351891985a5ac83e9708741a4a44d46c6d92a78f16a8cf27d37269c74c78e540560ad304d042c082c16ea4a2fb0200b254439e4f84f274e300f78a23a162fadd7a36046da6d0b00bcd8aa6252fbd81923d3db11d2484def654d60be7028794c9663d63c1d6d5da945f83abcb4672c29d32a486c154768d61ca5e0bed945da7c708115a904a963e4327f3a6014bbee3c78ff3d54002eade6c6cf268468c4d810068c3958fffa4bb93b1782aaccf37014cae98ca09fbef268624a6b84ae8e64dc734261c84ee3e7d186afcfc9ac373bb849cf9116caeeb888226d24499a992f3970ddbfab2945fefa599668411e57dcf4a550e4f5ea34d5d5a79f28f085e33e8577afde67ae942aa75ae8f5699b1e2fd62ff0486c9c927fc5c8ab30ab95d69da108ee7c98a5e0b76d5e77daee75f082335fe2202012686b2fea70893f6628bf71f1b5a38c14628988cb8a434eed608a2067bafcaa535a2ecfe779aa860b919f60ab9c74c59f7bed54891b405e95cecf79da64ab1747d676c58c37d5ee5ef32fd9c760f17bbd522bd3806d3cc50c31b55d60221b668bfa7a169ce1d6f99dd0e5cf6c8b7b18b86cb62ead285a7e0398b1af7874a60d23f2972a694085bb58219236e11413ba80f9362cbc533f5a91149497502ef375ee09514c00e1945fb0edc787295e6bb451b600bc3ed2207cdc0611ffa61b5e79f60abd2ddf75f5a2e53e2d40b411c60d219225716948ffdb0f0ac417c1ef09eb86a3f7623d66f75637440a6216bd23ffe27d0c01658647c3a1f81dd26e6e0c1019e95be1e4cd1be110c54bf1d735c2799c1258c62edd4954eac26b671f01e11f0e3d24889a6f96bb883af778501b119e45f7f33d09ff45020a1240d0052a3cc5391e8b96165c37f42e2ac08293073d447c3053897c4d1b1320f10e4e4d8797a33cb1af308d4fa8ab2b9c07226d41327606c444f0e6b60724d1f6b7e35d5852cfae0950ddc607a898adc60f6c6e0180f3245a475d9d8541a51da910d630b70e4f1a7ee20bc9143483a84897f9099cd5acb5c6efdd9bc2a25888153e33b5f707a1c590b6880ee65fcae38d6067c022eb4926a4c922c60d0fd68cd7d14844df7a364e378d095016eedf583663d75c4e87a5366a1403b80db70a66072c2a26a6dcf2128b87a6dc379fc8e19a866e41c4fc4e8baae2fa25d9deb324cb982e26f589ae6e2c625c82a38f9e37c9cdd4c308e906105d6ca4cdfb56e61584fd703751cbb6c2e2b28a060cec0e4191ceb899b037442a66d845f6f8698b79d2ae4f29a7884c514345742ed8badbb07d264955a36cb4bbc05c41816d9adb88e935ef6be9c923e868c2c45785765af4d161bef18000b3f4f557a48a3330e8cced60b5e4f12ee45a068704a194bbf471e771d06f7c2ad4f6ccf19b99293d15c557f7cfd219ddaccd5dea24a2c45aa71784613b823dcb5d475ce4f9607f749add94d0aac339fd8ccfe63ffab499b68639046f414a9b4cca11e749b30faae901b660d87a9703c90845ad8cbdcd767134c0711526244232493a30af6431628eacb790c1b595e8529922b3408ae3c8176da7e2d6b58396c73d402d35ca1e4d114dfc4bc59891fda72bf7de43e0e806a07eccb8cbb9e066cfbcf0424047fe33916b84e9d1a11e8fa79f46414d23548850c80c9d1385b33e5a11e9750e6154ea72e4bdeef913d557f04ab4baca1c3874d941b44f466d816cf03ae938914224468a655ae8c875a68a8f75fd4c9677ec4a37f23ba8eb4bf90c0d6af6c21238a0cbb0f4751761134223b2dbc3a0554346cc407a585ed3fb1fad7f894d1782366aeed6670d2dea7b892e752e288aa776603c99895876dedf8c55aaaf2529bc52640a97eac48de60215fd159134963267f4481e66d36a6d5bb610baac42c5cb451176537dfed7793dc8712f1f17eb3e524211cf6aa76d2f5a4e3ee884572be5ee8450f37440a6888876de65632c05c5d889129883485a80c7ae458beb3881d5cfe4ec63f5ebc7de829720dfded91ac27c12fb65b25e33dc1556793dcddf89b98e78fd1e7d436a368f0bf0288538313226c4a55f8db44530b8733da9c93b4a12764ae0175cc1eb0f55f091bb408f2b47a44fc02dbe754afbaa84c399b0d585b9f357af9c34be53c73f85611b6c9d5f5324e7ddc481bd223fe7ffecc70b423dcb1a52f6e97e67a839fd1fe1b75ab57865bc86f83c2a750640f112277b936398541eee2f6a05069d990231450bcea6b3c2e3cca2df9d8240ee4c6e517eb77de67bdcc56cdc689452c80558ab298079bd43f5626cbcd446d2a00322021919179ee050c8c5e79ca524a481886fe39ef7be17b2f53ac15d6c9878218ab8b511ac86251e3a7cf3689bbc44343e51da49085f98bfbd998ad905cb5c394bd19d293176f20a46e975e82ce508a3c15a6d13dac5a8239841de7ffcf202df5e4dbc9f654c3e801d9f4e0b3dd8af957cbc2b87ddea90ccae5b0896ceaedd2d30bea8e6f5155d0da95d2a15010175945e0e93a18fb76508b14022fe9de01e3fc8e853256e8121e3c8bc611e776c4a6f51a9395214c9d50c9cb69fa88e1f33b70af28fb6fc0f5c5925ae3d902c62dbbbbd0a2afea0daa17113ef5d1975863d39022656a804ac25698f380fac54dff54d1f7420fdb6221fb149c8d5ecd43924f03cac1c3e808fae31f10e12062fee56c1a938901d2e2de1ae7812014d2f3faf0839bbc66e33a4bd73cee4a305be139bf7fc6ad21e0ffc0f2ceadbf87ea17f1432e11cba63aec7a71455a2f464cd4d4190e9dca3353e79fe5290055155d30f0d2319166dfc1f9d49a0fdd9f5b4485b447545a3248af5824c2e2ea2bbbc351739145a03504af8d679c5bb21fad8e148cb51d08fdfb90f1d3f2e10e28c3091f028445f3c4e165b227dd3be312787077cd7122853fc55229100a7548d4e43e45ddb71e2104258621e4043c2ea97cd2bb6a36ccd38a36168872951c44eb01b92e9e16368f09fc130097f2deea5cd8a5aca9c5638afc5d57c624bc83a51c8f0e0cea5621d0f4d32d40c8ed04770daf11fff798afc0df31fe567330d9ff1c591c80379bac1190e43f855280eb213800bbf560009fe4b9d9f6b7f45046c87eee0103e383ea6ca3abd685e87ed7d0f5a32b8376995b59735294a939cf4c885fa51c00d44c2b8716b20641507178be20a969f2f3a368503d58724db950a3277af70da6680a0a0c1a51ebecfc65af8484fbb43ca98b61cb8b9f5d29b0bd5b095e1586e34db34eb73261ed2290a29644fccf923b7ca25ee5ebda3fc25cbcba401de3cf5ce9360489645ba4545981abd5d67fb36b79f55b2b60d37821dc1c31bd99ce100701e09d7921b419b0cfb66a29bf941010df85efd83dc48b853ee7abab100223afd079881457d3694664b6d54503a9efb68771ce34f70e471be760e3537d76bafc4af796ab09dcf63832a2f3520ceba498cb9acaba6fb0bdb55038557f0443f7304069f68212476b1ada3d3d8498390c01b0bf397c8e43123e33e479e6cc4b38a57bffe93dacd4c6b49f524a644e5f13c5dbe17b6feb3c0f7096da05e5ce7f02b629610e28fc2a168eaaa0f07637c004ad9b003c605b393ce16fa407f5596b44c574651d24b9b7ddcac0526295a40b5171b7ce72d41647d005c69bac046a2d28031e2c32a8dbb1b98396b204ed1fe5ac10819b6fea75cc0b327370896db18ad28b90d6bc4af04681d207756ac5ab910e48d72a7089eb796969c1a058d52f3c1efb166d267f21b8881705548df77484b859281b5ff2a50ac69f792f24688651b9bc91d84b9ab73521fbf4016e88be3524978111fb04127a25a0bd3f2003949dc27225894368008cf19fc5808bedc87c0f99739e84209e38c2effec8dacbadfb93991731741ab4bbfaec88b076f069b065c79ed58925a1f743c95973198b16a7ed9eeb0a43cad03135934f221d0818fe657bfc2798ee35bd5378805aca781ea86830d19dbbaf00474c30bdb09be20c4f96d02319bb1a2b022cbc60eb31c7ea7458635aadbb73884bb8a23b494eda1512b7e131dd084ac153d02985a5fef1909bfb03dfdc9e6e96c77d048283d4cbefb3ade607670346df70085f250abb69aa40e519b9a500e81956a9dc112a1f051053ac0770a9734d804b8a19058f720105bdf8203291c75c8e40c14875c049b823913e9db77a2f537a1a4278b4e88fc91d4f5cd77785e5e9e7a0773a372cbc5051601b7af56b6a88f277a11853305d006a5a514d82d4b965eb7b9e75dc80046e8a41478e6423914c139e3ffb1c1fc02f5ec59d2537c9d0e418db5301ec3d27b793eae10fb705778e8a9fa8341fe5b9620570daa54eb108b3fdf4210328111133c1e07f264db7e83a028d239f9dff76cf7e9d544bca0e65d54d1da8ede27b653fe6fac6045e4e25620c7edb207d408240e4255808f08643424d6ad0d67c859027c3cfc0a077cfa9a6588c5305a41ab7b2f7147ed68fc53f364f61c29f476b9d02b67aaefe97a01a4dcb0462fff295f4fc0ebf99d4cbe7fa393463e6b84e1385e1ac13e94b5e076a6384052bf5fb1881e7aa4cc44b7333b6ecbbf5ece50905cc8f40e948b0b1b9cfb6d053cb1599df024beb4b6a202cfdca9cfb87a0f4ffc50cba4c25b4c1448fb7500094d8f84df410303b6c145797e39f69a0cc6d0bbd360722013965780a8ddc5179e43682bd38cf71e3f8a97b3c932690594278cd61781584f0f1faf1719d2ed0b47cd69c523c5a570cec33b18ff26cb53f8b955393ac67827431f9c51792cc75bbe104bf73e447028c8b1be99f47be3c10c4a6e7603ec8e638898c5ae5984963fe1258767cc45603416ae930b6819d559cddca5e5d7cf0775a6db96cf2933b049ed9867efe4699e9065a863a87cef8aaad9278ecbe2f381937712f68063da2cc77eebdef2a9337bfef2a0aba32c6d342fc2147d44cbfcdd0b63b74a39dddaadd2011133e96c8c0764572dabe0c609d279c7435f9dd936df301f1bd4bf83dd314bd4f37760ce403d56caf6a228babad172ba865a04df0e5c8fd7e04ff8374bf9202beed9051b7fb78fdf3b637a0080bb3f856440bbc496fe47067ff00c73d67aaae953b87cb45cfb55da4d2a4d76daeda28497d035630595b089c4a370a0ef67f3fde68f0e8351500f5012d3e04a4e6ae6418ecbcd2435f29ec14da973d24ec1378160b57d44ba288bf98253f57fe5b1a34decd4f602245effc90e7964936efbfccfeedf840b551abf402c8b8320abb1faf307261ca07396983acff9761a0e337b09af83cd6ac866fa4eb261516f33bf09436910929bea5e1f2eac07ec18b17148ea39fc66951b14d21d48357bcf12a5b864535045c0f2a1fbb4944a33a91f0278c2148708363a95475339994c9260bf289d352f125a19216142cc5c01a7e2d49f5b3d0ea8dd7402ec7f626402273a285c573776edd092b59d14694194dccaf475ffa5c22fb1ffe101acb50f506242f4e589b43049a4b220bf13d92aa0fe690bf873b0865c6d7180dffeb864c2b41d7fb565b93e5f04746cf2235b919e6598f54eaeb7280fef48d913b2037c5930f4c74eb18bb74b92f6db3e73829979d0f067e031866d9a2d640f1070c60bf42ef4dd016b1e1c2143fa6b7edcb6cacd4152a3408ddeead7fb887557b440066644ce5cf34c9e5960fd66e3b513b2153c366039f62af4b10df4dc2678c1498854e837f90796ea9b7e4bc4732ef2072b860701e141b8f0c9eea96e2f4e47c91731ed691d050b14f06269c3e9f2e298216f4de15a5cb8707a409dac4453e23e32ee6aa195787be2b60506b46d92221c6d9a72ee65dc760266ee6eeb0dfd323b2b473d9f4c393650e79b4d0dd44a3fa024e380dcc33670ad749789e4fc6b6ae35ab596291893c10c21f833ea7dfc080299e55da24d8a73698eddc531b6eccf729c6c7288956fdbf98e0b8287c989ea773bb54dfb172a35a2250292d4090ef1b51cf9e008160e3ead99b0dcc712847805d4f896e6a797c5b99199cbb17b4de48c7858447d9b1ce6f26929d6b04348db0a08e7902737df6483395fc9105c9c446f2ba891b9e4439f483bfc1eedf4ec9cb330dd38c4088876c88d740a1c703f52e64dc397178a786c627e17164b1ea44d86818aaa88cd8aeec5d3949f662acbf310a4691bf329a3f9a5cb674b3c577263d97ee97a8318006caabf07aa62733e281dfcc9522fa571a4aca5490357a75db458c4810387e3785a1ddf5c3be8017ed9d4dc89ebb06b63d2c60b816cd5e675ec831bac20ad2333dfb6d33978b543b2e40e84a44fdfd8623a76dca00516af586b2de2d2c2ce6d9c0b815dc51fe8d5499c3935bb4e29a07d80f2070ca21adb1207fec0e8f70babd4ca7f357980858da2fdd5d7b0aa7f498e02a592fa990998814cf2b18fbf46dd224425b86472f50f9b52fae5aa47d7bde8fcae672cf4ac0595f7d956aa11a6f38df8db5f2a2684fb8efdd1aad1e8a9e35b3e5ffb33c3f1661688d59f51fc17af0bb67e0f4277e29f7fbc17d16974724b1a7bbebd82836ae2d309dec168a3c896f796de2c4af7a3466bc69d4121b335ec5bee075da102ced25361417d4939abfcc3a8418b7117bf0b15c4ca51a95ff5efca6227d83d3b77a771b01db442bdf8111781de2544ac1246bfcb1742f8243b900dea4361f99ce35486b0e550e369aa0bc6fc63aa6487dd7f9a35920cccda9602ff832f10206a3455fe31e17c4b42005760a070d3fc0943ffea545c8348e9177569b6f864b430b0387f4bac26fe1afedd3c961a93ee61cdde26e02bbfca3dd11a539e5b9a78e741c6fe24b7d8eccfe95b3b12690d109be7b58e18c9dd4ba45f4830544bc8dfe2f5ab6db5c5dfb716cfc1abf7d962fccc60d102909210eda95ea08c946fd610a97fbfd73b6dc15c81fdaa0dfadd4a4375eefb16c2fafa8666ee4ffd7ea13e65a7b4ce1bb79c02cdd287e19915577e017da8d6a693056156ac355b6ee5709f0def10009d21103e27bcc3f2c72822f22b808ac16c32cd252026dcf91c169f1ec6692fc38fc0dd8a8c76ced341ac89e117acf4535dbc009a66dd9003d142a49bc46a5451c468ec32cc720596f702cca809f22b65c98c601a8a7b826c27a82ab24a1be5bd103577b95f858d2b45a0a26fd459d7a4880b41feee59e8d1ff5f8f8c8505ec96bccc8f31718acd3b5c4eb2a3d4f784c3ae600ed6821e7a165f5a6686a5eb9815900296e4e57aef0764331943cf68212b8d7dc4f93a33f50e5950f79e572cbade6847c548a750625f01d0e7f67fbd83470382746766e09b3ed3a4babb0a3149537625a747a4e55aa43816a1fddf6ee35e5fa3c6f1ec32b1a3320fe0baaac5de707fbb9cd4442e4fe0dbe3c1e56f4b5cfa516071bed5200094469cca55bbc43842d4f957d349cd7ae8f69f31180e4d6edeb5fbc414af8f55a9020a8fd3fc35badfcc34614e53045b92eb76d43224ee7e932eee207cb1f902bed9c27b8b04d9e8fff77a94fd867136f82fb5fe3235e4537edf3c7c2ab86f5af9cbc58a377318ee175bffa01c11e090cde00cf66f161d9b88bd69e0c2689f38ec7998f116b38d190c66d2899179aeb7c399267b655d7b64593d00ea63245679bdcef896e067905b707c16276307f338fe45ba3c4ecc15a31d60974c069cb27d6613649385fac7cb5836d7a47fac936797da905f8ec7acb0e060804b2b22d8aaff7cb1b7c3ea5823babaac02d333fcda342d3cfbed1da0c874843f42db1380e91c88ee0488d012405ad55e93adabfaeeab58069da3adee8aebcd5a1df4f236273ab363322137f7a650aacd7f7a468f41b66196c72eb0b07b9e9900dfc70b6cd6376a282b42bc8cfa16298041c62b2a36b60d0c6c02b70c82c760861c0bc0bb69e734f58dd41cb58629a15a2a95f3e6748b1de176ffa2de6ca927c67f13d105dba77af32b9b1928b8371a2add09c6d267775eb241bf2027694858ef466643f229126d228ca45d48a2e451e3d15b592fbe11432f1dae73e18db20b76c196b580d3b5eb886b8dd0c27c1781aed31d48f2eb4cf6d2cff5990c50d8036ad674441f5fe572d43593fe4b1edc4a1b81d47975a68a1ab61038138c0ff5d7a4808b568fd69267358e7680da87d37622cfdc976a6ac0a6bc0d19d403888715ed4befe88f453c5241cc2d8df0593459ed86a2c8a8f7ab939fe59930fa62da5762da5c37de3ee8da637a671fe2595b0ab5c0262b59ce174bd62e43c759a31c984462a7d99e543298efd7dc5888f4ebcf6d6ed6cc5bb82f0b30711fb7e4380a386f2c77b0aaa367033055740d7eb580595dd2b0137c40e5af01a985aa49702489ae16583b8c45b323f61a34a0d4d0ebb22c05bed63034649428c8b38052817ad26c13183233dd5ac2f982049c2b9b070e8172d59e3128977ec73339a01302021712a7757b85110b3a119b42b2380f17c6a859b4c91ff2bbc68e41313c1a6f7f1ed86b1edbfd083433b2a72209194bb63e744d6489df2f082d95bf4d1657a9fe82c28ffdf079f075df659ee20338115b80e4b6f1f901bd5d0f436fb187ea73dbc4e053da63327a1d86dba9ccee324865b4f35072c7ce3f0ff195849ada05f659a7fe8d4651995cb0730ca80939e478bfe25daa5b1e119db4860e27dd3ecd3ecf576d26dd5c84ce899e74ad4326ac510f93faa929e0c8810fb8eff62de384ba59219e9dd0f8980e8792bb7f182220247633ea02555646daff49117f3cfd5ac029afdd82c9c16adcc37549185c1f8568e16694c399dd035d88226dca9dcacf065a1f9dfc14081dfe66382c272baddc37eb9c36ca8d43c6ffe258bacc86d4132a58685c4423e0a1bd7e41618fcdabd7ce4a958c4c87d0297ca583ddf55600b42cddd277285654e267ed2a10e668cbb410d5b0ee50d337c7d4f5f6b1a3f23692ef758ad0891f9234cb655e83f7823eec040d321c323ce888c8cee4b3acedac78bb9b8686dd6f2c86c077a5b340544a8d5344cfc920e0769e6154915fd985158dbe5c4ec0ad74cd899eacfe0b75ea9e5dcb4b38f7a0571eb96a380dae656d4a88869458cb18a7f108c2b349a3fb1aa11ddeb37100082c8ce1f12e28f4826532de88b40ab64f2a86330f9f9c896e4510d0239d275a059e7644ca728d8cd4a8f0f09d1c98e2177a8468682f54ef1be6b186d4185aea15bcb4c80c5a3c22f425a701800344f84a69e57b373131dc87f8ec004f1905f90dc9d21e91face24ee8955d051efc706567085efe9e814e5e6297f5c96f2a5c693f25c3f7ade40ecc31a9fc314bee0a5bc4bd4ed708f2ecfa4dbb533ce6ac0f0a4575afaaac82756e8618100333e445992ea6916c0deab57e0f5d4c7c35ac5ee105fb24f46899032c5a64511f856818702adda5552ae3e67ec89954163defc1bf95d76c53be2f1aa3839379a14c18ce0c9f26413a39f09d1cf44e6a43f1556f5588e49d6b87be7e90f8ce8211b4ffc26b8f951f8b6dac55cc3b157d00d733e6c785561dfafe8a6ae911f112206c632ed370c24768eda3344b74cdec328a0e27179dad7aa59bb6023f63b4e6daf45fefdbdffebf9b549297ccd64d4750eea7a28ceec4ad0afa9ff41ea7b507558416bd65903606cb9eecd19ddce65b0e143857abf3a01abf1c1f74e0e2740242508cecc2cbe8d37a9a8d9e4f167cfcce7d60e32e8d70f9223af1776775c62cb9ddd443fedbd418c9c7be5afc7671ca50b236f0cb8133a548b0d8e612d0d49202da2346a63357e0a2aad216052834bc059af16b1f39f85b635cf94b7adc61761a50b8255ef0a9b41f1f002a639df0590854a53e43107bdd96fb074c7684da77fb86d899dff36e9b511a5b84857d2519265db2bc95d735af84dbcd252420792a2801217bb9c72f33ab01e7e8e3978eb4c72c1b58736c494e326e1794c14367362f922e2d3dd35b67a780619bca3132d634cc58537d1f588031595129a2a02e3e8821f4c0b7106697314c516de0b41b17a33991e8015d7b963798a7a62c554f97d5fe866c11a16b38eb6ff9a0e1df9183e8ee97efb3d1060827574ae36998261203956832c2c3165b69424d13235639f9068fd30095a79442e71db776f51c6487b9c62ca3f12fd1274660753bbe87765f0eee67d6acfc01ebc9701516e078bb1ec77e0a2dfca8625baea8d164b7b739f18f243a6b2d361b24156d7300ccc86f555a0c4e5fa6421416e616cb4486a180e306b8e4250f2ea2352e3ff306526a5801f4efe223e8694182e0ff21946e3c16d33f3fc2cb78c9440ecb3e53f5c8cc10e309ddf78bceb9a0711bee5f7da4846c5d648f396fc5527b2e803df50a6ec31f6960d16c017dfb00e5a3c728006b1a9becbcb022c09d332593b5062bce82621495003330e68c5320a27d822466d68a07336047b645fe49347c8df4ac27a8fb9d7d847c4d06091cb3e2be9ef4dd06be1571d1c8a6956354b1562362d60a66f1f5d3f85fc02f84f33956c475ba6eaed482bcc61ed2c94cba389c4a1bbedd8833b61ffcceb3f229a3d1fccd09c157bd5ca1d75c53196837ea9d203ae3a31107b5599391248b30c2538c0dbe4deb09f3d358fd66084e77d79efc4f5b105def47d7ed1ccadc52a9c36991224ea8a6ed1f4c2144fea12bc60bca6dd8f4123af7fa0cba869dbffc9de26fee486c9a505befd7dae9442fc41549a040112b9e98fba7260aafa3bf1228ce279e8db6f3cacf60e725637e9d091973bf92e1bb8b3842b73498da152fcb4a56d336a3e4ee086ea12af533287a345431b36a872a1a445bb9826a519088414c85c5432c4da33dbeba71bfb1265a9b11489843d651c7dbd75c9a5cd8628143dc35847e27e0c87570fa117c8a6f26a607e296cc313937fde5663cd5a97e319d09ea0466cbae283018d85eef6b53f84b46c7329e4f4b6647f72b616d8664d2c4245843a7e64ddea9dea0f1a0b0b82f5d16732f4c919f75f322a8758a0bedfe438afebe7f969815fa39fac54ebea970331fe05965b7a0f3448d090819828f4479b8ece6ed4a0d8440a13007c7d2c7aa87cade15005ab7757710bfc2297c74cbd1627ba18f785f0a98bcb6243ec51ce939ed18d1548e3f028af8aebca42108b981a385277fbaf5846216b5bb6006159bb1a7c240a8929fc33871df42e714cbb318c9acbd2dc58a786d8962b8b39bb857b1c789760dbcd6285aa109e26514ac68db57a6fe8f8c9f5a84a3c44458ae1e147d729297dbe1aae0f83c3e575e9b7fd9630e51bf9f9c660e94bbecd27ece29feb1c6af16c0a48ae1f59c7bc67b67294e5d0b3c027844d89bea37220f82a30135a4bf95bac8449f6e0027d853870822318cec5e527c0201426e0824c9097b23a0ff4a6a73dd7075df24f129a50448d26bcb0d4233c1da12c0196b293418a4e3000dfd5690fe4ef7e0aa3f4c84e0b537ec6c325a884e461fc80a0c5ed06df6db91b4b9607b3ae8938ac6b4acfe24524ef9b0cceb59d77c737a331418487d5c9b95a8e2a3e6356f52feb71c9e377ea58a0a90b3d208e54ba90bec50a36e9fe6ebc60776c9e5adac14840264c2eaf4d829e8dc748d3053924431f4cf8177323e16fac1234097aa28e316e67dc4caaaf7258cfe4034becfe7d8bb316bdecff49582458b592383f064ed6757086cd25383c2428cc3c193f0c2e29c862bfb0404b4b536a6f6f360b4ccced9ae02ab6636bf8b95b21a3654070d8e4d48c4a13cf9adf13b647d4e3b8da536c780fb300e66180b3a00c3766f5070b03594142f60f3061a54be305728397e40d251f7f18f94c3d139918d4ebf443e1d5c73cf3f0e5b898ea787a51eb3caa79d1677ea15d0e12e0bcdaa65fd585695429856cdecc3380b1e7593d57aad421467afccc7fa6e6061ef7073d619a0d69eed6f18263092fb26348f98979de2d33a60fe853bdb98c1888f0772c91c4cea37e358850005882e842db0096030b460cc05cdd83e7d51fe730366f563870f73a41a10874722240286411fa6d8c88ab75746fb12b39296ea01d3fe80e12d0ffc1a2feb5336900a5bb09801e6c150244bc4c1ce058ed5a5fa36ce2eb5fbfa9586e6b30a2919d41aa91a663b88865f7629d887a201407012f36b4db792fc1d6691ea69f0aed1667680d561813262d3c302f4c64096c272b77d95a4a010a5dc3e0d108b9c4c21aaa496fd8a1fb3990785eff32fac57f07d70a0292346993c6f24e6dfd70ba5ec6730c423f24d1f6cea631e23a1dda7b707ca0432b01a9e0657a18e3aab8769099e29575b86b2556e797d52b0ef6f6ba17804c36b7fcf03b1983b21b880bc2b4494994115265ed26346e044e326fdf7c99c2cba03cd66215dfcb313713e54ea60caff1a923a341814022fd1385b85ce5e8119eb15d245a226b242902414776672b4f16ff05dcaec140ffc9f6bfc44235f0b2f050f0dc44edc7f3d7b7193804971416fb73e469166ee425d7436ec9846fc8de58d5e5dc0b584cb3a86bc13f140257280c44bfc91ea67e08d90fe0c459dce3415ea5dd630bd3da70ffb3dbebfde4e45bdba2a81f3401d5ee023029f3701cc37dd58fe5969b4a964d5870d34b51adc0baa22f62af18fc3e748db3285936be50a50ae5b9fc280df7feb5c41076e34ebc039766c3640558220a8d97ea3d2787cbc2bb9a4e03dd2b704e60a49005eb7d442a0afabc4d5a0d437218af72ab36e33b51e3048a79d617a49bf285a2942c6049d45d3da01cdfb3cf45bff924f1412ccdc352ede013f27d7d55ba1aca44378736ddc50b4da693828d6e50e28c2395bb07fc1c14f3e922a35f826523e43cb14594d2dad40b65104909acf01f3a997ff7d4d5d0625ef85ac3073d01bcb623b4b15c78fcb824f976043d148133c76cf8ce6a9b835cbe9f0335a73348b906a839e6464384dfb7a2f2f1ab8d55bbe2fc5255b88dab9434220653342441f124fb97554bdfc770f6c8c84091533b17626c0370b161ca36e62b646fee682d89a7510e3f6cabebdd9e74f9c55f75cf16f92473c8e3bfe7e8914460509767c7a2ac6361995d3985608ad46f537b5725ac2f4fe57313c5db92c05d36faf220d10295ef870400d00ddc2cbbc294c631bddc935b6b033a8169d41a4c64c7b4a3f28a9e366794c2ea248b231e85935a8dca6af37be9ed267a29341c4882111f97e8a0277d9f86e7b6a222529826457fcdcaf09d282b862c284451b46eabec40b502ddad8c683558977a5499e763c4c6a26b694d9c07efb877fca1653d76b7eb4af299cb327ed92ca36ddce46c4718ff96c15d585596bf76490a8918197d8130b9e83f91be1ced4c248d802aafb9aa8884ce333e98734995f1c4cb28cb768a73f7056598870d42951b8daff6d9a1d0811a585773ec14b324b81db32affde5bea0d171d1866c31cc1b8e6a64e399605225e0e37909b2d58065b6a6184c42e3fa79aa412133787e6ebe3ca4a03c01cbd91260250ab342322ce8bff263f10a5122b4f5143715cb448e1b7e4190b847beb859e6bc1b30c92ca156c9bdf633a9fbed4718879783905c39901ece61c4119a8e8c6c02217b8e1392f16a0a2cc7e81cd6b5a7cdd9fb9ef0e32c5074290d36d8be2f21b96da94e973d7171e50957d2fe871f774372cc811e263ba00bc7bbd82aaaa48f250943fa5c487f0aec43bba1439dd4b6ebd5bd6ade106c9436d24a503529d451a15601df91e47931fbe0f1ebdf75beb0686295a37d96891b46a15d5dee23b392254fed486df69eceabac974fd9e4781befad94def855ce64f12a1bc7572e2c6f6f6cacf91658255845effeeab8ffc4a84e392ab9089b64e9e59216b5d103265d9031ebef333e11f7224b39ee821619795e53af60c569083f6870719afd368e1a69cf514218c6710386c6cfef5660550ac8fbdb2582411f38f06afa0e81123d96d0f7e883db51be011281c69a11d6b6c7d4d9aea4748ad436530f2fd5c614b4255f329944061e2bb7db8919f3b9bb37a87dc55aa7a73c4c8e64bb3d3344951e3d28b91f929e2fcc296be6a4521ab271b311cff8259b9470c7d18e7e9fec018d831ec3d39462170893fef3762d3a6cb853753790a2266984fdb9931a7d0e176106995d91c838a449b1c3108e49f4878026d44a75803fb2003220bd19df66a7acf4188fd2cd45da3f15e8faa19dafabe6fc7ace38b4ae1414fd12366fd34fc302cce05f738a718327f5ecbbe36a3a536bdbe046d449fa60b8e8b18241c982714942f786fd568e9b0a804971bd4423dac74fb9e8b2850d4e8cda70116f5d4bccb45ad3dd5d8185fcdc406c2286d0f93182b557e425fafb3c950949172a539f07cd41a50c1ddeec64ea1f0e38ae3e62a2b11217d4c187f919c0d09456af02d3f70a88e025bc30eea3dfffe4ce5fa3617b067d09f00036e52dd2f0a7777bb4334727c684fe82e4d1d6e13ca4786d9951d1504dfac79d5a36acf94ba02fb90d76cf5e1a7704e0404d5d905c0f5888eeaa5d8d0116406c85d8a8550fe5cdb1d4182a6b992bc43f1f62e99726f5057872536bb4905ef78189b913228ed4fefb593b8d4beab0059e34724d5eaa1197e19897c22010c656587d99c5f1815c238cdee94ccf9f8d4cd8e7b55fe7346c473fc812a3b1d5d7cc0d9e13c8bce8c3357805ebd034b9aa158e88da2ba042ca04acc0f220dd892c29859a42b662cd6da240c4cc4bfe7af50a8ae1601710b8cd2458abdb7e3fe78f6d55325105aca908b24537ec1b3d690e66ade7d4217e864e5ef1a2a0b22712b5d039879234771fcd0415baadaea72517a113b40bce96fa4f7a0c320b8d9a8842c2bfdd2c86da14be667ce931d38eb99485fcd4304016a22d859b24162402bdc64b2db9c6a490cc6769f7bbd4d4ec5ccda0e5e3c334e156b9578763add6e1815b4f591a82eba19bb4fb6d3a98794587d7c7dfd1f5f62a0594c12858ef3aaa1fc3b21fd69533b62446eef6dabdbc64f36795a7fab3f8ab1823fadb6ef0ddb8cf8555e0744480c36d812dd824542742990dcccf9f62e33df6e81adc6263da9b205aefb17e5d22217acc451711c8c75bb97ae5f471af84238a5bb05d38b684980064fa59ab3d3743de76e483c762f8bad84e64989e12508bc85dca4e40ea3cc79617a1b804a7ba812a501bf0a4b156af8bf8d54a091c3acdff3b5a85ed0e869b1ef6418c1050382b4c740c2a3e5c7a16e3acdb6b5bcc1a10bd1d6949049c72a533b333ed39e3a76779aaa54f713a5f03213dfbcb326714d1c4729ac2905b2f81d0ecff736d97ca16044a06d97e7f5cc3b8e1ef4c8b9c563e2e21f13bce7051edf4f7030e83edd02aa0a3a093a060e24d65ab0e3e88a53b3a27e7b8f4308fdeb0035f3a6f1eca043456371243a8ad492212a7d0e4aee37de96fcf3919918155df9bc556bd96f548352d78a8c147358a591957818e99b606a68d14db5ff5daa39d780f6c52db01099a8b5e99beca552fd6d556e6e04e4e2d0bb5ea6059839c97abdd16e61feeec0fa5395b759077eb79950c7cfbc9ef354b8a21d0132ec4078656027221161d27b030fa809f54eedb9dbabc43bd7ff285b5227995f4169e6fc1e89dedcf7f08141a2d1dc6d3fd12ce24f40c9ba6f3178dbe8c638ee83ddb1dc4897471c2347d3b0923fd2099e42fd19b4a869e92183df8803efda0f40fd089d89fa04dae8888685213399b5e37677d02dcf198be30aecb832873f520ae28783c5b9b9f476d892f1263c47bca9e86c972572433fe11b4eb578674295ce87e9d830ed07900cdf8223511c592c083658dcc7a8ac522bd37dfb7edce5ee4f4c80705344df05474ce18b114be5e101a2dd9ebc562ee75b8f6f1c41252d164c763e768b7f1b43c54fb5b37f729eb49392db4642b9baeb8c74582e9bf9c65e7eac4ec59bf472a9251d0fb64cf1a57319100bfc79e1b39e89e8820236fe878f757c3c49580cb5d00b70a4ae725b2e99a64a30215e309294241733919f53b51d8fd4ac2b91c658c16c047dafa3f5eddf0313dbd48ddeda7c006f9b4dfa071f977189a6d0f3325b82d91baf0f9c3a61243e60b799d278661fe724e153929f0a450fd36ae3b6f76d14e1fa2b256459dd3b16cd6d88075df74bffad30a3d394ed5191cfddf58ccf7534ec6a9cbb3db572ab2c1614add34020b6cf45e2d590d2f7fb8afc7e748705597f26384dda71649f68779e2d727ba97af7016bae09463b6e9e7bbef1f049fe1dd121b52bab73b1d10d6beb3b3be7742d324662f2e90722d59bf5df15052cad59f9e136d294d59bba2c4427df90df50c30d8db1040e1bd1ea72b709b050ae1cee1ee895a4d95d5370a808a18680694051080ba5991dc4037288a34da253f5ea186da6aefc4d20c260412c4ab111201eed551dca376dbd6acc6fca9fb426d3c8d1e37de366f7e20114bd3e39bef2611374c83576bc04e32a65bcaa57ed5447fb78650999eea63bdebe53ad274dbf2c21a179683ef041125ceba560b1795742ddd55b02b97ae0c45ea54d6cf42e8289997331137f2b89ee8c5c09bf4e2676e85c658e7c303aa180354dfa5020e65480a5e5c25bd60ab6383cdf9032b1670e867af803906f20d7ff50806bff53b73ea3bff2d8efa8333eab5f6d0cdfc3f282e5cba209c9eb719b2d4c1f9e92d549b56238eeed859b2838d9de4e459e50e555bdfaf8b8df9e6082adfcc3fa9016d86088b229671c1b2eb8621764d666887e2f7b449f974984b89194e633585228f00567a6eb0ed9d63e35ed5940d3287f67d3c6fb272a311f9cb0e4c6a2e9b740c4b688dfb566b84010e8b26914485dac8b46b604429440f450a29a0bc5c2f9380baee6fddb8bf110275628ff68dc5c6e81a7d25a37a0750c1cde4e55b2db5810717500f469a810c1fb7a2b4aa9221182827bbb89bbc7e6742ed661fd97beb76997397a8a5a2a4f122f416c687d5ebb8ef4716c330a18fabda17f6c8d3b497708a66d44dc1d3b9e87ce3d42eff4cb5a9a81e0e78f550b107c842d36434d2c78596b20b1d96f312de5a8dfef6781b1b392c0794ee584b10dcbf610c1938a12cdea0ca545c67ef7f2bab28467cec0c617c56d003b1343c282c656f016a2b0997ceb477189b8619fe6d27a27031218bad76933f98e0b5d72610f7de75fcc3e87adb541538d1ddf9f20c3ae96439a1ddfb414029dbb5270265f4b0943751e09e3ba40e860683483731363af6898bed34fc144f98df883e95c1ea3bec892dc9ae1f8abe24272900694674c351d664ab30adcd9df10c8ca52693344f0fe00fd68c866b711bd9499040ccc5f051ffcff85d75d962ccd9e6af2daeedffd4493aad435254ca096a80a02193a3eb597f01211fd9aa39e558337b9a8992ae0ce9cc3fd764bf4a3437474c397bfd07f4764ac8f11950e66fbff1d817c785cc5d58f5965d5a15b4df28cbaf334e1c029d30468fd6f9745dcadcf979bb77cb5bd8fd2356006dad5a8dd0c931a9c0afff53940d7301c809dcce84438b00a03f477c0755059261ad8ee07cc0ce31e503eaa6500ffc51f26cf4cf38726ebba60df0df56059e4e9117d64be8a03b28efa75a30672d8d8a9a57525f16f997bee49bfe0bc3832a0ee5c03cc8847ad8c42f35ffa2dcece8f7b5992ca88e861629a4ce18571bb73e955a84c6566cc104f832bfbb757da42766bf007e41f8c5e90ebb416c35f91e65c13ed5f7e7a598a32d8870d1ad7d609a7095d1f66f05805554531339b6395092a9913e4938a3548a4edac9ae7d6dfebc6ae54043a782de21c2f75926a496b8f35cc80ab01f0d6d06b104970d55f5925cf7fb2477605071080cbeb901386d1df0b6a5f2543df497821771a9cfa16dbe8a7305a5c2761d3c3a0b2430d3b187c94a34af4e9c32673ecf4d3fff154a634d7928f251ba8da04bed9493fef94a46efbef27ef3140c7d0acc3e8d5885d01908d6523ff418a35feddbcf2bd9f401fbc9843d5745ab507648b7f17c23aadf76a1594668bed0e3cda8d61ab638a35944aa182109368e2e68231e528cf28b1c71d700b69a9232ff8084f03fa9da1bfe0b9812557e97f580c699a82c36e346fa3e813dce2a24c34d5ba39f03d06d90b70d4570b69956b1b9b82950d4d87855a590e66b4011bb4e02b1874c7bf4aacc44c01741c63dd19d28795470a303307389356462b7baa0b28fbd1bf071bd997be1907e1bc9ba41c875420a5c1bf3be8125d6e83daad9ee577cfaeeaedda7d9ab4c30fd60eab7329f9537ffd8734a71d41e6016349db803a901a555ca3640ebb14fe44440549bf9b7bd6ab6e537a66db77ae161da7c4e1e6eb7dba8ebc88fb83dcdc8d4d46eb24f066e2b26aa2cb4c1ef090215f5534652ab9c94d8e07565c7f851deec6dd4c8749195a533bb25200259b5f90c9423e302962b2277daa079ac001f1c7bcbf4950ee86b18d7c054b80f3660003701541ac6ca57e4c119228675bc858c9ec1ab8a0c85a95519c3a5d32d0923e3c5c25f2075f3e304f14304615eaf1689e497b29e2e6a625e05f322f26c713c2e60a47fefed11de974e389ccad0a65019a79bcbd01f824a6eb5c8662632609dfa6ea65093878328fcdef96e56bdb12d06659cc7196d706c6f77f4f7ba300412f96e2ee16f86bc1f09440ef75568343ae7a753d9458fb56e1ff893c69ceaecc2732c90aa501a64eeb95edc990c2abf2aa7690a9499b03e9ddf28bf8aced4aa51cfbeda8043589155737a54bfd77003c54d622e44aec31da85266fe1530f3260647d8e59b8ee5911b9502925a70ab3d71151d5fc4699eba1817e6f0e44bf8bf38d4260f8e32fbe95fde2cf8114bdfaa6a2180812939d3cb253a59ca69252bc8ada340d2717544477bd5701463be98d500fb04cf3116650a8f75f5887de3dee6a6d53b197fc76dac3cff7bc9d4f7e29eda36ee9937decfb0c07b7dc3ae91493e7e3315c27b8b3550b2ae012db659b11c4c2e25a4d7ce633a5856ab33ee89edc6647a293c8fd16ee3751daa8a401b08817a230ccf12b289252b7e73d9a48a041768b14d03d2b74c0f6dc233874ecd458014739a4ae0ce5d583a1c22dc5de97fdee09abfda3ee4c4bb0cf5e1065ed404e5f4fcef6be752d5f351a870cdebcb2902aafbffdcc43a2f410c59a92523cd6bbcf5c770eeefde1b781868fb43029a73b47c899246ad2d4822139a5b246cac6f5bd3256ebe9815905c2aa71d50c6ffc877ca560b906f7b1a3ef69957b38b936d47ed986bf65b221993e2e7c73ec8382bf7f82eb7bd2006a65c695d68a1a9d95357d58f1710c5495656fb4b165eaba6171f866eb193c6e73bdf697c9913960207a76d9e21a439b7e7eccada8d8880038f004f10fb95b324e03302675d6eae49e09c746ec8bbb4d9f9e2e42b9cbc7582ed5ce3b34722d4db1746830a2b18abef9c9c6b674c6d693c3a5bff6ec918b99ce7a2f5455d19b40ecaf55253012cb9bb5a835362681d7a3f7137bf13552539ba67e64ed0f585a2c2d39751d63f8513cbb17b8c3dba7e4b093903efc8d893f5a7344d447a7fa8f9aebf9a42e6cfba70b148c638effd6dbc714656aa9bf8768c1a373e27b23a15147efbc4b6a72c376bb6cff5ac958e1f3832400123cc6ef841a8077d49ae9a9ec6c49b21148f18e1777406d7ba6204f2fc5c474bc35e338e05b28ffc5574c62015f1eef287f57ae718a8858184ad6da9ed24ff6b4abc22ea1cee87dad2af8916d7e4ceb25906f092996ad46c8c9b1f27392e061ebc5497260c719a9701fcaef06d0d403688141971cab403e4188c638e3c1c1b36409f5f50e6bdcfe08c394a925c068f51190cbe834e7b34e4580dd03d756b68e3b50fe812cba8b3cb8e092defedca76892d7259b826de3079f50d0de717548b83091dc304e5ca6e44c01cc25cf916762bc3163e8acd3c0ff2d1bb60861a3cf9bcbd811f3f4e54abf76de7a04d52cd6d06fd1c717b05edc4e038983f2fe228ac60a16a60cb4921d582b3c9a7a1bba6901d259bc07eca8d8118e85d7c545a0f86aa49c1f94a7ad937d1ada1876147d522b16743aa8828b0eebd7828ed99199a6ddd67e44f63a222ec2326fd95d031398e5d5254e810665c87fd1585e3f342ead1bc92072723fef21fd35e18f8837881fabf969ad2b791785f7ae112d39d53c89407ba7e29b52764acb5d2ac1c316ea975e31ce55d39957a48054350dbca6fb500f4725cad994e1060a109fa7d0f71fb2c1fc1696bc8327b7ccd265caf56674f06ef0a5cfe8af19d15da38581e2cc1418fd432ce8bb9c96708ef35eb9c1328ed7e8457c08572d06f2ab3332dc229e7b4e1e80d085b9d320dc01d6aa0e33288f6f295173460a1d12b4a08618c22ed4b80b9373e4b0352f4710629b9d5712d93fc18617c53bb90b2fda2b00864e9552ba95b272393ebfab3b0d71236ad1588d1af072eaf8b16f49d0eefea6f50d5c62c867313c34107e6abad8f4d86142b45530920a480bd30b809833123930be7c6c99b8ae61c2d4ab2f6a31358eb1a909eead778a4b814ec299b3972050aa75618432782940648814315e657f692d92ed6f2a28d6ad49f69bf716202896d2f56cc25e0485884df69a3493f0a4e1fd142bd52937f2558d49f2f7219aee0942f0726ec7210a58deabb3fe81897aca3fa1414a7d948a79d145f5cc5846785300533cfaf6bee6e0f69be910212b5971e96de3506b286b4d0dc12aa3336692284e4405ccd0a5a7a2e7fc58b8ef7c9fc9861376b2c95e08469581e7173c3d4ac1a752ffd84787c23200c2346ce4b22ca99e2b954273bff6e7fd42d9fff222745cf181ce0f5ebc1095fe6f417c58a0e614d3fb9071c355702858d97e7d38b826d4ff4a0b93b6ae4c24e1f1590f4b42369d0e23e8ae0174d35d69aeaa6730303d42ef56c2c3441a960add81ea7195768aca50f7d59be65223c0c0e60600dde1d7b4f9bbf41be240f3497bb075b4eaeed199ebc2c00da5288579cc968cf9e904d7f2bfc516d9d4b7ca541e12109abcaf02094458c9e02096c34650920dcf6191655036df917691ae68367221e5d65f9495b36783429a3db4cba7df8aab6947b76b93a70840f8406d7d4b8973b19e0963087f09b931db6e9e30a03d645f174e4819e87f60f404d6509d8aa770468380f45b57b702d2a2f412a807c4df74d129bb61e5d1a9f3d0bab9c15cfe6209a39ad494fb5a9013616da48c6a34020ad331257ba1c76775d86139c305dbd27427e3893dda578717d9a7035b7a94e1947f4fa463d67119266a43cae59c09d695130164e1fdd79fb4d3de1b370ef8da5a9973385616a75da70112054b956e2e8e62b94992091ae1f4a1c81666b35a8af8c70bf95910e54a0d722f157cd618d21376a6806645d255986f24e1d2cdea40dd6e32e8b2e4651360e0aca5e5644ebdefd4fed1f5baf0e19065af738a486820cdd392626d89db211e8e5f1f569a55bd7f3905c1dd615e7b849cabf73fd6787460953b50d83e8337b573d45782eefc3835f0fbc03e5c83e74edf62d9c8c1135d8071d9c7071483b80cab2c40844f23da1686b5793f43a8c77e3d4ffe9e4e675fef5b14cb517bc33ffba92c3a550ac3613dc71c338783842ed56dd39f60a9d9fcbfe40820d0eeb6c7ce4c0879574341de560d4e71c4c98b31ca00adbe51680702d8ee1711e5a4c131c6b0b53acdfe03f223de1ba4287fcce808638abed59be1d975b68609b35433ba98ded0b560b990d6fbd9188ee6557118eeaf6e7cf76e4dd3a727a99a9f0a05f3ec445a4f9c90b54e46fa80b0bae5374f0e54cd1de657e7f1d1849072d60ccd33df02da0a70fb7ec802209c9a5e586e9dae7f87e974d8cb942a5d7832c7cf9a098102b22eb212fc8541f2737416264a284a6935b796ce87f83ed36be0a5faac1558e3a8c5996913c2a90cf77d25e2a9c5106065e4e654925b6d33dde4e67b33f6dcb4f7fd4176ea6cf27de3845617947c62aeba4a2433f2566bda2922c6ad8f48f88064652a8aa1496c9612857434053f584c1665b1def0cebe6b44b9c58ac49090a633a9b95a66ed760600c0d188de713b6233ad120bfdb94458ff7b25f208d6f503facfb6191f5e084da699c35008e00b9b613e73ca1edeaab4b1e26dfcad1072f199dacd1d30d52a760a7222d21886fa4647f5213e9f8ff55f3098e95fcb19c30bf5580d1b1e797a291184cc88123cef4dd5970fd7c56dfd79555427e563a999ce1cc66ceb0eb7ab08778b22630ba304f2d60b5226cd14ed2990ca7bebf31317ff0b6f40e76a43005e0695880ecfe40962c745a53d71bec5b436e77cfd9be458e6323ec7cd20afd51a64e5e853cec3fe1bd764e69fb9d4d8e3947385755925e75b6005a03d4eed17260e543084ed319d1f4b6497c1e08c4ebbf07d3532ff2244a0d1c650bd4e1bc6fa64d1cff40325cdf09fe55e197bbaa380ff8fef5ae0be6607730dbf53735b7a96da0f3c8c1ea8306a1ada85c03f2d261185d35b345fec34739b9bb8b0e498dcb7a0716f5ed738965fb03d7aa314167b8817c2e304d2cc0b9ecc966ce57199286d706d66595458881073a8504240221a3741e2370970f83b3542def0ac4dd8b1f24496a201bfd7be6ae5363d01e335cde87a61c97838e78ac35969ba44fc425de12353a73e47d0a91ce82c1a091af1367bb80f529a0519af6b1d6228d5f31725ad4c1d7f370367ea7725c2e6b76d7547c4815e4be83d90d8727b2ff969b1d4977369cc383f3e75d92327ba43a16d33c8c8d18a7545d56e259bf4c9d17a4457541a9b1161d108eeff9f7fc5f81e9ce2d4423606524190092a4ecb84ba03931592bd7ac3919e1b629c96b12c6d710b285c78e988a96cbb917197fdec9e2dd8929a6e6a06e1a85f5322ffbef032704edaa261e0f306bec889f6ee937ece861f0a4b42cfad6d70ad21b7108a6078ca9e43c01f074f37068595a9d30eb87ecbbe9a96af0e6cd62f953ab704721d96d75a3d1fc325bdd074d7daef35fd82463e1022003e6988c22842de30a9d5ac85f73c8200622734a82c3d1422b20500a290ef5323840cdb5b00cb3b65c365ff6583ee1ef77ebd255ae872aa0c9bec83735bbb912c76f6c0cc2b81df4c4271caf64dd87a6abf9909c5aefea670d460fa7d27dd755f048492c86c116e1041952a20b4520d16f2b7e78c978b15817d99f2e7051a088f09c5e8035e876f8fd97aca6b3d97758b657296979f4976696b0e51beb82d15a689efa6b60f867ddcc68cb387cc1556ab8e5a5d908dc337c473d500716b723223408c4e1fb44780bed7ce01c65f0f062fff0f1367e6dcca22876e0656fb968c977e369eff5b36c6303141ef9d6198e622ac8f99cff1e9c7889b080524d0a5be275b09dc1ac32faadf60790e278964ea6384b5d1bece13c8ef66899e7744de940d2f1476ba2cc62caf3f5b17a24afea92857eabbf1977a0ac4e9e115509e22cd176570d42d50105d37a41b5cefd74b30760a38816698c7b867c979429297fc9c2698d2514255daad40e5f6d716b18385aa213e8ddf8f21a3ac7abb3286767a03b101b150d0e0495c492c45ccc0c4f2e0f393c0b66607b5975d3c0bd358b666b33f38594c303147dd0698d60bbb203654d5f2cab1c4b5420eb5e1b28d973f9cb0f03d8f96be4a3ddfdd2ec1677a32cab4a7e0a1ab08f465bce66c8c1fb9307991f5de8f2b5b3403ce67a10a2c9397118316d7fcfd064285cb947975cc9536b2b3146090a7ee4300eff5dcd73d3f7e3590a03d425cd5da9ed55977d5b5e774089a257d15f2de7add1b096e0c38dd95812e1ad0bfba505963823fb4c054cffea31697b7bba49af255dad8b283d66f8f1dca2b551975966c509a4937820052df14dcecf44a8c1159f80fcfdd4d1b1b2e46e2cea467e68b9cbda9ea6a96f9d8b13c8d386d2fe43a7debd80bc89adcabe7677dd4fec1ba551e0dda8b9760a8accaa26aa6e9b1158eeb0719df933cd50d15bd5c60b12bbfdfb8727b0a2e259e7feff449f35289d5899ad836521fece4d5b25166690f20da59b902d636e2126ee69fb69af18f9f732ca754109e46b9e8b6b18ef63bb73624b6f7fa3c549564a3d7ed60d906b423127e2164a1b2f650770dd7ae0cf6289acd2ceb770b9d10e6f030e674f8aa7d434206e98104b10f03d8562dbf8f0dad4a9ab8ce0726bd8f6535308942f5ec24263653990c6e7117b2beb4040d3cc98a674497d5cdd8561bdc17fc526604ee79025175861883fdbb16b26d832323c48ef800ce6f276e7bb356cd1c543f8065536a5f81c9d4f4142601856cc6ba0a6ee1f1b8a49e05a6354aea11fbd9df091a4d252b105dd599a71b82f6be913a618c2d6c4522596c4a028e3c7d6aa73a1b6bd6346fc049d4565ff225601591b2f0b3164b239a00ca7951846b221dbd8e9fca90db69b2e2bb081452ab5448286af3d1ffcb9f1de518e407f45dda2cb253053dfec16b0a120c4a57c7df34d1d369afa521b77403dafc23fc9fc6ae949c28a3dd7f92cc73b5ec5de9258c0b4d19a04842f12c2867da4e917f51649bb489873757a4a69e980a7fc342bb9a740008840b90d64949a9bdc866ceb24842d0863fc16ec69f54ac2b40f40b51e1ee4f49570ccb321ad0eafbc2d89ff92f3f9239f7ec76fb044de39b8e98a4ff38bb43c1699902b7987c938236c79a61337d84083adadb5140e1f7e3a40d9fd0cd560dbf00a9990ab10dce987409dd76605196d3d108985f9526a43e9b704331d0b17cbdc31dc8c1808e0bd446ee7730ff239e3c6fd5c020eb082b5542820a35f27e88b3d07619a450c57d1c696fedda9021d37fe20af92583c8d30be99f23a85a5bcbf4d942ee4200059bc18fc3b7d2d1c352dc66b3fc12f82c635e5fbdf0f1f71903a2ef77f8dfdd3e9f9e6b6ec40e48029a8cc283176d1df1aa4cc8c489c75c3092a9d6a3c1a324380a4f5eaaf27a28a10f4f3df0abb99a04f8d60c0138ae901efe570fc324249b331e73d00b97229d647a7eaf39adec5b41a20a0240fb4d7eae7178ed6ecf3a0c3563045c8afb2ab6e587ba2b872927311c811e94290694e912390564fdc82b944122ba3cb0db9d8f654520056fbbc537a9739f88e13ea517414968661fe51bfca4ed7db88ce8037642aa12342814bfe605d9aca969bea0925bb74b018552d79d5115f0712a46cf13a20d935bfb7b52d8db18456c625632b3a73cfa8c100fe889e532fcd6b60d96f17aaa29f1704906ab3818a263b807f5d952b74f9b2c70fce9bca07d1d3ccc1cd366225e49286cf69368c8a5138588a5823faa3983ea9b9dd9befcf37fcb0131bb8ef8bf8a39e5727cda9856f4e9c4249b2dbbc7aba90d362f3faa47ac39a253a090157fc9824aeec455fd6bb8f45a4ac8d435ccf8291668ef64529e961b97331c764494cd46443c07f3f0ea28ac1c8782f2850cfb1558b464025f3f22863df89fb2b77945121220725a6fd2273d905734e1bd01c6434c7015b17f88522fe45bf7a6807367f2052b98c3fbd7abdf7286c40814298d25bc0c125126f2f5f352962457196843a602ebcb131934c773303f365c63e46a254aac55c00a24fd703f17dfd6dd48479a03696341d17e0b076df04823a9eaf2f1e80933bca819f053d7103f708c0ab82b96f77cd3329cdc6a7efe4182894872135eaf0764be5633423bd25fafcb2d21e85c511fefccb884c4f0454a8dc7f0fe7bc87bafc1cf2b0fbb00f2024da1d7fc3a8b07cfe334561b88006e0938cfdcb826157a7211a20aab24ae487e69fc7916426d40f3928367919f210b691d2ebdde4489076740c3ea6e4842bb7ea82a0c6a4fdfc5a35ed87d74977a91a9ee163a98e93b4a823d01be7cdfacd5698560d381f5d363ba85d1043b8b65173354dfc3979f98f7d046eef43374523568a737bb9459ce9641f4168f2b33f2e7cb2d80f3f5d5de6a92516313f1c98bc36213293ee7ec6a8a9a98cf3231e661e51e6b1a7be89ba20b3009f9547b3025c07cd918de309dd567f43e28f62e9aacbbbb286ea636b54b044ba69061d2a4e09bd442c82c080251f4eecdd44ba4edfbe2fa58616b8dab94485a0ccc114e65f1a295936064970bb27f8280879fb40d2d726f037f0a15230eec9231934342ab8077a42cee8f1b3de32287627bf2e3db56c1d010763fea629bc814d290be98902a5c7037e043f08b614e75e6ec871a0f3bdc85ac96015db4f47495d4b32813c47f4eb30d8be62992bea839e49ca1c12bd8c43c8058d416f6258b0394af0a279385f12a3ea78800e2203d36b2504c0b17da853771d4a78a820c3fdc767be4a5fc33489466d04d8dedbff9ca52503cb7b92615391249495053dd442ddad251a3f3f73f741a07659c246a7effe1ebb9e69e50063cb099634d1da0077d5bb33e4bb6310b77b5309195a0033b2888f19dd1bb250d5dbbca0fcefbb22f13fae12b9a20b64b70479241bdf4fedda037375e6e952150a35086f5ee2240795b5d6383292f4d0d46bf5a3a507fe612f664172a5fc9f98a90d49bfbfafbcfda5ccb05fd817a3590501918cd3be6d99018baed16b6971301a3e17d889e00b7fb7effd612a64f9de3f89c3fa174400779737e9b5c626b09403349bf5212a4a3fa171fa985c62e2b50a024a90c2ecb3605000f05a258872ab257889ec19f9b12847c7e94f152e576921a0212fc73f04186b04a9659c8eef900b8dd76dd20e2d9388cc67372d82c8ce54451986be9d294b2cc48ba2df056ac477a204ad05de49f64f44f665860b46ddd8371426fad88b2441908627d6371ae15b49340f6695e6004a3364eec40be8d1132ee75cba8adb0e9e40683e8d2251d410935237e6294eb1c24bd5444920856974955801dc6fbb77f54dd97eca9624d896270e09930bb6461e3146328b98aa44cd25a1294d62585a488041a9113044309e6b5093f3198c2298c85a906baae47f2de2ebe372548ec9376ee0e2438f13a7a9b2a5577e49852b9013b1af845118416231b7ec3fadf52568e737806c6f72246668f1e4106e632d5a9b6edd4b37f95cf6b542d0d9461303f8864e088171e1c6a3f85e1ffb23fd7aa03fe1d5957df2446856b0cfe4c3294bbf3bfd5d7871cff28d988c17c79c28806c525e958e8e5d40f57f03e1b02039e7061a467f8c86ed68d165b05777c0fe0d9f7c55405ec0eb85941a94b9eea4406aa4299abd44cf660be000f96c405b45012d238a5405206dae20438a11d6db99518d8313b5813778f1d24a182022d179abf4143e802b9d4b6c6bf240ec5b0eba7e0c95eb39c6f50093e7fcf7cd573a6578a67eb6cce0776bc17c90e471eedb8b0f842fc352696b3aaa46de459ba7974727a79ce44345527d14975326569baeab3d7a94be62f0642005b384a946629ffab0dc83cc66848a5ce6c0d8a6385e44ccc7350116f08206ece513608cbb8015d169edfbf0ee868c80037838fa1688296a890b5ff7c7e7a308993a6bd3f395ca3991800747c2a0e69a6dfab0b642954f4085e31f3405dc9a4de46ae49582ad3f1710365cb3269e13877c90b6f5396a90042662742f0f866e92a8124131f292931add417e79772234c19fd7ce7fc5e537bc0cc170f43cbd22c00e915cec35d543eb29846aed68e2861ab733ac54dcc60227edc291bf91fb72ca7282ab0153c8fecb5e858326074bc903c311ac91b09aca290e6091151d1478a9a3055400bd4900344313dbb664c60f6512f00a59efa76b59a7034a190140c40064a7e5140b24e21be48a80c5fc7940b6317d44ef3a89a2656b30812815665354ff906437250ea30d65541128841a2e55fcd83c599370bad12693c17f02765648bb05b550316acfa1406f4255ff7904124cadc8ef46db2352fec8add2e3ac4460552f8bb79e82d2b5414e390284a434e08464684cecfe27ea3a5b938906b0c47bc828a19ff4b1712441fb5bc6814e4705433a67bd145e5a878cb4bcbf0e347c3e1cc1b3ee2130d43eacf35c86c0e1bd4c308fbf80e5663ae4b6f7fd02a41c7c65d926d5214a3b6351d5085d7c342b96e075c745c79ecaf8dcddc6deaa0d3d00048cff016abbcfafb07115085c092cf52444c14f06ab3304a464869f7b7ea4aa673ac038063e4d527a9e1c84ff94a618293bc23cf1dfc69d30dcdf17a5831b3dcdfe15ec4184f6c0457555fb9cbc7243b815e03e133376f4d11bcdb97701af50444e9dce54e633958387a6c1ad32904a78d8c23082ffdb92c533a81f2705df1bc8b0005d8a9c97e00c7df4fce761a5023adf42c35faa6a915692f1913cd82c8b654fc03ab750d55d855edb67b5aa0d6dcc912a44560762d76fd838d0981803a9571eafc8c1fcdff87848808b5eb0350533f7b9174914e5b5e4cb06a258866add46984db06b97a670bcd0b2f13d7c3d3725ef5f8aaa1fcf432a89ac28d641bad68f67112957aacdaf8fb8c3a3a92994756eb65292d07101e73f7c51c669a28f61d875bef9f3cfd78d3ae7395ecd38dfa719f892ff92243c4f031d0cdf633975d01bfcc6ee2fd7ce9feda5fc56e151dded9a67ff5a9af737c89e54050003cdc2704f4e6b9815eb13e3c4560f202176c0f932124d9d9542bd6a3b5a778fa46d5a0d6b1e3b77227262af20c79ae305c8ca264a53ac3ce19b86773d50a9f81e6d4067a34675d4ec18de1e41e7ab7e2acd0e167ba6809b1ead4144c90a5179d36bf1a5bf1a2a360ba71576cbf25ad21913173ac5e00a5ad16e2511bc3f694db5336e7de0d4851914573a9a907a7f557dbc115a17f9a882bd3ad6574601f4d7bddd6902be349ffc0b71be6b661584a981d33e99584811eabb7cceb7aed90fcf47a84419f837003580b3d8c4e0cdaa6552d6a7199c5eb39fe4082195d020c204a52c680d72ebb2a105fe265f29d789458a823c8250e0605464e3987fdcfeeff6794338b63db926d7575d0a02b7be30b836ba8ae8f21ceb58ba48c80e293d366caf0ef7a3cb5cc073533964618623367211b9271ed30bd13d86adf1c7f267f1d4a1c3853e69b7c9e15232d761ce1401e1c9f9fd03767421865ae85a270701f86ac50fe40ad9f975d5ef4290138dc4a475ba0995cbb600d34e4de7cf658efd9f01ac996a2ca88663786f77c4e330aed15121047fd8cac2803db4fb41a9536aa83214974a85d50c8af3fa2c2a4907866d275a071919851e479c7bae5fc2c6890cf3695f4a00ba644dd5811243ef10430039948fada0c4790d413b9fd1700c10f6d16f63c41cc6cee897280c7c767e3e2c2c0c3fd285cfa06d4e14f14aa600dfd396ab9267dde3a169e44d2d02e041b6f7c8f100972c5cb07115c5df580052f9d1257af3bc022d9cc3e010df2dcbe09a287cdf25dd05d8f61708b2e399f789c132083aec2991fca354a6bdc8bc6cbddfc5f6d714792de0eed01a6ecec3befdd2016150ddc2dc6bda91b600c5eec471adb6fa6577e50108557d46b28cbdec51131fafc2caefda31aa9c1e70fc8322617e9a9a6b49dbc8725bd876d56049bb168ba4d90eaa1d88f7b9e0f5a7c99b03a80ea6a685ba7198d9213a9074e97aa15a9ebb5242daf0808307135ca8c72d745d5d05daf6398907364642075cd39ecd2adaa7add5eb90b68b9e7c6b59fb417ebe9215c0ca60dd359a9d73b9af2d00555e1e27651eafb6bd450da5ce7b502e29064ae840d45f8bd49618e2111c1792d97c8235408c286893b5d353a98f65128424298026d2262eef0014db28fa321db58778f2b0fdcca6612ffcb9e1f2b8ac0cb52a93e2664427ab8f1f526f6e4aee4c01819e60daf36ca848afab1e942b64a7ab8f974d6f7613fd60ed4f23434b9c63b6713f657bbe246a803192f03cdc47c0f6ce6e8b209507a0e28b5b4bf27d21b78380f446cff4dfee1a6bd3d4283d8f5bfb95731ac8e66974085d44072b55d8402ef096939812a2d1761cf1ff8be28fb4cbe57570bbdde6f865c950da4bafa802806ec8f44ca39d57056689f22fc71c097f0f8c0f292f564a15b0d8da5777b01788b579882673916735d8866475ff61d5e968c22b24a200bad225f3d0a0f6eaa679fc6544c69df07e97f97aa7e2d0ed62a4c29f0c9e9505793c6e479f10b351372f8f9fd57e6e626ae7cb8d5b55048cce5818109d0b74a808a4f2a57f7d7af7e345fb7350b762ca7ae46eb17ded61ad0daf9099a24be0606f93ab813964273d0ef33d832cab6da3c9084ff098e7223dd7f4b70678b4e289178f7ade7870292c020e5a7f021c8a62e551057cc98f9f7f2bd6361084b7a819c546c2c3d282ef58fc27a927bccd11cc3f379784b40e786e60102a0bd6899a71ace70909beac244bf350c23b5977afc5a4c827194f3d807cfe4396be7f4c59789a1517dcfbc1e608ab24952292870a5ca20a10963fd0f7cf0cdf23451215f6b26c94fb7199187662aa5f224cf6ae593c4af81c45322f2be458a67a8cd07891c33391ece6e5ca54f20505e9ace490ced665f80b463f18b260c78a900dd9caa5a80606897118c04140a97f4eaa5e1a01b3e72321da87409b4f28e1aaf5747ee6ead8b497912559365c4e2b41648055e7c4e1adce567033dd4ce3ff35157fbc430d0e5d1847d4727f8daa5afc30e59861af75dfadb00321eb0f8e0bffad7835e0a77b58660413ef95989268a6c99ad5e7cbf2ab9c5423f450db11edec4cbdb340d37a1df02191c245cced900dff38caf7f62a27d442aea71be32b103bc9a148116383bca3c02c5280b54c95d0a9e1d4646a8265d87254e05a4516cb712494dc50371cf26140b6b6a372e34031e4f987432e1e8a2cd4c3f853cc996d3a8bdaa41f785744aee7f94b18db1d8e9f78d8e8ff313ca4cb938a31dc59c590ae2e2167eac7aab7d8ac438ab3fe51b17e378296fa230298d71606e1aea2a025dcc9bc9c6dcaa6e7bcc61f0663c51912ba65659f1eddad44b9529416e07d7c3a496c743a9b3ba6cf656127cd7f82a3cd7e1caa778579db6a2fd131b30f969a00c57cd717a53715013f6d7d556c8224160bfc7ff677f97f56050ab3d1a7c8c2094216a20d069af62e9d9639620c553a621ef9647a881f93e88622824091efd8f8f6ebbcb06d4f38706a15bb8d448182d06f7931a2862de9ad06aec6781be638325d1bf15ba532097e93245aaf7f4c44711d17149588957b422044085279fe487d1fdc7de5153704b896a232f704b8cabd9d92509ae50bbc31e0000b74504d5fee420920f72bad39fcc900dc50cd70c05060fd2e1229f885a9709228b49653352018509a287d21f3611a74b28fbe34be4d69d4880b195712812173b65462dc5e09f89e45a3f3bc888a781c11051c81e0b9cc10286de4b5f714aad71df710bfc3a8ae99af30fef110a53b2b27686bc44e73bf2a4542c9c8054aef185dda5d3048c63516e29ac57f2ffce422983f743b327af626a9ba500b634e18cd930e0eea1541b467f4392720ce0453f89cf6225d70a84cf111e567fec8a3a5da1dfc45bb3ef7c5b282780057e2bd7101bbf6ef3cb395d9d25c6d0a6392b42f9aa7c173f8eb2a0ba19fffae26e4e1ba8968c2125ccc26f47b990a99aca8d38026080430486dd77f8eecec63e94d39777f91c68be5b0b031ef653f92ff8fc9046f13ed60d49fb463c390a6f121a504a3e31c89288ef60f75411a9de1912a26fb4d3ea03f6f2bf23b6376bc591a4c823a2650fd5da12253e628580f825b34a311cf90d205ff6f27a65b8d25baf5bf4879aeaa7c8f24d705baca807ed88b068e4c26e07265440718b2fdbef08b132afcfaa2559e7dc4e6dea88fb8fe63af63ecac44b66343937dfe945b1cb67d4dd20c7ecefea13ed169c188bc7191064e265db47196e0c4c7734fcdcf2195703289a31eb3ec90fc72ea32622eafc1dd9f1d1407e1eac95b46ee58032455d7b989aee404c1e1104f72253e987e4ebc0a7cf4fd9783a4857253b1104e4ca82ba33a91c1d4c4472fe19cb064dff8a3635b319f9b8c935a7020c6bc46c95f895c09ed36a6607cbaf6c961d99cd62edc2b6606c7da4f6b507e751841370e7e7927cc16fbcaa95d891a4e491bffd0253ac0754873b97bb3cfe7dac728a1bbea22aa0a60dc5eef33fc95c6753b601d563d9eb3b9d0e411ddd17de7d3f304cd720822490a7ed2bbf649d32d4c6d113f1d823acae2064824b90764745b52838b1f191c41b1033cdbeeb3b262c24b700614edd9716a0a6f45354ed9b9fd0cfe6c0ccd17d775001363f02291b4427b4ab3e10967dae824a5ea47efde1d72005b1f8814814a6e813393c469e59dd18ec8922fd0b348eb8d1425dec19c2077a3fca9e04ee8fd323af9f5b999d0e4eb2d624e2d71014c7afeb5074fabf0cc89aebb4738a8ab9b9f21aa5b7681cc8b99ddb999ea2087b90206ff1a66f493171a78ac628f9e2a214fe237c7933f3fcd994ab1923dd594984824bbd3cc2d5c8eb9cffad261c101d27e38bc8435a2c835660566b93c0ca65eb3cb8059a634c0b111d7de7a35c53414fccbd226d81e418ae3903696ad24f361ae4d74aa2fb44589b368f480bf698e9aad7d0960a102edbae519f7ec868412add15bec3eabd95b1a60acf79b2e01c34ccfddd25ca9550d17e7dcc2f12fa4632f5c646dcbc42633bc171e874e0ec54057e6af3449d214c61344cd88fde518844a41376b1e88dd0c38b02bc0a24c8578498797b5c33d3bfd8fe068ac93505ded849e52797672dd934ea1b925dfae6759be5c9c148443132e6de5d24991fc0cb6c0fffae94ce997222c4d48566c58c9e7915185ddaaa0e411fefc20dd381460800a543a3e29042cd4255f38870126c7813c3d3a8564f086e7c3cb630db4e42389b0d46877012f070ef0d4ea49b3099ca9e88a025f4e1e513d72e844b2c04394ac47b771d618a65fba70338625a17c9ef1624358c6265679072813815d2784293093e843d8ab56c7b8cf1daa2f59c1693bbab4a9a418949d571cbc753effb5a657cbbb1baf858b5bde4c8a07408fdc7e28fd28d25a3d48a625b98b1bd39c36a5930804f74689432649ad10eed02afcebad93cc4cc94a03b239d109dc77aa95cf0c0598df9ce04e04bba5ab468a78cfb05a4e497b38ee262c50732b24c0f3dde7fc9834842cfb61898f8fa0e0b8f4821ba418eba1102c7c1ec4c98b1566a1ea3986ce4a1de46e9e9b52a6186d1a4349f60c14c5302d61b800f87d50c2991ae4535cd586fa7c29f0bcdcab4e10ea8580e127b16e8ece28a0be3b15a81189c0d1f0a9611da690705695f7586d8aeadd80a298c69351572a4b3fe2cf0f7a63a69b03f74ffbb40899d624b6e50a1339bab53b55200a65bc7b9ccc03bbeaad12d9b77ba994f8e4ef44934001b879ff9ada1bae5f0955cfc9687639287e5d37dc91749d439f8fa52a6192977873993eff4ba9e34ceba012787b0cb5c9ca76676792c89140d55632733ed866fdf41ec9bd3cdea1f16b9c79db7079363691f957b3a8ede05d3d3ed85e1e85b7ef50523fa817dd29a3889562636aba3bcc489247b1f3838c1cbb0ec9e29b797bec0c738758a80c73466d6917b8aee9e4fe41dd226cb9940273cc3afe83c9b1a51da85c1064e37612619136b9b2b15bb9ce5754ac4fa15980aed5913467c53a467016c194c819230128a9d31c005ddad8a47877540f8d070beac275e18fecbcc4896d3a735a949ba4c3af41211f9d86d226f2b194628cf6596cb3f40110fa1709621d451cea36111ec6bfabc195b9bd46e253814567e63a7e59d2205a2ac713b23cb637e7b1f03ccb4a44c546575b06bad1528b4a505eed6f4fed94ab508023acfe2074e001fa14c3730a5302d0369ee9c4e53e7d55bea2b37c925d0f80a2e9e365815e8807c6f9915a05011e8b6744afdc6b74d6c304ebcc05fb82b5dccab1e794581ffbc61293c7745b2bbaab14c2944d3516a55cb3702a126837e991d9e9c036141f1bace27e425b7d835b70f6e4c20b304779d85ecf84d0813737100f7f8108b3c1e2716c8c4aa50220f4c5b6446a505139dfb4b276bbdf0fc1b342baa438a44cb62bd6122ea09b2f168c116a45ae04e2794c120b74c23515a836a89afae0934c8cd2c2b10fe653d72b14534358e2676ece93f996d398d9e55fdf4b9d5ee06a430f907c38237ec996c42189f2d33a3483674b55f5d73bcd3c68fe68c64f49e0355a67ecd296ee5332b56f4b3fb788df5b9af6eb068fab0ebdf00468a8f25fc62997fcfc1698caeb76e047bc080cbf292560dd0df1f975167e95e7c6eb8841ff7c5219b69a2d9a79b7239cf8cfb0a3395ed6fa4d0eff27dcd0e13a1a6816cae9ee7ce8367c5d6df9501d942cec2e6225ccb5c2f7da017a1273bd103d4b0cb7353c06ccfad3ca75b1f9a7fe8b4cfe447a69fa0307323f08b8005f74c4fe10a77b9c7b01302214064faa293195e18e0c817066b3d55b2a4f86bad2b4da30606e696bb9b4e84c0d72ca7af988460ba077db99e2291583dec987c27531d0cbfc39255c0a62f21754f7cf0d4f62344f5a8fa76b673bec629adfacc392680ff75d470821144c01ccb91c05d0da89106f54da69f7f5b777b6087df79a8018fa25683515242950c8ff71e18c0ad7d88966cfb8968da31f96bac21dd2372be9b4f0e4319180e79e7e39e416baae9ef075900972b69adf3871a69543d711ca5d9155f00ccd1b8526759eba5a3f367a97ba980c0b01ece4fca22818b1e8a10cecf3782441999ff2c4384f34c6e36e7407c50f4a75d687938c6b1bcf9c59d0781cfab5a518434991acacd7016b952f2d23a1173aae30b5cfbb3086077ee598765a99b77a1df8646f6c8eec55a4aeaa46d6d20d0bf032e2b22dcfd1926a0756e96a4e8e675961b73e45477554f66e67f809ea41403c0decc9d3a7a4234c62991ea4aa2d75b2aa065381b4d282f73f04dc2153ccadfb68a10feaffc685949b36504f6910cd45d67009f8926e461f2f8173f2d7715ae4988f7b71f1433e6eed4c0fa28af1e86c0e811b1db0e121bf2b12980c95aa0c7df8f8dbcf8e98a819d50a2acaaac8fa9b43a354fdde4b42b50112600d8df545fee3b51e24bb51dedf17211120569ac8e909e4269fbdd2f00c4d52b5d2b70e7a56c8d2ce3c1442a6b3f85091c867d3f7132f54f130f017d04abfa09fbc87031050368fdf078789f9f305c76f51eb313ff3745410a29a9b3e8ff458407c0b25b7c9315ae2210c78a063fcb40b7868a463c0e38e9098608df7fbabda712ee00d966b0c0dd3bedbbd659202b6be1d5b46aaf399cb996be9e2433132b2fada995303836f81891177159801ada3d2b68977aff89e4031d37c63a43caa1f7be27a398f68333a20ad91b33e0e7b9837c32cf9a6d67a5c987cf2a2f52a0f0848794cf607ebd48ca87b47e7c2e77acd621e3aaacd40b1a1db1d3497defa230967a45b02cd819f3d824277445165b1efc8d12aab01844a1114e38f87420cd3cca6a1d53fe2b7f548525186ce3502f7a513a8307f92f4a3c3c3842def01ec13354b438ba101b9570fe85d1e58f0992b34878ef2b2c306311c436d0f65cc80df9bf593b58828a05af8374d9f656a691bdf4452c34170930ea02a9a1110d7787b3c6442796cf28db5c8d350bc955509811a677787550d70ab6b5b41e51e33fb94f331d75521d38cc288534dc1c49d5ef2d97c8a7f5a9f85d06bdd958b046ce58fad667465b5367c7a80e43d0bf6bbcf112a9a479ee734c9d916d0d35776958f4a78ddfebf606c447d2df48be18a5a8d8c42fbdd34f8f496db070bff2c7df65c78f523aa75c350fcc8429e6aca20762f1cecebac83b50d48ee2346a8d138662c0ab64cc8339cefe4cfd8fcabc699f209bbb659e4b8830fe594aee90c8a4b737463c7bbb5d2f0e36b562b3c2cc8f90f21f9e0ca330415219ed2c274d1014d5653a440ab4b351a9fe2c3b48be41cb8557ef8b20b3a424d2c9b874e0cdbad31a58090a9752aba8c50c83f3bf50a3e56e6b2e1b60c29425eafebc2e426aa1a6c7ec579a1cb3c4b2c5d71a5b816e8b3b8f3ae2ec179f96e9e3fd9d0fdc297a22936b024380d815dee2519027b168be40dc18321f5f0cc54cdbe8b3eb75e4d4357464ae1716de8a119808b06f8a691ef7ab1661d96337a9dc506b5a3c705b219eb730266e35259424dcf7793047f3937cb3ec26c2c7d9bb5b8c7611a2b4eead7066101263ddf0df632c9aa4c99823f37b55f901890c9b4040cf37735d36cf2093fc1d68556921390f67ff7c7eb0bd5bb74029f55d10833e3757b29ed502d187436f921968550269dd75f40ff9d73c90855ee5e5759a8f209d30a77913e23ebd8f83131902135c0676c8853fc49971464e1160778af7be1905d9b4174587518adb7406d34eed3b70f59e66c7d0796b8211c3dbd158a621c0b5b1d8a73e512ca25eeb2096bcfd23cf6c24073af03db1a2dec3c43d2114c5722942bc84f39a814aa542b2377f3095db5f5963af5fc609e35c78b8db296989068f4a7867ff9b40490e205f740e59255546ced435367657c144d8d30b3b6534e64aa6b74720d9ee784e59492ba3569389102e7114f72b48dee33ce347224c534a70fe6327b7248f37ec2ade96bf488b079b98bf9505cadba99c7f4d4d31e2bd6d4c4c46e34c2f38bec3bec413a4d30c9d3dcdce60294f036a6468d474bc78ccabdc2b452508a8bc518402a7dbd974a50d3e25f65d0f7fdaef446c84ff9d0a934553fe2ac03aed062732cece56aae7f0805b5ecbc3c61a3e9edbbb6df1b068b32399dc1d10d34c0ac8a45ad14ca69911864535173044a9c03ad9a100cc5676984ac6593376e9d46bc00608c3491f2f13ad20d0d29fe0cf3f608531271bf6752cdfaa15caa838b4445b5874ad02ad17692305f977b0d63a9c503845765177655c8a491f935d9c2aa045eee14161ab32fb7480282ed4c7155651e0cc0fe1c86afac1572775c5980997fbeeed0124b983728f12609e09361410b57e6c10d85950d1fdc082b36dd2c42a00d59658ad38f7b769b0d477370c0a3b32a86aa2889f11fb2723fd025d94124202f2925db1d136d73d4269ef8214146c2d08ef0fee9377cca7517b76ae4d1c693beaf63cefef8db71725f9c3d780501f2cdb906e9134c5e73ed36d6d7f0e3f3787322c7d0840de5c97aa72915f6227e9ba7235bea9007448906213eec00e5c7cec98379dd538a4a22994c17cd5342358b0b43e95147f005a916d8b156027b99793bb4fc54c8d0ba3a69e89827a515909dc9a09b48db9c66b5596b8874456719a6f8ec3832cbc133c39f3b0f2ed7962b1dfb2bc193dbaee139c35f308d73e751732d930d02183c464afb4bae25451e31b269e66744c1569534bd38bab1da61bc37bb80acf0d657244d776f1a00ae0a463aa926c06b65efb8c86b11d3bfef641449e1a700f6529133e59c161728b143de587d9485be8af5ca1633fb1cfe2befcd8ad43523d5f5ba2619392a4256a3b47ae194f99652b2462c257a5aeb2cf30d8b0134aeb81d3ca95f556d97944e88a2c6bc6e87de0ec5f3bcf553da0fff4a1a81e4c9c840c2b08620a0dfc32ee379a7c157429470e3b04bf4b4016c5aae90ceb03fe778d7dc8ed0e4ab353eda485e5d5e404aa742ebe3ec744c17373d7088fb5172370012ea015dc7556a99c97a74e5e442d9a034572399eb3d24a4ba8ddeb37eb8aee1e8d825df5432cc1e532b796cbce683a0d0d3ca57a3242c638f32408cccd9cac6d0ec76a378f32cfc802e9e8fb92d261b530353a0c1443c64683af6f96f2de3b15629b22040b46fb1b2fae4e1e232bf66820352e1830b25b41d25aceb679737d83f1373027edf6e2d51c8a396b30657a6c23adc025f7064e28646978083f6e2b63da9f84ca3ffe1428fe0dfd6cb5a77cba7b67b13a9f0d97712ebfe27290a051e2e15fd17acd1a97695b1b52d8f0d22232b71ac6a77b6ee71bb5e927ca2d3acd481af50e3cd94862d1dee5d4f4b23ba615880b48e5d1b61f91c766853f3469d3de3421f6ad14bce300ed2b8495bf7ed7d8be28837fbd0fd7580750de61d7f46b23ba77884086bf7a5ed4c446a14604943f3f264a13f50fd89aa26c38ae09a9f152a2dc877851a3fc3f9df2612bb50955cc0a1d715a821da271ecad3db0764b953d0882fa43932c31e5c4dc021c61cea27eba1f5248bfa4e70cbd46bcb270bcd96df02fad5ec48f55426d38b968d1cd99e1010449362a020ea4c3e02600b4d8169f5e46afc653da9b9acb69f9dca4c86c6a155b3820e35cb13834ee12b321abf78333f7d2406bd4a5eadf599c7259bbe1260e3404f1e3d71f297bb9b916d47ffbdc8caa2fed0fd00d23bfd25e3a76c8d0bba4cc15df4570db92e09f7a972e74f7dea15d9feb108fe032fa735dd4d989a9b3ddc5dad825971b8545d75743cb3192e95511421076651ee2eb37a0782f33eb1c81cb4301fa154903653a77da679cc88dfb0afca760023b7cf7118c8fcc6dae7877de453c6a1486d742daa26f345d68e401bff34c90d5a14a026570da78dc1af9659b5acbfd262235b27ec8982560963486ba13cbfddb2cdfb1f0fb2be3c3ee0f24e8fef92f26252b1ed37a2f2c8a49ea03c68a143ebaac2e395bc0b0c46afa4ce46b33dabd2dcae6fdb76fa17429cedc9cc84c469f6f677bbeacb9c7f462a58c2a17fe103f8cfec59e1401d10fe343e5b3a890bc3199940fa4e8c16aef126776e9c9e7b25b9be5392e90ae85a72b1dc5d38350372f30e0c73d7019279085504b61d77d30317156792dcc976c20aba35c1c1dcd0e72ebed6dbd01e4a503bc80049ac0d052b81b5a03b91504a94b457031b04e0a78eeefcfe25ff5aac4317cadc192fff5473e9fb1f14d06ddf409e4f544dc9f4cd95f488d1925d2d93d22dca087802686bf42414c1bf73733518289f43af4dea02e9cb0a6e19c723fe11d99b84eb6e71f174dc1d4060c04b5464ea306d01cc3557e3f9d898192b9925c8e9f656110d4e2968a0122adeb3e3bb6bd1116a07e277d5da10c1b21a145e893de06ae0afdbef4bd4749c50b5d063f3994d8e43c9b667184469a3504fba93f8add2aac947cc0fe9e96bb7f877f035f12e2f88494beb94a7340822495ed71f6d9657a0c97b2bead4cb4d4c81e75f4ddadda85dca7c64569262dbe283fec9b52bd4b099195e2413256ffc04a6c30ed1fb2caa02991bbabefdac829f5182c48faa6d3f190eef116c2539128637910822fa08faa7adaf18845e343a9345f1a0e7c6a33187319c9ed1783cd9aa66034fa3b0433a33424b14f968bce9f03bf37cbc6f4dff037352eb2ed73761d5ac0b0ba16368b628c1b9946500e0da1717022c5ad94a980ede6f2d686c41c83f9fc1057b6a9030f10c13e4aa798652c0321d9a769d3e8eb3c888f2c27eef814b353ce16b4cfeef91fffbb21125f5a6d32ae804017dd3d90dda58525982182cce9bca64acf8503d36c902bd4ccc16170ebaf36d43679a4d21ece239ca83354d1b29efd7f6917e8f67f364b725481eef70dd3ba992b93de8160af8446a7993630594fb07b08ceec763340be34ed029a21ee03f92bbd5277eb9986e306eb7556c8dc6708e9d611585a3960aaa41fa5a257d768bec299c4e087d3dd9f2f4bb4053c6fe6b5d4221ae68394d3a8c13424309f832c239ecf126e70ab9a8aa3a736914af720fb5edd2d5e981cd845bdbc0e1030a4187358534d2ca8d35c56745e1100599087748b1e745f3dfb7288f10a4adc665e009fc17dc2db9e902c4542d6d7cd17083726b89f54c70932873c2db4a6bd8ecafa807e89e78b3b3c796d29f7f8f123615bb9103d68538b153cf94d16853cb4756d7de518d19b2ac567307ab02bc639526213467bf960d7cbb31d65d089d1aedee64a79ad3b21212d71bbd371eb9d10c7fdb0ed0015ec36cf113e52446228d3fbc8eaf13124f8450851af78b004213e5df66e2f142a30a072d1bad21999464cb5c31cfb70f0fcf6014446df6431bb90e7cdaad3273b8cda566c7c83bb7a08ee671fd7ae9a2733af5596938f8dc5b228ec31e5564b9e8c5bf4500d37d809a66f63d78d59c3badae85682b65baa6bb837263e5063b54d3c2617298c4fe40332616d84524562ddb574e95e1adf41f101b7048ff3f33fc81f385f70308196f544db3e171720d66d01e6081e1efb5a23429429245070e9817b59c05a5b7f695f46dc02639d10dcede160411bbdb804bb0fceeb12ae4254e4c778000b6f9b1cea0583da77ddb41a0b474f363a1390cf598db8d4087ec645df74133c19e64932ab96da5bcdec1398b7873e1b55d6975196642b259e78ef28095f90636965d773799eb70e3db00442d2386e3bfb1e88a21866aa4e7865b58b4f98dab951c25469518b3beecce2a9675f0067747f1acddfc660b2773cb4239e3766fd561137e6e4c960eeed493b6b3d516746b656801faa010234fb5151e7baae97fca5963f2f77980f046fb2ba425ae0eb4a87c6e439a8f0d0eee7746058d76a94a6e595d889f825894ecc706d4c3c74d69182e3424b02df96ce8018c748","0xde2827416d22656a3cf87666640aae36792fecaf4370a1010edc714eaa24372ef203e6232ae2fa13f3db5aa7f65008e269d6810a57512906736b93fc932827fc5ad17e4030f83235d352dc29b62d233f325abd9356e91f50cff35cf92ac4d7c426476930e572bf3686a58dcf2e90c21c401e3157faf149b0b55bd7479d5f26a8bab1c91a75da4d50270d2ac34f01117b8fad1eb7fff6fe116c8dee37bfaedcf94eea85814e25ebedc8cdcfd7edeb2bb9309954b0a71288d46eb922578601e94c6d9d92d6a4bc41adee20733a4175c08fcaa6790f37bae9e5ea682fac8b24200276e1326aee90e3ad10f0d31565e53a94164f1f3a789a6a9b436092588dd12a0c1e1d24e0673e12641b5f80c8db9f4a2e628c355f368469bc90511c6eeeaf806cec3df05e8e41b7d9f126aef307acbc9f0ff9b9fbc46854741a5ff5a134e592c7bc3ee019a63b7e4de5bff4f8b0cdeefc143ebca17766d2e8e06dbf89893588503dfe48ab3747436c405c3274832d5dba78d111d927e572011648c03a3cb42c3773652cf28b5c47f5c0b225de90910cc2db0453c2b1c27386a18f3d763e04f7b435de68cb32c6dc75149986cbcb25d3d9ee63fbdd778f701c9d807409a62845d0b019f10b7ea57ded616ea72b8cd4c181f662c7fadeda0ccbd783d7aa2de532efe226c69608877e97c055e980102f30584d5da9d7cf8febac54917b69b3aeab5d20d9ad462907cfcbbd408d4cf5e52c7fe1e3ef763661851fc862dfb6b82b9afc907edfa882867ae4d9c9abcec2d15475b45b77798646b0492076762ee444576ab0e6203374eeda590adf5dca5d8b52525aec001a3408ea17f9dfa1817fc5ba16cdb80db5a985afd3363c60359394885d7ebe088a38dcb7dd26ee005af98b6fdf8d1a834189d489c1bbb5bb999310a8835d8ab5828f12ada32d9a4397644a6d0366a669b2c6994229ea523c77730c36464976bbd5bbd1eac9cbcbe808f5d6b97624b25736d1ce68be8738c121aa57415e27454cab41e32a0f6c6b92a7860cd4e167e5c50c62bcdc014b63af7094ecca71f1d46ee32a90a73cb344d95c173b6ae22220d0b2f41e091d62321113f2e8a2e936ed57c2828d72aff8b8ca9b49283e751eb1bc47465b6c68eec12c836716bc2b955d9ea8ddf80cb8c5417a67e19f58cb74dcc3dddbc2631182159b63fc02383c456c6419cd64525159c350e84b94ee2e17b2d1c07be86a1fa7122a879ca9ad0d9094d0b96df5080025af035c15cf990648343c7ab004412d5a4b130499aa6c9754956f46d98ca8c451f65dff6f31c77720cea77d4a6aa6868c98517a2514bf1e81cf59b4b3072be938cc448cb2847ca3091a94f8db0aa4ffa1841f24daba6bfe9df435d5a089f71ce738a209d6dad7fa45f84a333c0b8fc6f285e85e3322fb9d430573dec9d93ac1a27b1ff6fa7eeab266f52b4f06683aa64b09371a10f580389f9f53255bcf3e80603dd949df75d1f1d4008ef31aaa08aa6d4c6901360eec88f49da43a07ce924a4a9377666d8e2de94b73b1a71aabcfd98d083744cd101f70160539528252179d40d153d02e025db45a657c2ded69c0cfd3ec5199e090532ff042858b068f3e83542c9676cea5187be455fb2d3fefc43edce8aa75e0cb89df00bdf341d01714214c2d7563a1a9560ba320e0edfe41061c3947267a22f10665d6aecc04a09fddb024f300e91dea94d7a044798dde6b8c02ee97c8c85cfa233c9958cb373cc466c88a4524c25bd0ec08bc7adb8cd68d2a94f46aa6492c14ef22817dbaa6edea496a6177c52a9ac2921e2b9c37eaa104290fb7da20e7919661d15e32b287002dc8df3e1931a5bd24a366f4d896353e9d5f0c98e63870708d2ca810fcd08046540298ee7b38fec8e85e76723e776271d9b15d6c9d031f10d15d7c0c3e8a4794e7a451ee02ae2466af39aad68d02f540441f51fb1789b89eae5f6cdef8e358444759a4f146299466785ba0a262d96576d6d930824398daaa2015f9a1d83d1bb2d98d975e083d6639e377922fa9ac3c236b14b5317c5953a897147489937b73c1476754d0f1889876ff1c6e28651f6f19447b40aef7a94d715243325998ac7854c9ec28942a51b957addb8c0dc8aa06719e882c91ff89b4bf4653d8ed0c8b7ad481c1022eb985f7398eebf7b1977eed0557271fe6fb276fb499e584b21c6c00b0ecfc3fd4b5638620852c35bde49707f39e555fae4d52fe54a8539229977bbfd959f434f14c1a6030c7a7b52b11886a20c0b9625df9599306a0602868de5cc8427a070ea4911db6357cf21fcb26a69bb2f15a5a5d21b0ef17a755dc98258b5566fcc1c43e2011c91cef13dbc17da439953cf0588a50c83b36954a2c78ff12b5c138e4b7c8907475784ed17005b3c45bf86f77c9b594098bf93b9db8d83c7fc156ee24167bb97b7dfefc8098e1ee233ecd2506512169d8f1573fd9bc1f1b133f280fbeb345c1bc7b0972c7fd777bd9874cd706e3097103ea2d7e926fc6c2ab3bc19c16134b029e7517aadf2039abe194700154c318021d41b90af49edb15e340ae941b6161bcad37492751006f0f0fb37859e36def7e8bf322b305167fce4dc100ed19e694974d01e7813596b4fe7b2e6811f3dfa2815a26ee19ea1468edeeaacca02bf4a1f65b88da886f95796b365f862ceb8a3bd616e9895e976c60783a72accc89ac7b19c50831f2753d810a20a7dde3644d36453f88c555cc9cc488413a3ddb550dc90f26e79c9b938d63c93c74bd1a0a2b042e1400545244a905aae1f79fc1ab99657beb68f45abf9a0c95e9e839e33e2088f1cf8e507d6b392b88118ff84b0f96de229627b15e6d4d3a70b5c0a712c75a9d2c927cb4be15fc4deea4f2f1798643f7530f30721efc5602dad66524e6d9548dc287eee135fa361a15224037ebf2bb2616373279db9ec04a8477f00eb755cc9bb719021a42f5df534182aafb5b7c12152d16c02cb1e70f505a50f688bf05547bdb2c7c081b539c4eb2f7c773311e2325eb5d5f9635539f21d74fa6f9ee2b49d543bc0f36f9a48875e012805716aa0ce6d31f1be3a9777323ac44a98dcec04bc596589e95cee2b9c50be16864a7d1a8a00ee756e7bab9878cb7c6a5b2751bf45e2556dcfa8edbfe501adedadc2f0976cea89ddf98d1a847e84ad8129d8ef36422d6b3719c083687bbc6d3640476554f9ea8e79990f364ac97c1f939c36aef848bdf0408964c84bbc01e0092a59756fe72efddb88c33b03f4c67a9e66027301f8652e7b46ba72bc0ad511d268df6212c61ec521befb18d188fae8946f3e18de6f374d1d77c2923d3e4ce240c0b35bd75b61e0b49402d90bf3cb7a7eac0fc755b26ec3fd194bfcaf259b4e73575424c717068938ca7223acb02259caf4c739de069b78ef05ddb5c02178dd693a5774441c1309fd1747e11efe545808fe53ef9de78fa0695e8abbceebb5f921d78c336eb9056184399dc482abd88f10518cbaeb50398ebc52ae6a22cf70c219d4ee70208f7f24a1f2b5920bdb5439dbb397aee562f9c8e74791d6b4711fede8f8dc3858bc5ebd6dbe4f05f8eccca0ea4de39eade1677b93213ac031e612388f91eb41452fc55517ea4718db457d6b99564275a264207f266972bc2d9d642f276eefc57da54a8bf01ecfabfe87d397ba96944414e367466ef897d1ea1729652376f465612b7bd56bc610e33c3bae8095c503be486e5d889b3d3639686c9ba28c4142ac366c3a727fc4f30fbc35c501281c03324e039beabdb3fa93b3a87ed28a9d8cee2534b51a47c23187e5409fbf59425b7afc58d651445fed9ae92e79bbf1eb895948fd6f025eaf0709020378869df2ea5bbe533e36f50248c88183c4108d334d656841f4bdf63eb295b60b3f26a264d3836a5f00892572938735d6c9551ced2aa553d93c5e4cfef566aee1b274a663a1aa6f07b20ddee69a1770e6f0b89399bcdd613d64834e0faa0874f64eabd207188272d0176d595a5ce3d5fd59f277e9d8494663ef3e6daae46422454103f5aaf883949a4b2aba1809244a23df779d0679ed963569bf692d1ac46d60802244a782d4d71f706c8fb03a451a0dfcc5db6907e70775d224182cd7ee7b57094361794ea48b88425a0c51a479c8703b1ce85303ab73dc496b3a0315f7a26ce2ebeb78ef97118460d6212158574198817dc2764c46af5b426c7672e286aea3b41383134ce6d8d2fa43dae28f568135eb6e2a5cf2afb4986e2b96c1abc201a220b39011a9b4daca321575aeb142daf0c34232814e27af8484f2d1ff1652ad3c3b12e6e17e6d50afb48acfbda30c27a2b2610165d227b413fc91485cfc93437b1a600dcc52c74fb047476fad6dca4b8e85fedf9d75367525fb61d407765cbe4555ec5050599949f7d6dd376a2fc00ee86909aa2338b31d1d588fbc3cfa0f3ad6956082b79c549169d1bbf06665c2e43da31a3bf2716627d7e914168626e0af37d6c5ad4099278998afaa76830d9f7e0cdc6eddceb17dc696da2b4ebdc7b85b2758426d05f08faafe26628f56f196f83de69a434bd35e9aa69813e1292f27910087f046e6a50653736b8aea176d54e119dab30475d535e746c2aed41f7c70ad3438004ccdfb473ec8a2b1e15ffaf2d2531c1648a915feb8b25e2a766289e20130586ac3eb3b73045d79db2a6a59e031cd3022ccaa230e8a3771884ef62340d2b814ea279a23668e48a16b11e68e2c36a51ba5c06de7030abf81ae46b8667fcbebe8e3b24ac9f04aa0222afb72d900db38c4185b2149be05e7397c9299bc254951a796ef292354f6815fc7a2e3ca9344dfa70ce3d1b07067366ee053a4c9f69d7c393dc2f5d6444928b8aeea5c0c325c4255e4db24b0694778555326a66d6203d3ddff1e7d8256ecba22737c34b43e60d296a5e0c0110303735ca99175c006e98df4c527b1176f61d893c5cae4f21b3213aa0e3a320fea6ad12d47e39c345c52f55850f80dcd7e3bfe5ab0e92a54500eb1c6480a28b591ddc691767a4d6bfe7581fff603f50e9b1bc4e0c0bcd0a6fda7ea9702c0bacb64cb1beb75653f368abcf1165d8004708b88cd3b298469dab6de74f26b848f02160f4488a25771b9da056d14c4c98e20610a0728def3e6361b2cf8f291c92499c41a170185827762ab7495d3df35804f0ee26a1cd8e83c3e682d77f51aeb4aba76a6d567226d3cdf144ed820569a5d2dba1edc862ce260bab9b5e49e695fd8fe3893c4cd19afc0e17106e656298d139d4a0f6c44435b9e9ee4723aa2f190d72b98f24580af25accc13eb9fef5c2f068c96999668e5e8af20cbd2774539e575719f440b6d0a73cbd6d4e8d988d217751961551f26777a61e5266ef0c8479f9414647fd54c8d8e43acb746753b8d64b2d15d911a1c19f1c4a66a34bec7ee24bbab25faf5568e4aac22f0c3ba2ad2d05f543e4f01fdeb799b544bb9d225bab5a50edb6518fad78d67796ed32ce835f8ee97851260dad648a83c405aad0a792f3129dc7ee0d9d718d9f30da1670b658630da716f580f554c63db18dd6aada4a89178e7ed68d9636ac24865651fbfc38e4a7b22ad18050113e4f73adf57b0e918df7cd100702777f0c152699fc307187ffc3d550308af3e0438948688ba011ed3650d8c84a138bab9123e76ecfa7055597af143cce0f4b8dcf9f0c82d28bc4f4df42d39c6c9b51144d25b8363d4d4cc7927fc2fcd0f3640e8ca99197a441c82c4ff3db9f8d270e06309400579ee08cc5828a15ea260e2b6a95a388b9cedd7ce03f0d63a0157b2b480c0f91cec0752bc21debcb79abae6d508ceef94b4a0b508b753b9985f10d6e5f17d923c3d2f35eda9b01e0b71c14be3d0ccd4d2c488d5bad7cafeb3dd288f6f3bf566d2f339637c629ef4c50aff2182fa0e9e45ffaba5325fd9f9050e74b14056891bc12576cf5523e1de35c1bf3603009ce40cd8efac5692bc3236420e8e7d481b92bc49bdbae619bb52c695033a94f096cbcc1db4ba73b77d9524c59a5d5b5a0a0604b911947a42826625e9bc34fbfec96d445d5bf32ac0f229a70f4fec7234a3bdde001b3b492a3a56e49ed27e830ba86008200fc179035e2109e4f2142d3b625e0f7bd07985255a7042d1ed7f5b24a571ab6c0baf9b99ee345962f7042a03e1bd573fec9218b9c69c2517bc1769b888a578587493c965df32bedb603f586dd8155eb0082495f1e6866c6acc53d49bc82d198a8082402da611608bd2336101bdbb81a5c0c5408dfe9130202381dc61c089edaceef43786984c54f62d0e4dba05535e86c17aaca5772a7da8162e193d1c777cbd7063cecfd3483e78e3c0533f83c909b26a8fe3edd4f1b41ae001b2e32d1f6b427ebc7ef334f4f7ba14eb3cb39835e2b8395c5294e0efc75c8c2b90a571859ba5f511dfbc1361cf7f321e528f63fd5c1ca0afa5a1003eda786ce7623fff6da09bd7b9af2eb4d1b390482fd3f402b2adbeb063550d6c53c2e983b0f5264a69fb04ec5c443277e1911ff752e4e8f25849f27df139385af89d109974e09d8c829d39d1c97338db6eb1e24b0ab0720af7b5a68baee0ddb6076d49762e934e9e1634c5a8020381267eb61f91ead8a986f2b32b214dfd0173b6eae3f91274a63d838f51b897277ba05838b8d5d41c0f5afd07f403c160116e879aaef0c5fd3ad7bbc164fdfdfc52c89ca661841f01434ddfb6b0cd683eca2d123dc062f3e00f3f8e3c8f0c18abd16f133e7204f2db3b80c51b0ab59cc7be0a66c1c744de604fb64ce3fd67f43c9cdbe47b0f0429aa93b72d8a0b0928f7cbddacf00a728d651d9379d49d800b73c7a747128858a19a7d6e8dedef61fff7d492358fe317ba4ee5fcedc7e623dedd3e21ddc685ec8b036e76d665792f5c439c6fdc32af1708a81cce87ce793c41734a0c75e9c84543ba5a6a3266f68db39c3dd28ba4dc52abecd492a59165536b0471901799fdcb7ecfab4511d7ed5ecebf651260e34773d9db2af9b51fd85c9e68f2a625ff36c31a3b257dfaefc221975e4cebe14772331a7f56228f3fbabc660c5c302185684340a19d8dd5604fda5a12c477c082aa30248615521b0ea560c476218ffa691cbe78c7d407ce80588a7e68d662e7e1fc2a37202eebaa824ca8ba4ef38fa4a542ca4189fb2135fd7318b28193468e9b8b7760b8f108b03d254ca14300c97c9231f7aaffeb49b3e7bdb91d1c8f201317243d5223f9211d6f42a63c281a801ccbd62abca0120348443834d3874ac94539b028dffa2292b9dfa7e81b511ff634b77572f8d0ec79e26322925565998001cf2105ef93f4e9d45d4116872baa3700d6d3930c60b95c126b941d7cdb6bbe9e50dca7c23fe8b90c4d66dede7a73ddc30973c8cc3ee9625303cadf04130b83472cf8d11c7b0851040e686faad1ad6e0c9111e56f4405a7ac9f5d455fbf6b88fcb46beb8ad43418dc7ad880c94e4cbba8e2c16555bc71812152240bedff0d4e73ac1c9043c575f5b9ce531b57f78ab2a94133784d1b565622191ae36b85709ef4bf1f7f75b558f320aea391a9ba78cf719232c9018187485c902889dc78a25a0e162dd8b3749e8aecf24e475d365e0686738fe5dbd5a99b925ee3926b10c9eb35aadcc2dc303ced0160b1394a72fcaccb791f982ca1d62df8b96efd9aee74129c615593c1857b17bfdb1e25846eb39f2360f12c84abb6aff1f90ea1218f23ac42793028f9f7249f8ad3b1138fd5efcb10de72aae9b2b30c2f415a40038ad968bcc666fec0baadb72079f41bbf696e1627e1e77e88f3139f6bbd3c92774b8d24aafba2b922431c67bd68b730149d4e961115ac0dc61f84a0e07051bdec2128d0fedf7c3b05f96c9859d6de13b54df228fd1d6c124768dd7a0170c25d271a485ae49c732eb4c067c6aedced32f71d780d3989eed0eeedf77e53fe2e0813ccb933ec4ed07dccfa1e8e54d9fdfb5efd986946c49e21f0a300e3cd3033dc0b2e20f9a3b7761902a719c17b61d733c651a0541eadd8f8b5c1821a7a6b9b136ef0276e222e1cc06d8bd490395698b454b468bc9c3a28abfcb9a1a4c21e217df9d6fb4f745376ab436ddb9116afceae7ed4d6f234cf352d864db59f5dc9b1c895271ad029e54370303b6fc8fed8d2f4aaaec71c813696929fe727191d673d65a2f93f1ed35ba3dcea18aa42bc924b42c7d8a1b3495ecdeb9a9837fb12ef93248db7e9ce81eae79e73ad2637ae055425fe150e568f17eb0c2f7f8470b7ad82d9fc8f08be67494b593a32a927529f43349fe8c236bbccea634099e9cfba1e0047fcb7edd1d73b4400919adf4f714b3cc6226b96fc4d9e9a12f72f3a9fd5493e65c002bbd8a98db5cf6ca2fa540877d41b9a5c70102699e318deceef2340723eb7c02fd136bdc907afb353e1b7560831970e0880134c25c1b56da0e21e68200827c721b93c3f2c9df2e4ed2281d953f533bc1465a7182acf649986b51fe085ae0cccc1fedd9a0542195791834a5d6f8fde677ef0352a886595aa0b03289012e672e909eccb92166ebae5ac816ddcb7ec6cc87690673c026262e7c271b393cd3693bb41d3cb19a02f2ea6adcd279d9254c6026ebb4f621256ca1873e39e5c01838de6ff3c0d59f2e862d65b2bb927a43f1a8e96ec0ff3aecf64083352407305fd1d74fc75f7dc81c395080dec92229ad40bcc69fc939edda13cff6d95d3cb34d30399daf1ec253c92a9f793b8f8bd6fbef4c60d60b4ffaa552c5637dc9c87d04b1ec13255e04ddb51d5c6f01a811f495fc18a501947480ca02cb478fb710ddeead2ce9468f6620b72d3a8bed945b0bc25f75be9437dc8623f941ef84e81dd996110822296350cf0614e27cc4a1f6e6435ccf4ed428409af4b8b917f9d420651c46e439278ced740b3771c4b9dad24a467621001d975e79fb737a622d9ce81f329df718758fab6a8860f8cc851ac33c93bc7e62fd4277d15a75fe9657356da4c105ce5a68f2bddc27d9fbd634ea01dd5c288c1a3d4fd00a707374b564e3496a99a6c7e433449cde90d4f3b2f842a8ce187f04602be13125e6f43fd91b763a36c6bf97cc0f22828f7b8dcc4b87413930305034ccce5369a4976d74ebca96a21a7a06c58e0759ed4e752d6701d0811824865e7d78d0dac78c11f86025e7390708a3892c95e8b5220147a019d311290be91604d46c3c73a36fc99801634b5293ed920f7d0d0be9a8632fbf2b5a9c9bc18231e4455e71e9d38758c88845fe0d94695bc7ba0fef20c97163d45894c57a49f20d8f6bbdd98c36e64d9add25aa37fd09bf1abc14e8a314d98653527edece97fec18ffa1448e32dae69361ad730d3e41cda67afbc50bede7d0ec83c7e3030028dd6bb36b8104f1c6253820f3905af0211e56b91e500d0bc0fbf66319e4d59c989ff8179c13a24ee3f1dcabd9a346f3666b232b61100210aaeb963bc963dcc8960b863b160621a8fad26eedbec498a022eca70461eae6a679cc82a5ea9511bc804c7bbdf7926626fb52f1d55c474cb0c1c4548be49dcaf3707dedb0c4ba9f56e8ae77199933be60593af69c72c9e309f26c99a6b8a901320d8f6f9ae9eb14e474e224288157e694a4fc099064b556628b74935f2346ddb8d9ad30ea1bf29d782ae8eccebd69d443a6d316997cfbdb6b96a6d54cbf2f6af2e70392236dc7585355384b813fd3c7f57a693153639eafb8962bb3ac108337a7615ad4731204b1ad6067dd4e5299ce126b74f7451e74b097124f2d374ed57f31cf6d9314a6ffe65c31e29425bfa2d65af3b1a038bfc220b700b2aabd72f8a189a5117afafefff91bb73786c03d5ab1b019c565b3911188e2948a5cbb2addf39b16fbd2e81644dd963ea99bb7e08863dac083b31e8b5693c60716f7e201a2349e3e43cf35258e5e9c38b33b9023ac9914587c78fd5c557e181a42e3912d41162f8e62961ad1785a0c62d559dd923d3cae50ee2527389b6ee1936c87ccb0c0e437ab1adef8e7b3befbdabe70ae0a370c4aab429f9e9a8d3935af2bad4633e7f9238fa0f9be6839a86ddae9a18091a3fc935e852c791ee16709ced50a244f25a5ac7754a5e19c29148bc0693a1d9166b262bc01edf7bd9080fd6e40ae1b0d78f18fc6bd8247f933ba3d8915b3fea59265eb4563f5ce8ffa8a6292ee79b382a4d20735c34dae62b27b110b6da3c6a206f3aff35bbc1cf413e045639665d466860cd0cb3b5b80e70e96329694e489bc302dbbcd4a1025bcb0a2f42a06e7e95592338688cc0bd2ca0fe0c4ac990a851a7959aa02223396ae78d0b79c772dab365b4ad708a793568d24650804527407778aa50e5245caf10a23d35a90e7a19863a732dcfb86fa45e3da7ea3b56deb68abba206c7a26a35123be2572291ef72c4c2fb917381eef1a1529abecfc5f3b81cabe2af08e299e06c68fd8c5d765886796c19d910b1e99933a9b847f4881159526622d66c73ba37cc68e7a472d253a985bf6c339c8bb5d90fb884ed47279b48676f46743e0ff78014e68bcea01686d41b44bd21d4f0fc3ea06003adc7b30fc8277a596bca81b85ddba7f0c3c911522b44b931ebb690a7bc4749eb37585582e7758a8116e1a76a21ed15f07cf38081f45a9be8240530a26dd80775f7435f6bedc5620a84587b3458c78ad16cc82f3fbc8dfaaad8c9fcba791d1c1658b4f95380a242dfc2882ff5c21c76a97a16a9044261a1b315ccf2032552bab0623b5a1e7e2af50d85d05d18a6524fab3751775dc72e8bc45b1d506deea76502ef4c8cec418d2f2d110f4213a90558d7510cf5a14e9faa045e91198ac6c25a5baabfc9e777913fc01d2a079dac842eaee484ad0b5b340060103dafb0d33acae8c450636a84110af2ba8c23f4ed4f30d414154dc5cdc00064cffd70e9ec1de31b5720394ef2795941e0a1c5c2859facce56c221d88deb4bd2b7f1725df16badf77732c361524c7cf88c5a5074d1dee644dcb12b19c3b1b263f0fa41376698b44f8cefa945749e338aabace70d9e2ae96ee6adbd778ee18d75bd42b827510e82418ea3ee34e79bf12d11100ddcfbd7269c27a3ad4f549e649bb3b1e1001aa8e772ebfda3793ffe675504054c877aeaeefbf02515a1dfe2e24e4378f0185a391adf1f933690b39a66d5ad8af6612d9eae11a5eaa14fdff61f38258a4167e4039c3c625c453b42d509dcb9a4f10040e3037fca4c76cb80fc34c8e31e718dfd40f7458e340a0ab928390e37de917113da9f0d26c899c535652320dc2f7f3eaf9d486ff85b5213a1a0756673c4879ed45af9227682f9d1047b0b7e95fc03b65eba99d2fdf606e74d60f8ae2f67dcb53c6ecf07c5bef8181fd8b2aa3b88743152ab0b5df28d45f8081d21b103dcfda1b4d57badca68917ad7ed5a215e1b779d9d7ad186868b08029aa0347436bddbe7648115db818d04c5817de7ebd2bf47e902a2fa1fddc25f283b486b259fa6137cd31963ece7e01ae1321f556dc1c32cb2ae950dfc50e438d85d854f835cb92d6191769d8cd936f515d5bf7f94663cfb385e387286590bb8a7835e6ce6511be92022290217220a6dc6851d3931197fabd36762ac89143120b9d4ec4b4162749872b7f435871efa044abc4caf6572a9fd4c536066b3bab94b620bddc5b152738ea0934d7723c84769c34b35236e43172ee42b744b9fabe4b41ac3f29d7d17489a7ee5dea04a96598016ec12ef382feeb9de7951b272cd5d0ef2368172940f289403b907fc0f6a390b6098f3dc88539433dedda11753b3ae6b1102f3faf05bcdf9d7275be38ea116d690bb3ccd9b6a35276c9c7f68415dc8f9088347967311f39a6b4d1e352084c1780ed625ad8f333f0f6e04fe1c246b7f47ec188e29107ddc5564a2ce95db7330a77b0139bdffbce658737fa0220a4c0c1e7f8f6d4e6913cde3774b97820eb4fe110a7ad7279462a1780f47df96f04989e9f4b59ed132ee8fb50298dd33931fe9d6f71eb4e77debab0eb22ea751955178b1044a6f81f5eaefdfed64b64f31dd53811aae57fe183d831bafadda3f20fd3da15317414c0e983e18b6586d6c72fbc39a99589cfa2e826c446959cb8dadb79f2078690aaf021ed1c0a5f5016394e061cb3a3132cf937cee27d04bc3a102c74b7c17b5d473843c8205944fa37183a67d8ae1de1df916f453c2a48680e6f7a7511ee0aa3cc0be432d08bbca3c2e06ce085f24f034f602db2ae23540b1656171a55662bdf373c6ab341d77a9e448852f19b01927a40ba5cdc8ac862a7b3280e09fb3098140d7478dfc1701576c1e19b36925be166b5c9707c2321132f7a346497ceab8337d99cbc76ea7754dd51ac724d7979675de4fc7c6751497e5b15ff9e3da4fca27961f2d1377c2bb2dd7eb186de6916f9b234872597e36b2b42b34f7d840e076c48d221c9388241c205b8377913a605cbab8a892c14286177758c3b1fa94d8e54bf89dc3c416da9bdc01eaf3221fb25785ea2c77ed67cebd3a21fd2055a3de669d810ef0c71bdd6bca9be1672d02ca27215f8e7e02599b5a2be3693b999cb998b865004f1693c1ff7db057184cf53d686e8d7b311cc62fe781ef443df2959a7edcf6eda1b400729d7dfaa743442f48dbf540de342f22891fcf0967c80105f5c5bb196efecae7fb779ce8ae45698aa764021872ce7424742da0ea00279748e7be65c9b2755a516ca76c1dac3b19041050be08abf5e047b1f170be253b351f33c9319257bd774bbc8f40eff451d43e2bc57bd8eb2aaa4a299d1ba725e5a12dc637311680b30b66cd88f4e97625cc00298baaa40c8fd7b1318c7938b8ea887bd2ec698c46d89b3776d941490ae34bb3df7d5f5c31f51e41e195b5faf6a8b3f47630623c7a1606d89e57ac83940e05e57c56bc42f0e819887441e4ea271b76eaf7835404ae756c656d3f34bcfa1b20c9c5a9f86ec8e2fd53c293d2f95700b98f8773b8d6968fc6818213d64fe443ebb4e73d474de8249135c066a08bc529787d9ea8ab38bae2afce45988a4c4d3e25a1d819551e44fa0a07177da7f91a3ca32fe4be2cc26c5c1378423ce9f4e8cc2cae47f0a974fc51eb62c7c3640f796d0e85a79d07a320b445ab937fc73e543fd19d1dc8c3b714f19a27de73958e510a8844e757c91d0472f918612c3056185b1b548d0462a379b4d2f838067eab11cb3694ead40f05f0ba38756b72d5187d51295db42e16c6579d5dbc5a2b5d8e9c441c8069c30f44a63b3afaa837b1992bad2367cb22db06c2a1c61f503961d495fdf5794599395860cd77e4f12fdf9fa06164c3e682251aa87be80fa4c1120e7cf2bed4d1a17cd3adade6f5bc8ddc246f6624e746919a0c1df0224064410b409392c247ac5cc5dba26392f2eb5d41d7528867ef38d45cbded482ea9351dfb639f7f897025affaa956ac2424a8e0b79a125a69178725a86187fa2205795b76e36e9ae74e39aaeb0ab20816d84e716e05336f8142790f49ff2db050537fcebb2135261ac617e755e18dc65bdf6dcf80e2784a5076659955f28238ed57cea0aef2b40c341f3dbc6aa3219f690ed0daddaf3baa095ed0e56f6a5b0c79167575861e3b8d0d861acdffd9c14446bf9914ff1f881cdcc71d44c5ece669f45e708e4ee268e08b8b4eb6e5a2ee79434f75775f991205c923d197eece253ea53c1fab286ece9a5ca26dd8b52eb28ba39ecf78ffb7f97ebb095e37d1bcaf563b83c7906c6855d82fa45a83c6444f49179224456cc958c44fb288a4a136ca95954c2a6f29687b7a134b2f04dbbd3fd388945b89b050aa05887e56c0330fcf2248702893427a5c03d1b9b457930683db1811345b4d83fada9e6d8e5cd6f0e2d0481077195d9f8dd716f26b8395a23c318dfac112dacde9b842a7c94c12d3fb35d40e6493184cc7f0c45d26d7e8a6a2b1b044fa26324c7f13c9f51359c788372cb391aacf98e95f4bec472e99013c3cf84d694f5f2854cb1853451cb44b3382b7858557393b529698ef5fffde892070687fe65dec8eea592d7129f5d664cc8c899d18be7e6e5aacffba3b8f940a26636a08bba839044a75948d5965e42a9d139e3f3f7c720744d1f737d225f435b226f54cee658a5c6238ee492cc8f529c874a9d92f8f581a48dc84f901c6f0ba3e37c045085bb3b054717506237ef35ebf9653d5e477115414d7609c06265537d84ab68096c98312f85327fd5feddaffca1e79389d6fd50a4858dc7d08389914a16f83d6a96bafa757fc3eb62612945984f7ec2e5dac71c2f9ad7f3b8d42b0d94f7a58ff57804b7431c1ae1e629fb86f5e2774ef6cb539ac2fd8b6e4018951c2abc43d9a37e8306e7856813421afee741bf7f52ad5c71ebf665312e736847d3a143c8f8be951d58ad3bf4c101603b9a452681a5ae64a9f6011b3e684dfaa4133fbbf7bd08c766d591c13210e16d5de81f0fbd0cab254803b120c551513cafc2365d0a10646666a63e20a9dfb9d37cdfeb3c87944923e83cd442f0664d0eda5f16c8c23f0c0e4a3fde868127c4706949fb0e1aa76a0498969f2b3224676d9d7e3be00cc2da7d5c16ca428e44657666037cd0ccc2b3a53d7e75cd251e14810a3451cc61f422cb9b63fc181887f33d7967d48c9026685996b02390ec930ef6d684edcef72b185385d8b9dbad829e5d2ee582ea62822e88f976730b1b60bf64e3e75684a5ebc6ac28a42ccca98a89cfe3169e4e8c5798dc34fe15d15914f4f49a25fbde92dc39837293e93cd604729eb800aa2834a42e3cfe38bd3bf14d8cc747932ed0723da52a729a96e44520c83653fc7f45bff141e6310fb144bfb9da743a12a9d33a377db1b626c8a29437143570af037e169e0b5529d589b21b5acfaea920888ceae6efd76a8e64bd2482f283759db22f31bc1e4bff9fb5208b09e485bdb08fadb3899855ebcb5c8a1ccfa9509ad203a182ae5284c88b61c79f4f6f3c869d9551f3aafc438d2944a8b94e90ef16efd5c04c2aac55d8d144ccd23fec3c4f639908a99c81039d15dd164693fbf4ee0f3376f9bff4374f11e2c3821c065e405bcb3fc43f1417c960a8f094589f572be9ecf3a3d44ed8c49be7891ef66e9fa43655c500d083435cf2b6a4c3242a2dd7ba6fce9509b5197d7bbe0ef8d7644e143cd3dba34da4ce93bab7eba48b8dd0e31bfe9667e9ac2f974818913dd5ccccedbad9b77a60eecbc6ed7992d6e8b60e7a69895198ac7d92599258da0d75fbf41a5c3d1cc54fdfecf56a7be2ec8ea08991cb4554eaf29f03cee047629a733bc77614acb5342006faa2adca32807a95927ab2550ec707ca6c42527c7096069477644578b9a1725b0b88328326af55af8776861027c5a3e5e6bfff073106e764aa368d1a8115e6737d8bccb0bf9f3f62ff3ab56b97d6bebbfdcb33164fafb4b8fedecceca4abac4fcf3e52001344fd76cb416461b10273035a6356ef5bcdf5447857fab2f87bf8dfcb0de7899459594c24a41f19ee681c76f2bfa6879a0d537862457432996364fba2a6a378e6658e8660931fd0f902803dfaa3c49b22a14ebc4e7dc5b9e03f290b9bf4d9350f006ac33e8f6a20c0dd8b23d52ed705ad9827d5db688698796897003eecc89803fc31227f419148ab6c23c0463be049eac1d79123bb290711f97cfa53f148e75666570e44faf4a4f743b3ef46601669d814fe9866c58f5ff964e663047653dbaaf103b30c44157784321f98e744662ed3c4ddfc2a5212c658c8b8c3aac15e98df0067875e0eaa0f50cc5279b83efa817c5bbc5b24e16e5efe3adf4db47b31762aba36f41ab3ff608cc438ea6d1292b16cd2af4a614d8b678e5dfe43151df365f02a957c7e2f2bfca048fc57d56ff66dc6e2ebad2b25747f39a9fb4dc99b297dd79489e5dcd4e20a68a9d49b4487242ce952189ce4a4bede67ea57582dabc77ed8a91a3fc99e84cdc967ec921b2d4074339dbfa164de74bea7a82b84c0ff148d00530fac3502ffaff2e49156114ce480867e0328bf19b582d368f4e67e410a37e44a4bb7453b84278db21cb521dc1e9df1ddd7ef03fd9b0efb306c485326949212d5f17f66d6b0e01aed011976b835bfbf76d2a6ea81b240842660e598bf17797046c07d176f09f2a98a1441863991585a58431102cc3dc61b76c941ed6203bf3109200b57f0099d71189717f1168b71b37f1db481577a07602534611fc08eefb0b1887f3957c78bf0fc7efbb220fd34b38da5368401837db0718f6f2a48b1aac6593cc02136c4fef49822281e5dc481d9a2df82af48325b4d0f9808803badfb2566ddefc7c442c46d215b0c65f8e46866d5483294aa98f3b0976a41ab2b1b2f1c207781ef71c86f6fa0a144673cd71f66db31d6527b3433dcf6b8af69e88c6ffebab3e4b42afc3098df97600129f81cb5bc21ac3c448a69dd03831ed081aa8a7f3ac088a4d48d4da4ec824b882b38edb26c46e8bc72676d0b9ebd5d58bc6a2672e68d484560ab398da07cf74b5caaeefe869e2e8e9922360f4e56f063d7953f2450d3f079a2297c6c4610600a89e0342b0eaa86c336cc66ab5861eded78367b02b589e8594d485be1256fb6ada0542061a5a4489c1d9931f5cb9223dfa23e539dba65b95d27cf52901c1a386d33fdadfbc47931820da124620819f0971c383deec14d27151ed32881ae3d76ab59d566663f179d6313535b910dce5ac94458a20f886bc776fc544af9de5d6f3e1f839a71692d8e2a892e8e361978191b5f36136c6cf33a96ca71a443415e83fafa9a3a3f89767410fb7a9490cef95aa98da8c8c5459582d7b6df044f94c7b312cf042cfeecaa15a5f2922feba125aed03abf63fa7fb05cd5fc0ef7fcf4b40f1f400b0931cae1b7b2cc3de2c0aeaaed25a6fa7a16beb5b77a2b1441708ee2969172815cd2da686525bedbb9215b2d8d74aa8b7bb6aea75be4e3b0c05598537c907b42e4b71ae25746a8bba01574e60fc87a205c6cb236f5744c7bd9423c92b03a59a0c54c3a6a8d94ad78eded4660147991794b1265eaa9d1720f3e40a190e4f4d0aad2048843a5ec8af60a4a0ad5342b56baa9f666d660e826dbc68da0b70a902597b5bf6ee0d42821be35fa5e58e78ae1ac7667c0ed9cc621e0069bc84f64e9f03fba6f115dba973021f31c68462fbef253510e196651d4fe2c080c0c892431aaf6a16b5c4901c0c600bd04c0b5be189959d5de82108ecb2148620b24e2dcedb5df7b30d03f207d86f5306a86ab8c24d5e3e40e325b945813b8d0a3f4341fb214b2cfae4c9f83ab99679307a15ea3cfb3e885e129b60dc747bddd66dfb0c18751ea85cdb969d69af0adcc0f7921e6f7c66c592681eeb87e023d07164cf88392c69c4bdb2e812ac307bfebc95af758232f5126c1dba36c00fb423997e174bb501a0c8f688ee9aff113ce23c08a56d1c52ab67ad0f3ed82437fc3863b445dda7b7027653e2f3dcc3ffb424ef90d314eb4f569accf7512df6f43bacb65055cb6dcc2336158453073b861712979de662215dcf5a746aabca9c616016cdf70ae42d3f02faf01efb1910aa9dd888b67eddf2f39cd804ab111921b0c364b9fdbeb54b64e7e38fc9c96cce10d38e2dbdba55177489620d4e19dfb25e10e0dd0cac50f4c97a2a0c4b53e465b9aacd837a3a1d4ecfc0a2d8abc18821f729ad701cfcbc972cbe3f7835d24ea84463359c7d748ad745473bde225e39655f3d4dffb60d60677be32a59d05212d1cd7b521e4a7905f832db6e4d2a751718ba5a64e890abc1daec2e3026d360acfb523bfec2d67d1f7be993328a347fcdf8d63ad8df6e30bf2d235756cdefc182a7ad8c84d55bc7682ba0cf7ca961afed6633122b6d5cd13fdd6015152e2d072c381b318fba71d2d15a6e42e6f70130c2ea8a93d06c3288ea2fcdd77c9e11ffa577558b14657fcf655e5838ba71e87c16adf7831d8c6d5833a9c8c773659d52e70f3b854ee178ce150f6c5c634a8dc5b1fda2825cd254e852975196384015d7d81208520ccced331d4bed96ba6900818dcd05e6841470a8608778c037df9c5e0a721e0f88d66751417392d41de8ab9d4e32b9d00a7e4e03d5cde43d6468a11166895e854746193bcfebd0c85c96e6f62ae7d6df410f9d4f2e0069cbb0f2139bbc920f8d7574db34c49d39fd797656fa2fcae914d61c8d7e507ba743fb0b74b1432d58d68f507d50fe77e5bff63e23a7d25c6eddf0cd8ac07cddc6b7b733aac607df46183bda6117ace8f1c2e0d9f850cc45e359ba9b439a6b89363aed5fb2e8e08f6ae88da76e5973670d7f20e6d9d08ace694609d9fc232927838da277161db20bfbbc28634a7bf0ec13ab9736c20de666f6263e283a6dd0213b29add838c1998191dbff49ebdd4075bb5a77511907601e11997800b43e2827e346f1d26eb940067136d2ad753944835096d5f2f4caef2c26ed8f2e9eb470abfae8852aeb78a5125f7ae6736a05229a827df2872b023b6c66697a503e6b6666100edc9a782c97020ccfcd95254869c54047cdb236fc2cda37d8185bd2379dd1e681fc4d049294950b0c9aa0ac67d20fa6b0a1cae5640d193644ae16c7a7d9ada8fe2b63f846e9705d642b37cdd5430af6aca65be00d94efc203a69c3ec0fd7c2d90c9736697b39a85caf78f8bb13b4ca968efa2f875fac72676acdee6b772054e6b07040218f400358ff4f2e0b729ee89f19f114ef8b62d4114891f20a5b6d448ab3212eb94e7f281fc183cca1d960ce015bfd13fcd6056c9b69a76735adab736a79a90481fb3a4b0ef4661a62d2b47ae4ccf3b2f4737951e34514f59eb48428fd1cce0b243d9ae8bb344e5e4c1d846feb8005e12f5e9e7a8b781ea4f93274cbd53c65c6eb3068d4c9dd96204f751303d9a3e7dd6948a68c974fddb41f9f970c3d7f0d175ea8320142e0d06e1ae1633a8bb928e92ff04dc5a9764883c09fc8df0313cff549c002bf3bafe1253bf1301c9b459bb89791427c293c9ae76decf46a4dc6a1ad3f17cd782ddc95c1de6a54e020d8be5fd0f71d58e385bdda677b3832378b34c6de1cb300d2f72ce2bdc74532c94e81cd2eeda93ab23f6f7a92f6fee7cc02d31a12c89a64510dfe421222d4e41824028fdd7bcb41e7d30b5793493777dab1d5907cc648d917f8632396098001a300e04cc265aad5a192d22737667d1b4a013c93dd4549de03f84dc6f73f52d3a7bad5df408e0981e65451eedc926f289fd047aa21d967215d2801d0782ba1fad26051777d73cb5e6bc63dc1e35bb975f57f4d9dd38630dfe02fac051b11c24f2ca04c852b953e5ec11ba6456a436bec82d7d453d7af051dc0f35aad256d20cf926933b06fafd2eb9fe5a127f7eb02acdfe9a95dd258b877ea3604eabbe85ac2ee491efe2bd4e3a42ad1d15f8d305e23def4f8fa506b1dae72d33375ae1575d991f6c962ffeb8d79d301ff1ba2137b21979637210fa94dc8858a082c9a2a37d054a3d961af70a78413426fdd1e2c3a801d24d6dc83accff07991661ad74ebec4131fe8b7c71adb2d22228e61e6a7198bdfb88251174cf1d9a24149932767c916c444ec36a9238c08726312a71be5f99d0b0ec3302e891f1eac5c804fd6adbd34e655fc0991a3b0018a5fa9eec722c861d9f6ea49132b09c9557e81076238121cad842ba8f3f04743c198ee9bfb5287e9ba29c776e739f212de5c73f6b30c56a5428561ba7885b42c06b6c062b0f55651af26a23abe8d4e8d4b45da5fcfb16a58406d701e1595534212ed4c6d58aea6117707c0e3844153f69bbc26ddba1ce4baf88b5bf8716de39c058eb4c7645b65da191df172e46fad4373d6254d820f0d89247945ca10529e2a16ec38fc79871894d8d560de965443c489d1a2cc2bf344e143986123262ebe6be2869dbcf5f8ed83bc4a535f293bd70349d305b94bf8fb10b5582d04e412f9ee40a7350770da4802b970fc8ba5e494b918f1c59f5b42c8b2057b5b9ff3795892584cc626e8fc77e9f19197226043e0de73ee7360523976ab62fd2a3f2ea0ecad97ccd5764379d0f1355efd5e53949db398b02117c31545ee9f54199fdf177a52f7408cc8d24e1383e0e4904a1b6f23c220048a21898b87c9a6c9c5a71b9f5064c8f9b317be777406a3e6d08f2c5459d75e50db15e192e5d8c6c22d73b4bb2f9fa3b3b483f5ad631d6b21a5e2f714ace7e8c119ca5266d9f8fc50bb8da295933f0489faae5e6b45a2b8469770607be84c6db4bd676876863bf685ad81da7b8889795754239b68fcf01586337e60bead56b5ba3623c1ecacdcded8cc5b9600d7275bb7fcfda806ae7749efb7739dd8fbe0c44d55c3f24e98860c5fa41aaccc89117530b67a7fcabbdc73d458471919ea03e52dd70f676133f7daadcc464ff162595b686ef6e20aab1195e3f9de0decfbf96609a61a4d0dc25062504f7da1503969f638ea3fab7ade42a475778641358c2420a6c844606e86e462f0d3a4deddced225f71956302c9bf3082c9bde0668c3631d80b42d3a447d48e50651fed126e58763cd525b954da6cbab1608a8932c20bcabaf299c8872413e2bedbec4f159faa1caad48ef7fa785ba5d1acedfdd6b01537d2319c0a43d26cb1598e1eb1a169d814a6ad594ed157bf21d1b16d325b4f8c3cc7c3ef117ada26a98fefe10417577dbabbd6e8b64fde0fc7b838822b0e7e8ca136024b6a7ed1088d815198da78565736ed1cedda015685b8ce621888b5cb9628f1180081cff7e124f4da5b0fec67c87e2a3eef4aa50fa2d96ada11dc124d0e1812076ec355c50139e30a7450277755e8774ecc046850a8126d01c0dac9ccbb522efc8b0d737cbd9ec7f7e16d81c12b462618324a01ad1fa587c96e51f19049001ea6606842ccee54e247d67c874f959abe9124c79eb55190cf76d8f1d892cac522528c18a15a72b01e7838ca445d875783767b08e07acd054569afeccd4c318e6c9abffcdac7aa985d67229da607162fa90862425a81e7ed452e175ce830d6e23eb7403c145ffbd33b4464c9a538fe73a906da665e2e377c1c87287e2b73f5d338716845ca2f4d64a5ebff1120971b0a50e7348cfb9516d41bc670bf82102e718120251c06be8ff3c77eaa5b8b32f0e6668299a377d724bdfcdf709565e306d2da1a78f819d03150367750afecbd23222166857fb8e2c5676cd31a292c2665aedd70f7340422a8f94ec8900cbd70fe95a73b482a57969e41ff945ad15efa1787dfe04c8dd1efedbc37ad3c0ca60ff00f69c7848c57a31ddaf9818a5d8a6b4b5e4574b3ac9859fe60009bda5240320a111044b263be88efd6a178fb2f63eb17004552800ce99c5c63be6e26d33d96831e40119d887f4688f8081973284174f1da56f19722c7008cc4b3578ee0a93335a27cf1e4857e1c9e2e845daa5e3e563768daf6f2df8f1a96e4d22db41129d11d5bdb5c7c7e9bc7aebc40aeccac8232a924afc21f7f5dc3896f31ddf0f2620ade5d64c02d640634766ea9cffa0986cfe918c4dabfa2c7aef5593c4fcc88ff153fa541dc107e528979dc8d8fc8b4534002f2df2809c418156fedbec286a2e21ba83220d5173f04ca7fc03de1c02d1850dea9234344f1524c11e50ed491fc8e97a9be64f7856c93fd125a0a2dde018442cfbf09c650dc9d7cf799ef8e1433db1574f9159d72941b0101e3dff31365cdc00701e41c1e39f8d5c23ffec3698462985e5b9e2148d434a8e013e98bc39c5a3a1fe119f4336c46ce1931b9839f635b0c6285c75049efaecc87b86ea7463a5f179462a9f62a8918f8244c7411f9dabd8d9e31914806b528498f54658b6a25f6da458fac662fa1c1f30f5fccd86dbee3a2d3b3c557e9b7bd9fd8d75a57ef7682b9f780e59684619ad58da46bf026ae0c38e9096efdac53443b0663b6840a236137d1f99249ff01891d5250317d03e96fe5697c87cfe003d6ceff1b7ccfe373351be08c0f7f8a832805982b0ed915efc5f501cf9cb9680a9c546e12070f17545a5e9456fd4ca33e297b9b4128b925f473b6040125b6fb651a9244346b21366942cd414500edd9c1c49b1d9c17af2b22c3d5e79dc9dfa51873d5315271f046fed0b89ff1485055005fd7e30f4e49d8abcdf6c59df67946be5bd42f345fc33ebef5704b07722b4ed0ae413519fcc60daf0b2edcd5555a1173bf32f8bdfd848c3e1540443d729c94b907b562c804cc62e7a9cc0ef917ef0f0a473c8b238f61905908466edea44206149f8e3abed2c1a03014bc61282df89a9341c9ab2da7241e59bce012e22adf1c76e6c99238209a47d95dae7f13cfebe233a71c5f8e170b00f1bd475c94265a5e73552ac42e047909c5719337a9fee86b4930c6744562517209399fdcce9357eaaa3cf42c7ed6634d5f3416fa57d70c8de3ba38f61464a3a459370e06c1be5cf2257c6eac26bd72ad5d01605fded9f9f38878d2bb0529b63cd22753b2489be53c0637082997f42a78522f049fab90621d4e9e89d8fb1cc1d1d7f8e89cda7eafa084de860b0a2598310f83c019d02f73de3b914d295b0a40714d4f0ee5fafeec8e3f2e12c1f156730ed7bcb2d07d1208de05c971cebb98631c6fa4e79045f60c85d9f34c3b7558267a61a9bee81838b91811e324a8ae3dda467c54840607f23c9de7bbea6efca5c293ac60232cd822e55dca9f0be091d1275932e8c593648ad5fe2fbe5bd4cc2713ac9f9ca98bf5b90fd04cc49461753a9631172308e614676d6738a339e9b556c8cc7d5168966e111c69581a80590fdfe59e93a0ef95a8f265a824fd18afcd995e2e28f2d70455392272980860bcc0d348e5dd09f49762fe6228e58d442b3defc6bf300ad02faf76e40d3a7d92c1a9f3df3fb42e12c8a0d8445fb5b4b0e51c320aa4bc171695e88731abd2244b63ce57e559e78a9168d497ea5dea9c266c618f17b136cf5757d2d6d3969627cee14adeebd3b402f09c0a9c3842110840d0d7fbaef2d132c6bd33ba0768dfe6e73e98b31fc1f1dfd5ec04899dc35dcd4d2632bde026dc270aafea28d592027503c1e00f402d055a6bc3e13a96ac6868ac082458eb3fe073f63e9c8013d677c1fe76649a1e92b23288f354777f583e315b128f7559e3984b2e19f54150ce4fb300aced24cd63b431fd2e705d5a3bbd17e466bf9021eeb2ddf2187ae44b63faab227655b146822689a68efbd5065944fcc229b88031ee5f836db5e82342565d2cb42a18f34cc9c06c0d0d5e86ddccb1ff8404902e40a5b880178e2769bdba3a963ce4f9be6785e1651e0e16088141b164c9a3dd0af88bef7e53e5c6304777e7432404bb1a70f5672ee1d2c166347b5adc263934148d4a532ae8201191e1c951701bacdcded4d05e1c2e04b82603cf1f58b9fbaf809940219ece49ab41d8c057a9ea43751cd3bc7668142e30568da70cbbc5a0925cac002888b28ec5c621be6dc655f414b930d850d90341fb6a5800540472e8efaf4dd1392e95c2dba13bfc949d1f30baf2a333999dd311ccee1beb2ecd09126afd5b2d6fbf3fdb05aa2604a2f00928d6bdf652f15e6fd70f3c9f0f0b34a863fe2b8f44399b9472d8edea38d34ce254612f21aeaad74aad56cd6325098f69a6e12d9d7744785a46d5c0d44ab7d649eeff640a335175bb154ce1857bcb7f78a76cf6f4dd2a95cfb96bd66ec2633ceb77369d5bc305d2fe733f75f372944263b7189144c1f65c2e51898c177cfb473f32ae71d5b1a80e048e3cd85700b552272b8c6b072175295038bac7f12785ccd5a48965e347d4570513b458e02f4bbe4addb6242a31580dc6bdd902dcc711e7551ac939be40a71f9e12b4ce4c9c50c32ba9fe7269d629d012d152740a3333ce0d23bfb9494e5fd6517fb00495f9f88f68f17bf3712c0e862c8bf06adb97c2a9f71295f1fe95c32b66b2db40ca8b6040e21a46f4ee01566d0fa53e447ff4f62c8e7ddf00a02e5f238be80e71c2ac8a73670dd08ba657e1fbf6fc799e179bcbeca8c142fb26a58b850ac042bb884ab01184b7548f79e65146c51311a803b6b6acdd7b27895d7c4b771fbe97748566de1d4d89533a9e970d2b3c91b4ae29bfaf1b91107f3acfa6f442a6cbeec0cd97e4ac40f81c99b40a11ebeebef74f300786bbf62698bd540cab9b8bb3115d1185935b7bfe6e086411921a3e0a81edc33187ecbc29036c4a0cabe541a56b552b6e4bde9597215c093372134387ca4637b4755707c786dfa0f4634d0ab751ab70a3da837646e37e9d2a81534b6a725a085ccf2fa77424815a8c62b70e60db2a760cacc5438d059d64658f70678e1bf114c9630836e72c44b5890bd320fc4af32ca0453f6e78592c2687508c9cf0dd7394ee3a34d59b64ff331b26da714c0674e3ee16a779131c63a6e60d727a3447f03e93cbedd84f3816f76036c7bdf31ee0ee9897a2c5eff2efd90d29c8331eeb6b63d481be9387016458eea70c34fa6a5a7fd2b58f18a1f804d53a6effd81a84e911a195e0cb085b9b8a4dc816df4c2d5c67e7949d1743d907c7e036ea01b6a3c1e73c3195215f858b107a91226b3b4e186db49bce28f2fe786c265c250522f3307fbde7a8d25bdbfd3c664433f4f923dd122cd3bd744ecee34067f9bcd7b7033efd917ba0d48f561d9075d330f220cbb6a1d29441dffdf2e962b1feb6d26daf58bbf7f44734d1239ab3001d79712b0fd4a7428945d728bcbb8c46936de76309559482089e06be47fb0ad3f56d85fae26f0aa8f675cffb888fbb1d48e33e5bdea3ee9acd55fc3353d049af03ca4952694141475f960a2a45ea20611a7bdb3bc5ea37fac468d6d527b545c967c6d65b8db44ff36f9d2767f5cac0f145be978dbf5d28332f738f8bcc594649395b43202b6d96973ef082ed530bb0dfe719c8ff697cce28b3d40afe51006acdcf243012c8d6a5b1d85613f87c873ef170bed8e4bc14169054cf7f20c72251e3ddb75c86dbc2d0d12184e24aabde7823e80a7cb25cad3778ba1ea4a23d3328925b6e7832a7575c1d024ba2a804df5523fd2d61827ab93fbec5c1ce7cdea752d240f7b6b49fd1d0d0e4da285b76ee93d4eae6f5934438a79777f0e8690ec56e0f1033b00b65d91cb7869dcfb0a102c3cc299069c8e6a1c90a28780868a2d725aa3027ba1c4b70f3b21490c978d8f937d5f2dd02251b44de720cf8fb3c24d1407819123b2e72b50a399a3187892967ae6f91fadc554a70fdff0db8e6ed2678c5414bea686e6f05a8d4bf60273b7d232e4d86ea4b273c1da436f0134745ab9e74421bd28a6c853634a2d9c2c5ebc13562d479627b0c58de1a6dc7b467549410deac4d4a189c6cf368d3458e307b28e4ee9ffe78569b75521f50bf1249ed2b2c6322802d01d7ab76062e1bd230d070007dfd23de690e3d6c315f31f98527b756ed3f64be00d78028e78c2795195346c12071d8823dc21736f06d0b0793dc1abfd10eca521dbe58e1b45b3e66c371acba7a311cda95bbb0f303228665fe2d902058907a655d90d8d2791cd737731b7bc771a9031e281d05c32d7a2b2f02fc68779e3ae2184ab47559c5e4fba06c47ebf5acf5894290b4041f37452d41d170e4d91f748b740e779e3823df87dd186d33c9046a3380c26dc2e5a5a50b71837513e7f81b69e7e188720332c9b527bd0a3462fb0d80a0c5db34c44b928b606b39cd7d20fff0d8f37d0bb4bb73fe44b4203461eb1a2e763eb386290259fd69edb0b3ad2f6606533d4d55b3d144476000b4ad92057f4c2b3850f7232619d35ea41248140087b40e75d576d313b5608bd34f2ab9655e1d4fa0304bc9b1458712be580f185c10b96de153cafef7d4d4815e7e2f067f8fc8cd2074cc0c2325d3ebfec2c6b338a1de2aebee659d1ec20587ce523b14e11bde3b5d64db24ba5435f6c853a8c3744f27cb222c34f37d359c79e4e1d073dec2a81f1ce90ee2fb43c82dd853bf06e622c719e2799cf874f74576f8c3941dee7650229012e599ceb568ff22cc4c469097ee38d2a7bb6fa465c4a38e95795bb9decc52c8900387fa412c37730e3c553f1e651166692c38c3f04a02db79fcfaa7d4a7e4493908c150d427d7d82ca7d9609cf32ac8d65f23819f2d58b39471da64e78f14f988e92d3e2c86900d7d6b12e7eb9e37d2ec85cbf18cdf95778f602345daba1cc21be70e1a5eeed83c1bce0a3256b2db630b062fe762414b6a17f1c3b4231863e6340a0b12503ad6696730ee781e9a7cd57687121ae63f08cc5e67f9ca6d534b6d3936d92f3f7d9ad983d46df1f78d2053d6896bfc6bc2c2cd97b0fdf116a756fb35c9eb963b5a6a7dd6b16aff8b0ba9a2b68d55985ce99b177dff291db27e7b3d54ccbfc2a6aa07568c59550bf620f23708cd53ecb69c9e32cd78206186e9de296ab4b140b9743a2e14ebf5394887a0809e83e1cb8a6a519dc7efcee10ddd59926db2791aff3112d8a0188e24068da800b2c7926980b535b4b1b25cf2774774e8f793b1e8d865994da83b87dcaad0d72f5ef247564f338ee0546755f325f2bf9dc533064ff883f5b267791efa1ecbeb07259835ef46bb06b17df7a2f32e34bd3e3a7af675e5262d3d5df7c031452affd96911c3eac76417997d8d2859de74b341dded41f07c960e6ecb1f0af073fb5751f29281f2c6f53c15aaa010de327e2786d6d3685c6c9ef0ea3af695c8efa5e0806dd6bdfe4ea160dbdc1120005c9fd9ac75e0dffcb9b44771f47b191878b5d577ea526e76fc150990f157a097459f75c3ac77d9733b70491189247316bd7472215e0580ec82dc2e3249bcb2ab067f5e3a907dc6c91c4f18e44c5aa5b9e457a6221fc9c3b12506ce104aa71ec26e0c40bedbb6f0e75ecd31e8d412ed3604371d006b40fe58402b8fa6ddfcecd88044505a6db9d0ba53cb188c7c7366a3aa582416e14cdcb8bfb92129ef8862f0f1856a3bc76c0142234476b4e285f131584c36d30518ceeda59321f3f17b2b8cae936e878e1af3ed49542ab86bfc48d001953735c1ab0a5c86fda13059f7c9da54678dff0e0fc0cd169007e2be067d718f23aec0df1a3eb80dddb75f1faa9942b6d36d8fb4c9c4392daa3285453644285ea17d089039a4ab78479d47417dfaeb5bf7527e93bdddd9056a6096af72d44fbe8af263e8e591be176048023f507480fe7eaeb2e0a16c206bf26b0971fe3566741926a05fe0aed2d0868f959fc5e5be0cb8c85b76528795fd57ffe7408b42e739ff04515134629c9ed634626a1f10b5297368605340f297af874833985ec26b3af082e57f3033390df47260ad2b1267abb6a4241972c88b4a7df4273886598c96fdc4c98cef2037c02be5baf940a46d1d414a948e14f3c8e8c9ea68a41c378ce4bf4cb18170321fece0e4d9e44a279cbedb7ee21c9bd2fd7a6a83eaa29db89baa516e9a4a3fc1dbda4bb65a86e6d80f007d8c1a5270bb2442ee3035bd4a7397e7199b3b37ad402169881ebffd8ebe21093bfe706136289d59edd8bbc69f55848a664b527c99835689f43cdbe9d91995350e793f9f97c3732acf548f4dd7d2adf8d690bab1c9c257c1dec128f0c377049263fab808a7ab66f5e0d4ad2c7f4a378a969b4d52d2e5d5a984dc2f3b0dadff627f66afb8d090bd4dbc1f8afd1c1e260c67bc44408fd00171617a27f8a551859daf9036725a929f9bfde72b9fe22666407bad453e2cb7a959ccb1c7d62a8d826c4e278c1c74fa2a984ab277b5727d0e59212c4c75bdbaec37e7710a8c7146ccaf942d6f41d83b35d7a805c226b5c538803d2d5cf067502ff4dcd13f52250895783cedfc8dfcd2433924e0ce9ebb701153943647211a35090f70996c65732cbb8ef3893c55d2ec0076c5a9c30cb6a66361e5a6d65ad4ef7b18d9dc6288c7216beb9da0bd0d17f5289bd81f5f22f7cac7afd5f65b558124df36f5c5657e98da793e689c11cf346eb08683f2ffd908ccc736aa3cffad5e3996a2c4e4729144ba8b6aaaab72dbb2b4ab6cac0ea0ead0b1c062d27c2362f510d12a39a17c0efe9094059a7e9e113690545430ceef55c51b05944291df59a2da4faf54a8fac7c7787ddacc39c67811b4639a698829de30a1c8a19eb1d9daae3b60a7976e368f81a5c5688b4678bb6691d66d063d0b8b8745a85234e6ca10f952109bbf21a3c1047ac360f3ea93a7d61c24502f53a127cb7238e2c5ef8e8d2c59e2aad5a361ba5074c32aead3a9afc169d59dfda632c3ff2bdd83246eecc38adf21058d69ca5ac7c2a261dffaa768183d71fd415fab2fb06955d99a4f308b43ef5971f15578887994ab535f91520dbcf9abf19a45598582d2ce7d27d34ea25d8b51c865edd8ab8376698275dcd6fc6e88d74ddb9aa99fdc410b7b87a77b272f576278f279b7317a50e425df29b836551da1b7c0a1dd9c98ce1dd410f1111f6663f302e4fa910be6d130a70d4972e304fd442cfa59585fc94389fd5c4034f1a063607cb9f9c72fd1628d27f93170cc26a3827c7e253d108f3d165ec3c3c3eb96ce9b10639edc1563e952f7c2c360dfa6ecc5c932529499fb5b8bd5a33d2dbbd0aa687bd09c163b8df6d81e28c586af74272c0ce1c10f29073e8e30818911f6f0091f8fcfd5c1a1c72b0dd6c3905cf2d32946907e2ad7b5943658b6ebb9fe948c748cb13ac5374eddebe50e2217c7013dd3ba75c2a8787286ed959f3b19467293c8a75b77179dc9c8a808e8a1201f5bcd98372e0c9f375de25ebd971b977acf484c7e9b4400f4a0a060f43c28f98a2248ff6e039a1a79b306ea613fe36c4acfcbf7f592d92cbce5931bc114a2ef7d9ca6243f900f39fa79ee6292acc10236fb2f37bff7536bdaabf67c0d2e5753b6b338bb7c444c48d2f5855719e6b75f1dbf25a716927661eb8e5eb9037d0cd9bd76551849e3919aff320ffb8b6b63aebceb328ec5c1b7ec7528cf8377a8f691c3969b384c05fb486381966970a998a0f7337b1426f69f05ab9df6c3fe8a7cdea1eac1dba595415e9df376356887cdbb42af187cce36784082107a278fb0c90606ebf719eb52d52330dfee7038dd53d8905b04dcec9b48450b2b52e09ade3063fa38d6141051428bae62c4a5bcf7635dea988e1f4bd426d9bd9c3722df8b5e3e8714dfa5af79e7ed7edaf96c4e16f7033ba9674c69a0d4526b921f3117605bdbb9e38e2cbd05fd9ce86b9298efef712af23738511a56ae943544c8128fbc5e4924b1fcdd4e06ebfbb77a6621912a1f03c9cec9de9561e9e5986a5535213371efd3aa0a38d9dfef1ec234084b25e47927615ebf0d00bc224527c2fc9c820adff9cfa3267ae6c71628f608b8afc72d485a75baddd2fd7a3d0e0d8cc26ae1bd7a136c909a450f4a7edc772969262a8b8582051ce4830b40910dd80f46cec17aaf141f0c6dc7bbc9ee2b740dd6b4a1761d293e9ff4eef0c9685a43129eb4b5a3639218d4d10dfff5c0a4fdf0a4ffa2d97503dbc48cc6dc61d092fb6345c60350ab76834c2a6bb058140e44529cea3997c9546bee0d64676bb4526854178692cb4a0d3ff89508c1cc55a2e775e75a0c0453302ee6d522fbdc4f0089385ca37412092edb04130aae0116a4fde06a4dc46846e63357e7d5e6bdea47dcebe5d04358d26ecfbd2ee6d9fc2f4799de28b351809c251153221aadace8e01a483cb30f84aa87381646d389923f285100b6c550fdac9cb7cc0c87f95b277647b0d9b706b06f52e224566c3ef167cc4697bdc505029922f71c2b000310adcb368977e726ab6c1008a25cef20186ae3aa3b9655cbaa85b3c17ee8a80824a77b3cbd6a053578bbdfff6e48b7485a0b469647e6e1847fa6c98b920f3d0584b8e41937108ef0cd778af90a7ad3aee0cbea545a58caa20418dd09ddd2cfaa339823a66a4966fb95c119141cd4debf23ed4aedd75f3c8a2d8a141b46653337c6ff31e7c9084e346e8fd650ee5b789e75c8f1c3660df2bee2ba70e89b228bb7e6e9847d50ee67532c51a255ee69ff52797530b871f3460180c0fc1e0a48cadfd220c454faf367705462d1832f6a7557a7c02ffca81a55af5724cdb2a83c21b1da1c7585482a3bbaa24cf1a3e72ca728ca71da377d060c1c2dcdd7c7ee699bc8e532b334c1843a1083da244aefc6ffc781e2593ba9c9733bb9b9ee3436a46f55ec124f086cb63a5d9cf9ff214602a763c1e41ea3a52a8ef7298b67944223c5647d96ef76571e0b8d416d0d848f5ecdac51f9b05432d887bd9ddf39fdb60aef6639df0dd514c435b5fe5068128ea147be4cd6bda8d1ad2b57a97c9e4e9982609055c5e68037641292126b139daef7dbd7a33d236c4c08caf0d749796fd85fdbfe1d96bbf8d98c7ec9ef4ef8397ea4e29c9923ed9e2b2a0640231b1802c35c9b596d22fcf718cacc7cbc4fb84f2f431cf7452582fd40b03b1c7d4b10d48ee17ff9361d34f194e2bc93d342b94d1897df38134f78ed1c166e7d4a2745c1d15c7684ffc9d55bf4238fd2431218ef33ec009c4333cc5c974af4c2e42661d8092ca74d61fb3b565fbf843d4f1f6b319d0715c56950747f4c54525715f94b4e151427ba8a617d0d04365f84ec747e9b19a8f184ebc78a1c1a06f342a05d6735b9492f60bb28eb3592d5b03047ca730b8e968dac86648749a4c3b303b8cf9cfc1e0a1e95cae754a0c044101a3d55f0068446a269caf04307d255740b8700867150c3c1a4a1de6ba1c61f8133e26c4273ac06b14c99ddc4314ecf1c9ca9213a60bfccb0fabd84df91e2a4a31dfbfba8ae55c14dcbac3800990881cb53aeff89f2bfafaf1cb0670d27d5707d0983c3263ff74cd1cb0ef31ce6b3fe1ca09becb49f080068739f595ec5634bed5f2a6d8baeac4cd5899f77e4254533c07b697ae0d436c4f184c4efaa36d7031c52d16d9cb6b2a01bed9291606e7b85c84d8505dd7d4a6ef172ed5041607c5d7cef0fb88793ea66c33eefa2df1a520c707e4f0f013d27644c44fb5366ad250c2c9eaf3e0a2e5c8bed02122bd9868fd33b599b6825701618b6c064ee6be4a6c0c7da3047f4d1b1e8329f2a9d6c053930b420332c0c1842b1976ab2f2b605584504f5448cf550b7f88d60bb867410192523cc5289bc563d441472021f4ea599aa9e165130dbc4e1149a651c06684f41d70ac42a5e9d80799b2b52867bcce84a837542f37fa99fc5464aecfa908423821246165fa258abd08f3d270309d1f44f31dd2178843b5b9ca87984d30afadbf4dd04078ba1d13cfdf06fb454bf451e13428166626652e5c555f7b27c2b139272201798b805b83b21feb87be5db17a501db643fe99f3ed842ebefa29c3594f21b55379ce9b1810a3f6e904238803b170c36d4df76ff8cab3998d69d2efdc9e6d34f4730916cd5c4bf0889db64a014bc282e595f297a614b74be2f24033e0325bf11b100195ab0aeefcd541391419f91cedb044a55ce69f0e222dfab3e426e2dcdad5d955f47d0037daff52d7d08f1757c41a010fef591a514aedb3ad6d8941d2a3df4fa32c42cfdfed81c77fa17ee10330896286269e4a3c0bc6a4f179cf011c9e35284d70d89116b88fa066d3f4bf3f74ba7136d314090420045c921885182e3e4730b87c9684d9f760003517937295378bdb743caceb116ef46d168e1102371c7300dee8ef11ddacf5850b2b94630d2346b67bad5636b5fc58e0f326b4a5ab48a9537ae42831141bc13e4b30c78d98761eebbdd9d309d64152ada1cee9545aeeab586da4011e05b784d9b080571218623c5e96a6775170de8b468960efba1a6b436d87b57868495fdcecb7c7a48726801288955941af26a6d8c83627319a5a82bfdf10f91bd4fbf3aeb8b6d987a13cb0ffbe343965b702c3d7797193cbffb037db65404cfd9a48edc9a064b3ce0c5d02d08bb10b1ed122a27e590a6c767afc94429d74234067e18a6180f1da4c3aa9c51941c498d4c9ede932864c8ca5ca9522283f39e5fe9ef5a59808dfb6c6c9ed7e4a156b39a1626e5744bb294b673e7b95c14e4687655a1b52f324c95e1bc1f30317247c242a3a183c5618cf26ea13b5d972b29defefe936de2fc23aa98bab7d6dc174f23eb3c291d9b7bf11b51de9c629603d6b68a44aa2943508392631c9500772ced32c8a8bebcc59845218a14a52e13f50e9831510d7664fad2b58c37b96714fba5847fe66e910040aa67bd12d5eca05e77a2db23f6445231491266e6a4c7fbb1225f9a51ae6bb29bd325fbbc9c0ca1f4e9e35b4563675aeb51b92742ed62626da03dc88e0b423e3eafe506f6f2c1476bbae05a37f417e5c7d1511e4a817c382daa4df1eeb8b0e3eff309eefa7d82b0aceb1111e67a70dd0a2f9329d2b2e00508caf21832f7732f287c43ee2aea89020d3a65d1a6d2f3a96c3acc8a524659d62c564210d41fedfb141f1aac751257a52e95ba638d3304b5c0c75f0373e63a3686be88f84ce8a40151966cb9e90a2e8588a9662f22886a85b40441436b59d73e10c8c295ef7ac2f7abe0e66b1df39567c065b135c342d16629627ca792bf0ab333f323714f2d9544a67c8e036cb3d84f4bfc58dc39cf4ba2ea32ac48b9f3af2bb8e292cb58ef103e82c73a21416d86f305be0c884f04cb46c2f8855924df27541ff92a60840949fa0cc8168dec73f13eb74ef1066be8a9812eb74bd54734d9367b8ab8f6d063f9af80ae224db818b1e9ae201fcb80b321873989587e978cad9911f140c66c5affa6225db20b3b72211bf2d825f5df37d30a3626992c09578f6a5e8b79a73818ddb87e5feccd208ce11ea839812ea6c2444862f7116c225ef23b472a4c178f8a3a6162d04dfae3195229b9742954e4135c9bb3de566babed4ad191be5809f9445e5ef9d025af30dc1d2cd15405f1bd45e7a84cd50f3a61c991bbef24718aba16b9e1ad5d1669ec65adba387f72880d59141760167c574137c30531c81d3361a75c50ae7f99f91bd3b0fe352dc0b057a5f9d581a1ee90485961f16f71166abe2a7e933a1479cd21519abce71fb5fd16990cf8a87d4a30bff3f2c24706cb76f71ff8f81aa555830e0d2168cc3d09bbaa0eab1a644c8a7a6c867e9e6825de63951acaa3e21722d2fd39c248673de87672f9965e026b3c7e4b5011741a8aa7b1bec7948cdf4ba7bfb9663baeb5fc735ce0556ff0b21501329b6e3efc80dfafd804bd1d12da1800271e470f3566a93f5e8c164121ddbe0fe363c999c4b234369c6f21d01906831c8ff66a051b04aad430923ff60e3313b0f3cbe83c4cf592af2cb0ec3ac0c7623f515019a9a3884c2f15c480e0dc40f1c1e9d51f4330b04f3e1aa64b8e992d31810f7bacca8c049b40737c66317c7af134cd61b25068e9934117a0b4218242244c7b3158fa92535b6d4418a882040afa8ed68bbd07935c3bc48a0259edfe9848e16ba43811060c2b0d4e6432509ce0bf696dc2b293527f9d0bf33ded122eb2cfd44462e6ab6d1cddee2b874b7af1cb04f3762d2720b9040b0c183f335147225d8cac5490a53dc9bad02a3295c54192d86ef440925ec5b1caf20dc34e8fac94b6c4924ecaa49afc0de8f222c158f35621aef1945705ef94289c0739ae588a11dd0d87e4d38e2e1a675e02408340bdc9359ec35c214bc3f2ba1bf91b3e5a0827404a5f89180e0dc964cff7d9abed5fe261bb9617226f917958370aeeafe509d86d322e290f14a86560cbacdb5bb3ed2ac5974d544d32278e5bebd83fd9727e725cd189bc6c53d40d465820f3f34aa3da1757afa2f8599dc3b6e58b6d921942af2d07228ed2c2dd780b6d31fdb64f50438a36e60b9f2d34d00e07b5fb07198ebd1407a72cbe1cac0dad78293e17c14f5320004eacb2fa3f9186e903be2454f91bdbdaff428660f7e854d916314d84102ea1979248819325f02954c83196d008615adad0d09c518cc55364f7f725e12d1d71721ef831cc66baecc844b6ef3750552337527d24f6a0f8dfe3b2b6ee04185c7219d93ecbffbebe02f7d08c9c582201757576fe231b39f42292e562017d4c8057aa353002884fc1ee48cccaa3a4d18f98ec60ce47cf9a01faa17ad2bec01e80a85791754c49dc18065560a7bcb1802ab1472ca92fe4d225f3b61613b1a5109a19e6e151502718074f6d13cfd4bb822c1106018dba7349627b68525428e8ba9eb930b4fc568a011e90132ba7f51e4c5330fc7b36a15566c3e7c5c4eee3aa455feba27aeed6e14e441f7cdeaa68ddf2cb70d16453f4aa45effecddd5140ee704214bf2bf8841ceb2a7f15594c161e3f40fb80b4e5c0567c26edf490e2f11d2d42689b30409c30824522fc8c035eac4b451cdaeb90327fece818a4cf148c83c3d34162a37e36971d98ba82d5741174b6cdb702bacb0811aab022b9d349f3cf8e7c02cfdd129fcdd0eb55af69d9cf08618199f0f22f193aef7476033a3d5f9743158fb557cc84d4753c26be9e13ba1fc11ca3eed562e6ee20e739c7ec0ac3d2c8fd187ad580cfb179713e9cfa185b13c1391c1da44f0864e39b0e3fde17475ca0c30b97e6c9a7edb8b3bb6d169cab613024b1f186ae3f963e92131593670075dffca419b483f0146cf2cb0851204b2dc49b00b1c61194c5d2cacdec7c283842b759f2231fe70a392469c27651e163bba7a5f2f850f3c8f41ce123d72481654b8d2eb06a2f593f70d738f376593832f79ccf74378f7ccb68cbd864e025e872fba3f8c8b7d1498e53fd655ffc6769d3eeac86a2308d02782e22916683f7900724539f0469e6bbfd7ebbcd52ae2b02d3536863d27c6f703b956f90243088b75478d073ccf09f50b11322e4f566c534e9fbb78d7b073a1aa2af9019eccca7d455118831c75331f03b37784d7db35ad87f613d9a688fe6b1dad585bd0e3042058ae9048176abcc69a39398a4115778f9de2343833728cb58912017db48a9692eec6369bfeda6f1a47fd45d00c2ca442455823d9fb8ba50074cf188be857631a856c1770c30050c3e782b1f0ad4b682d24ae6dab65881f9c49e6960536f4e58798eab2ae1c11e62a824cd3cb78c7963a7f358a49c648db76a23f2379a43d7c26de9aeddb0805a532ffc74bcf71ca629633b072ee89e4093e91db4a354be30c691e1ba0557e1b8e67094dcf5ebb5849bb89fc4d7b652d3a14f1b202699816842dcefa479534c49a92be6474f73e88d456ee3851d94c8d04b05a76934414c048d11b7a24a176db2dc79b1fa09265cf08c75eae577ebc499e3fc66064b63797d2b29dfd12ce4efadf5d363be1e0c9a1092e199a2cca581e08bce8c32124bb8c05dc000b36f0058886a74135f2aa31f03e3e7491e6fd4662d6efa8e6c92a1a8ac7863a7a986414ea4d1f63c306a052f14fe5433e0b08ba05c41cb8af778b70a9710b8c07d212f06f74f4cf186c843d942a41e4510024f921c2093845b6c2380caf3d1c5dbd4401e8a575f0b38f94490bffbfdf9fea1b01271af3cf9e270266d00f53c8bd352f8adfcb500d626a6bb2571823b199bdf064c32956ad91c5d8867eb8585eb5933da0afef2f743afab057d74d016c9fee958198b3e9f135c5edec3af214a6fc54c872bd5fe507d3965065e952a74fce030e5b87df62648388d6d3e65871fad549c7876dc30410eac094ec7d9e4b9102055e3b7594cbe1e0c921b719ac4d15aa5be1f38ff953d75f71720d6f494076441a9630d49ca2efb78591fc4cd0fccf03cf86df2408f1c278ad32adbd31431c7e7416e74c7fb5b7bded863cbf472b14f02ce033635c05c61dbd0017b135527f25e96c33724c47a3ee5e2ce8ef745700fc32d8bbb884deb4be191adc987ffb88c13b1e0cb02056df2f90bde0f34795afeae26fe01766b03fd9c32e228b5e9d4f018f028c6da947b89c89e4b7bfaaf0cded931dccc513c01eb8b25af505a57b886a45557fa7885e8c7c6a0add90e2b2e9bbb0a907bfefe41393400ba52e20cb37e916899c97aba130eaae640be29835314b0178859788d6b169f575698f0316bb7dda4f5f8d4cf2b61ef38526777338e037962c3b44b2bb513dbac0cd4dc03cbd2297cdfb64e50a5395488f7ff19f472c5b920d5be19a40bf67869629f305802d73de61ad2043cf28b5c320b37a4e4a08779cca0fa19ba2a030cecf4052bef453c82815186e5de33d5cf559b2ba08613e0a4cc503f93e40536afa4398f3e9636f38643fbac36f7e0b237373fc30b88c9ed8cad4927655e1a177e417570911a1ac37cb97c930c2c9ab164b504dbd35d8aea63e91b188184842e1bebbb707f0b505ab9d89c31d5d3a68ce9cde7675faed484c340b6ee6c84f2262b910cb1154dac8e385fb4fdd00ad40c9c310688078c3f7027a35ac1c15acec1d746d9fa977aa19617c438269019b0121ae900ba977fd8596fa71717ac2139ae4caf8e6ffc18657718b8848a7d2b5c1d40093eecd16248d56094c86a70afb12d3b7aec736d99777f2136006752556eccb377fcd9e70ac9e622f9ddcde284803259f0407aca2b3a68bc230a4ba0eb9d7059bf6b3805c33585c0d57005eb0b434de08fd9de25617ca4988145994a2934bcdef4398ac596c0ceab53628a092630500db93a62449e2b75062c2c8507267b0930ab490cb86c16b2380e8a6bce0af007a06af0ec235e8e987c2b093be41ca57a04f88112e4f0fb7c1deb342f607621cb5f0e0a317044b5cb1695bca1bf02e66b4f88ea57414fad18f8827677f827e6ea9255936d0e000ce110fbeaefcd6cb94dcdc485393bbe8abbc8190f5b63b0104088259c1092fd33e24d6d7a2014c78e027d069b8f33d9db7e7227e81a8490cd291194a134c9e9928ed3097f2cf2da554822db62ddfecab3963d70691ad0a7e15303f469fe30043f8b4d1df9d294a5a58bc45947db644840e156ad28c7d0f2d783b6f1a5b07a4133c488d306a1807e77bf2d872af4a25a0d224dc5b56aa299b87d87bfe8e20e3ef5d1f6776bdf0c8eb0796dca642ae9f90785969485b7794e23ca5852c9a9870017b22e468e04e493f3a157716dfddd7749f40a463d4019fce6c80eae3ea6328c3ec46aca524c88fd747501455f5516baf562633be0ee5bc47004124289e293439dc807d26c21cb4ccb7f482ecb2da244b66ec385a2ffaf4eac2bd1553b1be01a019008ec111655cef56d4aea8ab581149ca02eee61f995662f713c853293849ae92894792a4422a0134ee9ce85dd284a72250b8e079c23eaca4f494d94d99eb14a98744e640f020885e34f9f99dcffa3cb0ca7f753c38208dbdd70a81ff97f58e79796e1c86d171a713465701903e3c854fdb2eed7ca71d0f0e92ebb656030036e678c0db90d59afe95a5a9d9454a46235d4bfb01b16c10eaf148590bb72eae3c0780e6170ea15ac27f122ccd7c288088ad4c8cd36fed77cf4fc7ae6f6199bfd13a373ff71226a969925f4015a91c6f940e3a9905a8c2d3fad5c960a48e16806cb933c11812dcb7cbea0cbc7875c0ca8e3caa0f71017d0b3e5a566c6c219251a41678ac406e2802c07ece75e9e7cfa1117aad43c320ee390f848fc5b6849df3ce4aea3dd071424b837f649050900a5eddc7ecbeed2ff1b53f24d6b92c4ad53ad4ead71d70bfa284e03a73a4a724515a3df37f198ac2642fc83544ed26feb7407483c029497ee4f5fdc30c472fb51c71653d418fcc2a0b5211f5f4252081ce836d8a3099d1d97301533f537ee4b5c2b4434e2f3d647c3e64a15076753744c8c39d6a45cf485f3b8189c5ca6da6aa927a8f119993effdeda9fd4d8e36d7f25ecb19c1c7474af5ba5a899f9689994c4927bc6ed0887a62eabe79ba05fed24e9cc49cef2f0c08cfa0b0001aa97737e4fbabd52a2a0bb1f9707d73f61883d1927a2e4537512c7bee8d30c3630944eba24df0e3aeda4a76357afaedb878f66648b18a2fee140cecea23776b7ea81702c4ab55ad325bd6e7cc4f92987b0aa991ff28fb077a084186a3948f3b4e5c554c53de65233cb76b50ab44fbd6bf990cda77b97ef679a0d13414468f1a97f8c90f17a8eb91b82c1591746ae24d372016bd7b478620133ac166c52d08a363f45c771b3bd7b99b470bb3355284209b43c091907ad72549a58b8774d0bcdb04522af8c1af99b5bacbd942af2624c2bfe873a658c64021b9caaa97a06794f28ff9b25cd4db9ec0c58c262c3f6174d3e9fab687d13ff4ee3946229d7eecd19d64deb53bedaeb9a3aeb025e5ae993ef267975c1f59895a3c4ebe0038ad090da327491e3571d6e88912fdf024f21389f5ca93733e0b971db074aab2329349c6d2d6ed1420c3799767cad212a8b5afcf5714f44792aad92b46dcdeeb1f304a1b23b3f31f9aa2ab3006dc975b1a4ace97acc59773801212af2fc9cf6c695eda3b33ff8fc0b89bd78665992ebb35b289cb02511ad2bcaf1204f829bb5aa81da7a1b7bddc272a19a9a26394578527734c773afd4466087108e402c6e42a5000950fc0a22d69b9f583e698a78720c4321b0a9f519a882b5164388ecb0575f5d0c27e6a523cf0006707b3a2a7a17a82e50af3ed74cbc3af7f5419789fdd2d2c93c514d96ee07cbe2e2df9409a906ab52bbb474309d6f981fea40c0895d34cbe215f6402b171f88c453e93b897bc362db9ebe81102133906d542a3a62da25f6f224917119a379eee08d90668672387d9c84657e94f37b870a6f9df29322ee835749738786d2bdb19f0cba3962702f314ff851a4c12142f11dd86c30a0a5fd57a8333fc9b8155567fbe4c6128ecad9f63cb9c0dcf84785fa07cc1e06ff1b65c985426c12a561f07a6a8f03a266f34ad05854049fddbbc44695866d94812b5bf32d16c719d5620233427536d567c9fa2109b0031fcaff85bc3dabc91ccabbba953b76ee0760c343c5c5d018920370451b2439b6cb2a06d161c0c4d3e3c3998085306fd55defd52c7b5363d640a515ecaee00eaa1d5b7cb548aee4e2e3443c4fadaddf452b0a7d7de661c18f26ac31921bbec182b5ddedc38bb62b814f43d7ed9d7a0758952013ba253f78da8f66944004a4f772ca09128e5ac58befe3486ec4aebe0cffac116da938c41d3dcb571ae218bd8dc6e887ff311648659d0b0e4262ae0dd67c9525ced5646e6eb7f352f3c84ece9e59003852c97d28b9b4d066c8fa222a0ae8fab5c90eaa90fa378b343526a90c2b1ec35a89f6ea50898d9feb58e21a9f80aed8fe9be7468f8722e544e19491b3fc9b5318055f9ecba936d907dc138d93f00e90d178374d5736d1608d9a8ec0ddca6943be132b1ccfb4f4355815ac4979359b4e24ac994684b41074aa0af5b5628ab5e14e708898490cbbf782956f7a7913bdc4b72debe8e539a185e4598cc1ba52c2d93b6d2774bdd3b8ecf3fa43b3c0ff302c4d76d3da57d8b797992e7c9b2b3b652166c7bec88095c84126ecb0920c13839e26fa35881db07bc2415fd08cbf1a8837bdc68505b2d6fe24662166f21cf79bbbcba9be0634ef8b7e98e1fa27c923a7b217e3ba9b7335213834e7b6a7ea512da04c3f5d0a4c1e76bfb4884c91f5e9561f1f9d1dbb40cd2bd3b4bd17d4c5eb472c7debc4e95d2241de19d380572f80b82e699c741e6d85cb16d51ca3c5bd8fc799e22bebb20986e5530b6131dcd3010935186d08e8219ee4ce0ff09767f46fe2624d2f8e4f3512ce43adaa5f4cfd0816f89c68268e69480c25fc0fd742bfa99f785934aab99a795dd6bb34ef28e349c0cd736ea0d71c560e2310db670eb85f5ac94eba044038df6863e3d1295f93c97d0af71249e8c21668054112f3cf1ec242171c32a2dad44bb56c037638f185ecd7bfa0d37d5db646ce5d6b49488c2f48da1e661a4f8f0abb8268e6bfae2ecde7ebca55b1967a218ea505ad8ed8874d4f7b0de5161be68844005c38a5fd7ce4f5ea32944a7865a36d89e01a673b17264fd1478aa0d96d810ad345c69bfeaf5f0e9bd79e300673b09ec76e7ce2a8ff2cd45a802861a0507002945850326ef2fcc5d76f00bcaa83ffdde32ff7528067147ceb1d982a4bf8b2614f05794a6c3edb6a0cd8ff15d16f96ce14d5d328c45622422af291bef9c29e7305d69a0cfd65d766bbb0df856d844724c343c78d9ac785831440ba498dc709e32b188f3d89ab5170fb4c0352710fb28c1068699ecc460d46791296892eaa770d2a57afd55beadd17f3f052ae59fc17de4dd7c38b4824e1757be26412c9805ccadda69b97244fa0cf636079429f809aeb80422282a1332301138e180f8ac7272d16be8fec78a0589f1cbdd4cb47d8affee5b88eed8c1c96f28c9ee5226765092d18e1507b4f175e5683b534405412e45d6abaa397643baeee06df25c995c0a2c91e9cfec586c70d331203c09f50ed3679321a9e25a711aec0f44287ad10e31538d34d509a5c986da5d7389b55c8c3a84c13b1d8b07746b0f79e1cc1fb18af24e0d9810947f5cc0509f4e12aa0ce88c84918e01b8f1a37c1eda0a34993ae235275421baf58da1c33f01df00afb76d454a1ffe26ae37b176af569cbc5601cb137441a4fc716775c73e11d7a15ee59004216bbf2383ba6a24fd4bd692ddc6a9d80e0db6e2e1a9cb56e3551ca12f61554b79bf337304f4f5bc62334adddcfe9783dbcf6824a1cfd6fbf1a2bbdec74ca41d5df828df4f94c5c7eaab9e3131aaca281d551b5278eb249baa2b06f7fb89eb2568df5128c9a17b9a8440b918a0f0add7c93c48bc79db75fb920fb3d61303b635f7e108f57634d6e387a3fd532831ef282209bf22c0f6da6ab13b8113cdc4c50095349a2c48931e220753c23443c4768b30a1717efb518f2ea47a90092d5033383de4bc923e62af5b3a33d471cd1a84633b9ed115d6b5e8f9add253b0799960dd2a5752f4c5640a281f553bda972a9e92a50c5617c14ce6b1066944a8b5cbdc7873b5951f720a32f5b4be1575a3174e7f9f1bc356caa57ce0a59f49ffb5036db009c3d6ffeaa24ef5b651e36c8a9790453e8e287f73ecd9fb4b8577219274210fbcd9dcfe30aba8e1954960a9fd4ea373224bd0760a66743bad963edec90254a97f3a7d1516afff85d34a9b173ea40c0deafc5d4abc643fff0ead0afefc1a96318a8bdcf79b0b1c50cc70b1888e9b4476b22e608008ce1f45206ee9998219b1bf1fef7d17ca3313a16494271b550c71866e00113d228a4a9514b1cec227d4d11f3b89e10488a07da99c92cbef60fba62abf96ae1f997551fc1c052b5aa0613d1b04cb2468f5b5c879a1e76a34d84178e8bc06bdcee4d9f7cdd829891f858c6f0642f65d4ceb17e33e6822590fb9126a1b7cac0b0b9a594452d108db89688443cf0639eb17f0f601f13da1134499578b4a5e81aea64dd2624425ee15b0904edf1c0b57830e77917acb491654656ef60775fc49c5df664af409abdda5544a6280fb52dc08f2c145ebb2919219cab6975c4ec4461643bda6a2a247ad327b8e7dcac493f7a5d9e2f13a0a6f3988e89881246cc87f4e5f14af74f2794b948908bf76ba9e45ca95139b79e5c7af2b12c29d62a2c3ed632183edb0793e38dc3fc7febf16b6051078508f2fde07eabdb827625ead47ffbae6d94fd28f675e425a1e92c7518a4f76171540ea7352012790a54a418f8b14d4c6e872b33a1fd6abe827bb359bd6a229d72837756154fcb35d6dbc14c55f428d552ed9307b5b65a2b104e7edd2eef5708b298415c65b04ef2ee71281367a3dc06e06f4a31203d08391c610dd5bec551a1bcc0a2eceec6660a8e3c4ff389e22f7c0e93c9bdcc6c5710e6d5e7777fc98026afe07300c28fb2ebe985eb834fcdd867d53f6aa5808b43b588b7133d633d85f9e196b3c5e30627fc8ea9798f6fe3051fa206efc332aa59d56bd99fc7cb39b8d13e4f04a02a5c8cd07eb4b648097fd6734674fc60d6613ae68fec1de3593fa23eaa682cb969fafcb28fcc278f26a9dfdf67a39848dfcd1c94cf6d2190105083fa2aec96214ee56879c6c89243b36caa7eb9abb07300ed48807078c4201d40cd0cb901ce808f5c13aca42183ac5adc33d88aaf9440e75e9d4f9dd92465d607a6bcde210956562514fca485c8eafc5b6a7b750f783bf5cb0c09a3fae2e7c4c06b5395fe9ac8f954d70960ebf67dae62b46833bbf5763a221ac40b59a23ac69f28af07417e0877fa9c7b13f0e2d873d66b4ce8ad8574323961a7f04c8e529dae20a52485d13e448ef76385ae28062b57598967630a9d055c0f371dcfd4f4813f4b342b6f570e61a24ef87dffa75c554a4113417b474951b065f3ddc923338115d3879a52f4a3c6e65e9751420244b33d57199fcd655ab86d3947944032f8c00496c92fa534579938b71f35612109e442c2803eed7062be6074c669433aa4f85da7c4db63c4dabd1c3b9656c2aec27b3a212f0c71d85caf4195bcbc3577b9b0d0cd57daa2f7fcb3b8a31cf43b994541a103253167f1f695fe2d0b2067aa981b9003b855bd42f8d816fa37ebee952986566d68970273688235615808609a4d996ef6823ffcc053734520677a2098ddcafe59bf31ebfadba3c26b8d808c8e6b38548aa14917ae532aaa48092fc95fcc201a313a90988767f67319a57f20d4f9c5ec56fa28a6f0f9cab57a943994e790fca1588e8bc5fd9487aa4dcab92a3e703eaa23f48b738b967cfe121ca277f53a6a06a140115ba57a93ef7bc606cca0c3b14070c86ea629e8c6a83f2717e38fa3d5aed69f5a27aa63fc492ec0b7725382ec90f1a5b7e198cc0bf285d750c081d1d8b6328f18569e08df6e834976cab500788f0ca344cd4b8156a13b5f4225da6dc27be7205ee5f35745beabbe86caa795c3155a80d5f6d773a21d0ec893a56c48c6b39a00544283ce395b6ef487ed0ee788b32c3b7b9c77a2fbea7c091ea64fec0934e2adfecf0960bd94a3360a275246bd86047077bd898c1a5a5a6f63f25e44ff3ecc86a159a6ef6143b996979ce11ca2d8178db5c10fb35d484f503e19b84fbf305aa288595d588bb5328a0781ad68c6bed2d447232fa942e8561440c340e656a3ff7a438c9605994222d937b2aa480b47523092c8b3b4c0ac408b7a4a1e8d37b124699a7f9f1b7aa3b4dc84e25199fdc5ead10c298afe269c3093ad583c5b3ed0a7edc8ab26306ebda50d9b2820a116b7a1a7c8e9455360811d0f1336caed02dc57ccc62cc092f4a1c7e27bc918cdb53eb19d1f5a9b73beac532d43c0bb6c604a79f6a96066252c2580ffcff13e7de5c50ab13a1ce5f4ae37f2fdeab5469f6a3146456b87f3c4259248b2e0dc1ae13c58ccd15750a3b1fb086e594f21651041ba6a0498ffa741ebb22fc3d2983be47064d195f9ccf5c978290d17d630594372d8874cbae2a7e73c985492fc30b02a664021bb6a49223442536c930fba24f8e0a1b02d82937a97474b6f867188901dd6597b7a3fe315ddea07fb86faf80d805d6feea9f9468d6ed5d948cb1c987407d94cd3a9be12da583fe47c2c7cda20226502bf7f4db1b0ffbafb3d600515f840e731ceae5cf526237e5a88b74646ac8fb94892c4f4c5981b83e6c07695bc3d6d23554741b2d0cd6f27760a1ba869c368c2fa4c4f41ea5b299bc2d720ffa56ac6ff30cefd98d3498bc6cec11ffbf9bdd0ea3dc6f2a210f60e829b9b4f1801267a90f624c857663acb5659c2cab4c2b5126da84c8827f2caa9bb92b921489c20161d26c1fd08cee10e265af51348980a563e801fca01031bd78a93f4815f648804c3ba4e78a482a3cf565393ec59f6e0620cd3f4fd20ce900cb4c3a11577d293183b342b60575a8cc1636eae95ff249d767f11746f231bb680ca73297b2ce5fa2a145aeace1398c9034789a94601be6c28322cbaba2da17de3c95f8ad8d8d37bc6f4ebd24182fe2de126c2bc235bd0bd5d11cdd8e67010b9a6fea6d7b99a2357e4b5925ddcbe43ad20aa16718476adc34e162c525069eba2d67e00b33f4d27d2b8684eba7a8ca5e4e5556e2a8ad5a0f49a8c2aa06320005482b557da9dc63cb78f994d4c94e7e89021973198062b708d6d56d4b127fb5050b4c22a99e9f74ad2b8619228ee22c31392596145b218870cb2e0226398dfe3fafab1ba5138c81d73841a0a9b1df885f2534efd0cb30d1fb458b9e10cd1ba3d1981c95bf80dc53df14fc3c4a2da35eb5a0244699b72ff880cabf9e0f406b24759b673fa971b02cb25a26cc97122f6079667326cf8c4f6fb60b0f635d85388facc878537cd4dc2b3eb573a9ded2986c29abddb55f95eabf03fa2e926695003746528e3544a2bc458a5e0cef231206aa17b7e25d2ca9ca71ffb708282cb7e58097c69c58955834b52b78a31200dc1266df5c48af36b98aeac2c5896901785ed804da3213c476c42920ce081e982b7bae9e43c4a0830905ee72d835c7dd937f5dc782df60a0e920b918f120ae747d0f1dfcba331e03f9d494e9ec1ea1c9274b30782677d8babd4df8bb378824d47fcfbc950d9284315bafe2ee04be42837cc714153518919b436d11cec1998272123f2b9bd7fde7a0219bd529c978ca6b0fd6e67b7b38921d31bcd9d344b0820a7c5327c812c38fca8cacba43c3cb0f75a40530addd8fed844d737d9875be3d35f70c08aad28686f80b4afa471e71535865ef3d1dbd45a705b8afb836750c84405fe0b2278c1ca8f55d7b9c3ab6169d5f79f6cdb275e9367fc5224de58b22b6b07aad4aabc28ed9ec6e5a782a4a2d24f6884e3951f36e6d421ca63a06569fb1808f307b876e55f70c82a5141d6f5aa5d044e6f218321921b58d2150e9aa931555d4dcb0a49d4366622c09937002f02bbec5bfc5af57086c642b6a09c87ef281993a5196afcb75810f01426fca5eee5c9b5542f8f80194bf915974a22c30cc3e0e7b29e9ef859008321c488528cf69c48e72093450e5da32f1b2bdee76744b5cecc19ba8ecb3a11e3f9bae4b7418e485f7fb59bb4f102add231ccb8498f778b3e695ed449a7d221198faf3d4a93f79da56dae47365b207e8049d61bcedd61347e18a76fcde3615d1650e7f03314f3a96517b96a84e57083c1e855dbb7f6e52811e94e9e7179519f987a4d10ba9c54d04cd4ceea9a0c3f214da906c425026a804f1d48c93587236ae5a2d99969c6a779f0723f7addafdc58edb8a152c03044846bab4ef099cef39b852f46c5a472e42008b18b988593ab7487577c96d0303794e8beeacd4b368802a6dc16e2fe4133b302c60fe46cdfb5fccd5c76595cb7fe6d56344389b248f130633aef7f6a1cc273a9d97d06ffa0d675a6b138e78cffeb29b421c0df4eb2ba38f12b39db3aaea8d5d68728c67d4672ad5bd3c30d3744927e364a7df0f8c7ab2842691bc85ee5dcfde3601d06c925bf934b544e31d448831d4dab8c1340fc177388fdbf95087537df536a0fbf8bf59de6dd5a738d9dcf1b3babf10867946cbf9db4c8eaa32b5588226f9c113b0653022f371db1fdc3246093c701f8fb6c553fddd7fddfa4d53fbb4cf864211ead3eebd2496c37e7ef93e87dc06ffdcc5099127e27a59ac56e340cd5db2281057c9aeb3d9d29061e4eecdde2d008dde8833e223d371a533659f74b1dc4abe2c729b0426261c85bdf6f0706238b49cd7a1defe5f1e28c55cd3d8d347ec5c661a063ac84142bd4733b2d3ae044dfe5999a7678142c4a7f28be90c3cdd9eab24eced31ed303046b95888ae4d1f88942960e354de4a6324b039f7ea8e165479fc873ff7aa5504fbd71b95ae66246eafc5f69b004dcb3209b94367f0e81a0563028bdf69821046290d2b0f9258af46361712d13d632ff76ee18cc400d98a0dfff399b693404ea94bf98ec020a0aff64f483e06422c14ff919c1f37b8192344519499af26f198f03b60a225a01ddaadcebc2389deb802420c4681185871081287ef99f504c456622f872a534fdb10b73b1cdcdfb559e51c5e89c0b1e04ab1188a2f299d7341c56d49a0eb713f3780c18648af7222b18ffd357f2e81f1ec88d11ef596797730b31aec54b0fc95aff4c9158c8c4df5cf0aad034ac5111c07f8330411d8797b811daa4375844b0b19c668692b2331c7aec104077b22fa41b90fa80680d550d99071879c3ffe240f9ee692b4d12163439e6192e6ad8e6612062c1b0aac8dfa10d7859d2ccda54fe37a7a71179614bc8f6ab34582e73df82061ea6863e75a1b3f008eb1fcecced93f46516d737ad3a372e71bac837405d0bcacf0dbbd14ba5ff82736206d6d2ba9cbda7e7645e48b61402cf4003cc9bdcccaa9b9b8905f2e344e80c40550422ec2fa3c11d2ef94db356ba54fb3b880770d8a902da67eea4e043aade083f1096f4cbbbed3467c7f37e6f3ea88e76dc00d8430f3718199333d1f3334f5bd4c36fb8a39e673180baac5b399f36f7d28a274fe0e680ed6b73c9dae5555ac090796365a2eb98a442781bb06a8c7465c12bb444a77eb2144afd882d96c2593b1ea6f7cdb3c1557bd5ddd43145ddae3150ab3e7f2ee9ac916716f608da945dda524e3362004529a79077f2b5550c6b4949c8d4616a80f452ca9730f3b0c514c9d18a9c1fd522b2f4fa09759165b757b85e96604d625c55c197cdfeac7eb845a8a70a8712e1040fa1840977b33120cd2e38fd5e5840dd97ef11f8a7ac1aafd62bb08eb2424b1c5b8091212fe316addda7cd39e6d65227455c72cc9e2931526641916c3925e59f3ac72db1c2cba568f6901d5afc6c2ba07b775cf2fb02292e74419e0ecfed664777c9584be8bffc7f0f15f7c23da50474d35ef688c80a213319ed03704e5934d0ac3f29365a5fd8c56c6d43a9d345fc115381529d18809e36503a34a698cba17c05e62eed18888730ee59f39cbadd7dab731db9b6f5c1356dce30a754c3da129006865d9d7d3d5259f02278fa58446eb935cb8c4c1985cdf66b59ac3497a194d56cbd7daa1c6f6effbcc67976650546764584f94c77161c501c8236945e73c7181914982a78c950848b344e981df34d612fdc59959874a5df76d841d503cb04aa0c330f6882fa026e11b78f3ccc589ebda45a427dc549d06ccf9cb2ed7f8fafe0754e2663f0132ce7a2f01211a0875612d0296f4d00119fa3deef50e698078694cc14814c5cf5a23ee30a671ad2b7bfaab98a64c0cfef3994fa7cd45df0169ea2feb4c20c38e8b56405fcfc311c78b315bcc16e88d90ebd33df02c904629e6d6caccdd1bb8fd7cd709c030d7f7f25aba5086dd2c94c7baed806fd7220b219e25775576f2a9ccdb61cfcc053065b2553f33b776a9c1cdb15c092efc10b8cd4134c54dcbe5a25a1a2452985ca17d2db59580218a58e0a7e6f8ac317ccb3d05092370c8e1603b5fced6e5ac290dc82987775cc94cd1e81959bba0183451935b8a08ca40dee0e9929cb3e08bb56cf8cf359ac93b3d4423e9d793976cee2d43073fabdf5f50a1bb64839fd8dd2bb41e75432a4ae1156e3cd3e17d332119e8b3c3fe1c5457b1afc29e3191b5a213dd407a024c8b5d06c5cd43939b7eab2627e605e47a0f4b1bf38befcc9aaae2e8301d93db8c9774b06ff281b8cfb3a31bbfe12bae1d0384ebe892a125fdff1f1179400ffba18fec4c590c0ccf9168315baf16613cfdaebee515e27ad8b958e7dd02a7690ba6da14338c8674878644d9d7d4f8a9a4ac76645f25a50783ac964f57fba697c56432e0a0cd34df724a647d75ba7948791a46eacbcd3d6fc211fcab2db097af1315919613bd25e88a77fcb36fb9ba58ebc0231007c540474ee5c1ab8d2343bca3932c4a19e5a6e47a6f8f6df595f5349cf7650c9cf1b8d5622e9d7d5fc2a0d8f4896d1b525f4313f8dfc542e0c3e69efa81a010d449cb29379575c233030426cef0f8a7c0d9b0bb008dd1538c915f6f8d20a355443fc359a6ed518adbd5b9556506d50c8c141a4a55acdd74fb2d6aa8363e8d8bbac961a9d7bbc667a5019f1d7824226e6c04a56b52266ffc6a00789954a592020807a38bed504e84ceca9853ef26740e1c10e682f4623a0722b40e1966a86e2b482e99e08abcc77bb139e2f288175abf4925b164d66e4f4b403b1a19dcd9c069df6dac6645fd6aa8aaf602ea4034d00dda1f26e25b96e680dc099f463224c0053807995f0df08e064ebaa2a65ce9128fce306fb7229e92d7128d02e046d4c5cf41ca22e65e7ece053de0d8dbdae283381e0c03974586069ee9e3e0ed480d35cc29c6a9774f930129e7f6c24609cbe3806f3bdea707c44e85b4d6148f96921a6696d972849ee2734e91a7e1c1fdb415056798e28ae1dea691757cc689166f5fae29f23d1902247b3c4305044231065934d833653db620878146ca2ff86701509fffdbd28bec3a988fd72c0a45503213305c8b0ca41f53ccfbdc01f206ea2ca45efb34752693b8caa5d560367f562309cbd176779418485576b9c0f2f02f361da5d8a59aefa6fd30cb9851f0b94e492ab383ebc2ee496c8ac6e6655011fdcaba0e6edbd5d096c82316a5c72eef18b08f3754d84c991a94c37f857664a4f837d2f0309f19317014449a070292275b59b0d039587322f8d3ed456bfcb5a4ae331f7bb05a8b99625e8e32020ca9134bf1504521e0227329c2e0e540fa32efb46246eb024495f5d21e0305f04ab79d1d086c1c76226d373a190e5aecb1eccea1f364d48b76efff816c0cf0a2d782d994fc1977414973ccc3b3d29f7c103f227e34ddc1f336791851bc5307802b12ea901dcd3a83eed7cc020ef5e4be42b160621d24e0185b5ebf4d94c8584bb29930fc0b462d7b736cccddfc83bbb8903b44d7d3230fd818ff6efb15640d76488bb21dd668578da725b1bafccac50ce0eebb7865ee073d560aebb5d0a27a391ca598311d795debe16ff9c59626f014b616dcfad4b20221346f6cb6d33f148f0bec797d72edb2ebc8aa9f09dd7390b6426be90c473ae82a4953aee53be797d2789ad579a546ef0caabb5802cdb737cfa19c0638153f3162152e3354b3e7fd59220f8c4017b2ee37f4901b5fa017feb630e944879ebbd6841b996142568f4d8afd11f77f494c510baaad73cd0438f9e2969c751e64110d704b84b48e797e0ff4ebbbef99ad7bc49cb06acbbca7d13a636a4d2c558defb7fd2712c893149d03ae65177f7ce3fa180d608a46213edadabcacde781224f8ed955bc4821959fd5e8b4da2a6be50b38b55b53f24029babc77bd051178e5a5cdcb97a218d863e1185dc3f47f51b03fc3616dfb0055f26a726f5d029f622ff5c45d5de3cedbfef8f8552a0db994e921569cd7086ca83c697e8d798ee70676d9a04725717b9259f3bb0779e9da2cf6bc395e95f7cb0078894597f50554a118eedbfb35f3fd7c3783cf9c821f23ae156061a34c3850ef377daa909ddbbf5444547bf90d43c895a77e994d569ccebf9f20ef532eb129497d5c66b0cb1427b7ba315b7930f070d5625fd871a71e3035e652c8298339c552dd25f156dacaa4d5822e8bc8b8bd15c7fec4932d75b916c3af133a04631ed6275c35fac949abc1497ef8bae156208e5658ef9c9b5356848b04c1ad5b90a0302a37d270b6f779123ab5c0b61758c46d4236469f97522fb4f9327f6dc735737481eba41cbbc4882660edbcf3e22607d7dbe5d4c089205571a888b478c74f4187f57b36c67d8a42c66dc9a42c243d7d1dfe7fc5d2d5b12abd4c388c6844e126dfad587aef0c53a6816b3f350e6edc6b386897ee11d067e7c5de4a756477ad329d9f52421c9058c58eeaadb2f327004d05e24c4277596c172d7b47a4262c16eb77b4b3c8aa887245f823395eb39be5505ad7285e9885b91a9eb6728e108fe9c875f4eb273c253cde1b5f62037b4036d8e852a5fe17ec0ea3b45d236f4ff95eb7773c43dfac696ddf7839fd6ebcd458fcef820685a687e83b33dbafc6106d3e98934ba6559d2d97a9cea9bff02919ae05bf31fb7a924465bc89bee4d7c6b332bf3e31f5508a33b0338bd8aadec23e86f307dc8105e9a0edd6ab1cca287d5092764866307d75daa78589277412f0596cf8014fba762600558d37c5ba043db87f78a41a81ed1d2c813d2003e769b06cfdfa5bf89ebf769422b9eb8575fb81821d7805826092c8494f0d8056f143d5d9e015ff511cf585ffa8846ef6dd60be83b798472911c59879ca3a62bdad064f53a017a113a0d2e00c96f0638697c9ef032ef2aa9e34c378561b1f07cd479b89950d0327140536e527ae4fd4c782934383a413def6862b92e4e7a8840e08f0d8819c8fd704b4a2426f79c9e22f6a5ccbbe717ad8f8b081568ca4d864842e74f56998a55d70ecbb0a88c2d9d3c7d88741e7921e85fab0af173ea94394d5839d889e63e59aa76ccd5192e9d063882abbdd3b70e53cb8917f30409194a077b265c99200224a861a455fe137516d596364397b952e11b10522bd69fa0947728ac92827fce17139e2366e5993d3dfef3e3144dc1c48e6d104ace3cbcef252964b490eed70dda671bbd5fd942a23060439944881eff85d79b995ac0be235cc41f36ca47543861eaba8fdb133f5df1c9a02450097aaf7911ee1396beed1cd750b5439b5fde77c91758facc3f42dfb3857f6c0b891ec729e244459b6dac7469cef6ebe9608fa19eee5fb84017f3e407e1ca3c2873a0a89fca40f98f04fef4334d81e8c27bcaee5da9b3c4b60f8eec8a56271d08f7eafd0ac3aab00619633f9489aa839db0fb31f0905c86b1bcf0d84d40fbdb05432bcd031181c7adb291db1bac22addd620573b57c8867365562eb6c1c17aed1f3bc1875af22559f9a075494dcc39951d3d005ec3827294a8188f772dc6a45775d0dee4dc8921d0e112afb406992e04cd77bbe42d40f3245701b332a73b398197c7684cc0ddef77a23dea75dd48cecd69d872dbeb59891f4cc0ba27a41488421c1ba218985088ca17efdc28b7c7fb1a3c77b78529a59ca1bb0816e9e5f6ec34912015158e4a5faa6572c8fab6ac1418bc184bda1c1bbd6db38db6e03031d638301e8a023d224adaff03262cdc4474780e1a940408653c692bde88c8524127f0a33262155b569483428ead94f0e258f090c0eee10bf6ca20a682993079a49a44dcc028773462b1b09a30e11bc7ce10b8763e5f84ca9cae3e61fef6fcd7ebc3def2d181c3f5b655d33e535640ca6de50e75fe7fb3d9b63b18169613e49f4fb8ad1f1f388bc6a3c83e77498b93da7eb4b27f08a86b057075224afe23f3443a3166bdb511d3b336f0ab76c4363b7d83886a95912d5862b71c762f53ba08158631fe5d673e946a60efb5c298a4715ab0ca809c08595e30d4005415fca6581e67d331ef77a73ac2e42d5c2c98e1030cffd93f4f3215138dc4179c685cfbea653beaffee3b242717e2488558822ed23b3bec793826a942f6cfbf564474e41a6ebb9029b0989b2e75f9b978d84d705412251f04c03ed1699cf78e2a92d2f5160d4591b8ae9866189261fa60d24b1a12f5028938474e5c3b852e40e8ba7a6764933ad377f485dc22986e86cf39e85c2b953ce97355ab0a207e83a102e79fbb5a42b09d748d3ccd7dc0586d3798083ad04dd95295d4e5ccb0dea6e10dcf573ba512e28c830ca7d2cdbd4b0871654b7ec6d2fbcb845d37a83c1b763e441ca7bbc3ca2b8dbb1f97ceabf6080b3af0a984e72eb566a9940077452777e10559892688657d0bf7346c9e2390a65fb1d63e18a112c9c87296e57302a86daa5171d6559dc885d0562e3a0a37483526db0e33be75723d71ce735bfedff673c350c327e8fcca0da9bb2662395a2057c0207f13307975cf4bb4beb84ff9f3411dcbbf1eda768b3d134d46cf82f5d71008cb2611d5d7b3a74b9c261be8a2a88b5db01aafd82d17c6a5f8a64943e53660b08dd6ed964cc5e7d70425b452e3b9a15c928b4e731a6029cbeb489180fb276155fa789785a91febcdecb94e81f5aabd0a271e2c8f88839ab8181733847ba30bf10aebb35a564555be5a382067c7efa378c812d1be9760303db8fb49459d69d4409a649f14ca4347d3d72236ab7ccf85caf44ecd6bba7e6423c4049e6c108dba27e9709f8e1482567ff1171343ea164d0fcd5d5112394418647d98d3fa72cd8b3f1b774c3255df1eaeb7ce4c861b9bbb519900d828b07c9a57e4b99c1cd9c1699bb9d2739c43218ab040233dd4cf8b08bc424f41b08e2f6766425d1e854dd3d9342f7cf455a8b2268824331835f995e551d76801d397c69b78a2ccfbba4b07ea9b4d41588fe1b12786018af9773ed6159809b4446c63845988521cba20449a344ac78d2b24604fe86bf75dba2f799b007cafaf2bcadc4a904866af6d251b6acc67b5c355092b7c0c5c2ddca2983af9e9f042538080c4b1ebc635cce3f6ab305ce81424f4af5ae898f44a8548e1422abb0361ff86ab2d6ddc4cda40ba423f8e02a6424779d49c519cdbfc992b8908c384994ffecc94ff03f888b417171018e4b486862513a8b32144e17597e58737b424a87094164675d62837e65bf27febfe32c886af9940f35c769be8c3fab72e1dba7bd0a0ce38a0746813d8ed234e6091d545cef687c1c7e04a0811cba0907eeed0869ccd99fae344fedfb467a741947ec1ea50e6c1be2df2fd55aa6a0c92705c6f4ea650ef51757a7cadea11c85698f4498adf8a28f0351c3668f72e18e87d216f3ee53bd927e25bf55face72652ef568b07b2eb3e6b855eb7133b3f56afd390cc95f35d3e3e8ad922a272abcc9714f9880ce872613ed8abdd52403adff8d778d5258db2578421747a14780bba95f2a85790fee551777ab878e6d42270219dd56152176333ad2d73678b3bd6a7cd6a091f135aee1955ce18e3ef465ca9507253809c155767868c6b259d003fe98dd0bcfbc5c43186a3c8af6e12edb680fd53a14004b354556feb15f5944d32a2b56e5f0ccce7fabb04746d8093a18c3825739385b93eb1a23b4f75a51e6ebac37400523361d571ab3b6d5df5c140ed6d7ab91509e7a991c5bd904daba2f17380d4375457fa806460606605aba62bdb0f323feefbdd09bbc930d9e940582a122bf936ef2c62429f7345a51345a1c697513cd44feb8b19f3aeba2a334888b0f3b07a18b0d26c56d51e3c835d8a1dfbffe9a77b3cd6efb7f0024bd616e958dec622e56832ca1b5c19b5d2ed2df3feb8f831a6eab79c3665dc982b053d26bf533efd6ea7fa17f3d9e5811fe6e342b96cebf3260cca3242c96080b3330fe2e072d52337c92094cfb4b550a803e1464e8e52fe4a805141a22d4b9eaeaf0e0e47e3a4bfe8b8cab5c5bb2c1ffdedc714921656cbfc17a61e7000bc88361d7bfebc10477477d076636f1cd0bbaae30a62280278cfd6590b491353e788ebf5481c9fa9cc9cc261169296ec4e534035cacea6d350411f01f3ccae5c65e4b601686fa821c1825231f8bc59a45a2ec1ca023e290d5c6c097d0f41dbf612d081e359bb032e3fc10ef78280be9e0f22029b0c956ebad4e09c6f36425c1f82ae968d1828824d1546c1e1bfe1259ced742035697c45e8a1129b2e1399b37afa7bb4c74cb15e3bfaa2750fb50c3d42603a763be2fba92653397c46a973cfcb098a36acd4d5e27dd1a048159093d497d7662ee2f70b760b2d8324f4a65c947da4ec142d9a4196d9ff46087da0787f88701ee1749f76f0b045ed42aa87daae1fd63aec8cb96b60dbcfc81843228dfa652d006fce51a1d630b152639186e0ebe20185ade3f7622851e60a6f57d8ee2362be6fa8e1691ab72fbe849d02617b521db3a2119b89420ccfc6b2151f77aeb918da1f2c34d3039450427e730ce9635ccafd396df528093f2c68f29df2dee82d01265f4e6008b5cdbef603e1f26761f6066f220d857cccf803d981119e26dd9dffaee9c893612e47a4e443d7aad0a564948d05d5b1cd82aa33f47877e275c960d5f140690eb3378fb5e1455f5ec28dc25bea5e74360fddd36025fbc1f5af98b2ac73ccf1a73b449aa51c3f66ef5a6e98e4c03d28a433eb6e703de706fdedaab590ea44e848e37542adb91ac76dd95aeddd276aaaddd384e20e57fd3999c94266f1f52b1a6ec959540b7fc97ca0100c4e98d1cfa09a25b0d12acb2c3b5877612123675236aa23386880912a0983a9537d8c5c509ae64181eb7635fcef40b11b10de19aff8236c1129d0409f04c335c5e5d3335f68a0bb2b14eb883d4b4871b11fbe5c080c13e0a2f345e49b7a44127f6fabff086ffe8540dd4234809add7bf22dc4e931f3464e0ce92dc7c72f9c193d10f5a54857052b52487c56f32c61bab9b8d36ee1019e35f1a957257e2e96261f47b9dacb691252793c5b03aa6ba76589ae34a65bab91332ff09a37c2feb166cdb8a04089ba549673ece69773c7790092f512ff7b33a9c283264f50f8be9f7ce44b7819ca374fad56a8e91eba741222aeddffd5d81c0349bbeed207f6eac672d378b48689a15add113b72c518caa4101df96d3d0685d176434004793bffc27d95fbea594b6822c43701555f9c3ad42b06c7365639fed8f43a6acc8d674ee18ef1b85375cc74256dca5baafbdf5039270f321538ce5bf36258cd915b394fa5b629744ebc2ee438b803ff4a8977c4e0148f242b6a72558ea64293c28fb061465744e5dd9382d6021054e2558b5abfcb3f285b7bebf5da2b8934024eec1e8a2e2759db26f3cf877e701144aedd40ad6dd6d22d6dcb0471e1a6054d2cd43ce97b038c59f303010e7dbd90487d89237b3065a23e4f524bf5d7acf2e8d5e792c880929f08335350402269a339aca95301ef20d0dccfb9c6b6cee7be83c5b1caea44b649ea77911c7f646ab3506b5fd96e7c01d471827544e5952209aefd62b86f01cfb3f675e3434d8f83f742add60cf11bac967d610b52f992d4ed2e849f2f04a6cdd02f89c268eec3d8797ca85a9b7e052e067e477c1fdc40a050bfe15af545561673c12f6dc28773d6ea13597a6aa1e786979d743c1645ab21679ca3c4603ce7324cf88ae75a93e8677a06fe3c0b5760037f2b0dbc26f9f60c5ffad9447d8664554035e18bdccfbc7d31389a66afa38667a9af997f0e56d59c39e19a88bf678caab7851c2b51395fe4850a74ccd23f62e9bfe385c818da135136677f9e9f2c507cd7344bbd7401966a221898b90add6ffbc0f9a1cd1b270efd668d2423aa785824cd7c1fa27df0f7571dca5463adda1fe7f75fa283b4553626473cab7ddaef7fce947a6f717c116d331708bd6d43e756375da19ef5024c91b270ff382c5055530be6a9b55bb50fbad9fb392880f5f7edf2a918932d69313ae7c7287620b1a99632307a8f1a9534f0e35f2fb1e9673330a7a7ebe0ac0e07ddb6b8f4102ccb0bc095d0003e712388f75db445b755b6e96042872bc9742ae33d605f22397d461e7a82ec672a3d15300bcdca535e90817732be4806f79e554baf5cb633252b7b7d2415997c5b16e0cb3b8663760b5b41fe4f9a7dacf68568b09c9e23338dd2bb6050db3aaa02807136eb54c0d6ec504c30d226f0edb67da41c0320ff6c2f42a7da444647c4eea86bc8537175e96edb470bcd9ef9960b1c2cbbf4c0ec044c37534c7cedd13a18bc09f0fdc6f270c536f23fc41291472d110cfe167623b0292a628727a18fb918c10db64b8b7fc46786783230235dd4399056ce22c7fb268e2a56cba54c39bcac0c5a91ee23a27991f9caf2fd8a5241ad471360659d5f114accb2420fa97856384118bd705d2e93c1ffa0480c7aa90cf0e905fa4262f54aad23c0e4afbfc84841df8f1facf173c63a8d1ff3b2bf33d0c8458d3fae6d205d0cd200a9833cf47ef1650eb2505e473c836f538837c55d36e1ee1c51b2c430f64022adc9bf901b4097038344620bdf70e8b1f1f4585864df4d761366ed275cf62735c56a48e56cab23d6af69b8c1be7af7111079c645891aff32ef5810725a64223e8c5b39ddf797aebb12537e712b0e0584c722a5c350464f9a23fd5f46bf3c96cf7ed062284791f0b2e765b448e107a7c7f2330eac1142792811fc370f6b986b3716e4c4e5a975c8cbe65cae190627390c7b948d87629a1dd9e164824cad1790ebea6416d6562eb8a79fdc921192e35aa5e46ea3672d01c02f4c114432f2d8740608f8435ed314678a947addbea8fb6ecf0d59459174314ad07e7f129715838fd381deea5948dc1dfa7659df679c671b8451f75bf7119687a7b6c79632b514d76ba1bdddbdae404af027a73ab195e1cc5e80dc4285fbe9a6a341e20d2c1c3b4b6237a1a20afe50ff995d7b67941913319161854b39d68fbdfcef88c8f8a1ec6c80e70ff58ec6e473a810d030a6e7c7bde7adb2390fe28d420d369dae8a96e3bf927f71447ae1b07f31138f37adea3a27deb610c7ba88f293ba8022b9ce15604c45ab3731bf207a232073fa6e8835afeac32b10203219e5706d7e8a757b5c482210973114c62818ff348a079d65460cc4f0ae0f65087acd7c9a5e47842f7858d133543377efdd55da96dd9ee0470a223a092d7a2b98dcccbfda49561ce06c93eec50fc298e60f401ff32e1526de5a8e1353284afafdba38269494ba8797299192cd6ec6eac34c1e5b2e619cb3a9f0fad9196a76cfe2aae802326eb0a283e695db6fa15e94ff0525c1a412dd9e7403905b23cc289a16cfee85ed0eb45e54c6986a2cffc9f03371eba6fa0e64541813052e340c44c1faf1c14826a65843ead480e4293e100db0ced2047dfa10139492ae27a4dba99cbc92f7c84a8b50e854bde8729297762edaffd7fb11d93d70736f44e2029de6f75f6f6be45073f67c21d1aa40006b0d496297bb4cb952e9d284afe23fcc81649399551961beac1d40f93a5001fa32a18944cd07dc9cd7c7f4df9ef2df2cfa0ba47944fd6641a4ed0b90763f697fdc21cd9b003f37b105c2673c4840abd6a84354e6280500d93313c5e2672fb2a5d3232644b1680be6f1100d98317d4ee54c963fe6be7e9f8d7ea0ecc5abf5184457765d4d1b46a8e636db9f2d97d6b6d253e1f5ba617397b95a8a12c4fb3f5db0e5ea6a3ddeb2876aebef29bb9e12977b05d37ccbd412a1e18cd05c4cbf29468bd451fe225a19c015e2fe89183ddde8337cabcc5e4fa5571d5504053124e81b9d6560fa77ac6d92e9d477f31dc88a63ebdaa75026557cbb0f110fd97558f82bc2eaacf6d376d5df445e0c6fda6e9b0962733b6b982501b06ff4d0ba6ebf5446e7ca2dbb6ed9fdad74e1aa5a1a12b89c6cce3ef11acc4e7f201a6e1f2e3c3741167ab0c9724b24a6d4c4aae13d37b62449c888aafd1abd54da6fe69043ebd77692b68dd21f3b02997f9fcc895f0f6e6b0333fad2441b61bf623ee32bcae56841059d783dbd9f1ef78f3ca3aacaccd281e9b0e09c43bbce3455507b22c7e74884ba65a6a45ce78356f095f4efc1c0ca3810b9b9aa06c6568b7bf90c3bd8f30723eddcb4566cea7bf2bb28a4d795fbb9a8e7f7ca978e6d9380b680334e324e628923e8a5d619cfbe272f45c2370b698faddf7db96ddaf1543476ae4bcbef68c7229caafe5a840675f8273c6eb611a369641c6e452fc22ee7aac281f131af9b5bdfc91a994dbfcd878b368c5350caf495b23a1f9451a11a384bd44d5f9881c0a4b5e01affcda4dcfeab3d70acd4dd47c1bd24e0fe3ad17f9e633cc29c2829103b1e7266362d782c9545a371de6623f6af70f6d230c9f37adfa19d9ffaa0a7d7e89619e7bc74580042eb10e0f7fd96b9b6fbf18f7acd0d88989bf41a6ed01ce19c1f394215dd82992e54c4508fe69f347c518482a68c712863487031fd1ff5a974bdc5b1ab55654174da6f87c83fa7cc3fa95932453d796bde84da6e930668c0ecfb237a0b01819309faaa6a8c7dfed0fffe81ea5c8e11e4498f90e8449246e2f665148f55b51c3179d8f2e553e715285e8fa570a41e14d5848e0b6f3be619b74e0cb039a97dc74857b83adb3f1803fd0f5f49bef404729e1309f93748b7913fe70575d1b718ab9bee860c869a815727fab623ee69462eb5446be3825ace4254b4ea62da0c78aeea484f39f248e44647d99e80cfbf6328b0ad866afae98ab5cd6457271aac4368f589c6196825da1dd07967b6d1d2cab633000c0cd4b95bc42fdb6e5a768755e1748fc05ef0b54e087b1123aa39d26011c6a5177cf037484af25354cbadf63c27bcebb87151340028dc402a64d39cff8bd6a8a28d7e216a21c131b1dc3ecbbaaee89b4c65286704fb13a198b491f5f1a081300dabe2046f714d5934f10cd34786cc4dbd75ff5d4012d786cae7cdb979cbdb3b9ddee4ae184e8adf0de8969c8d58fca4731984bcf11746d1e58a19074a32cd58f96a678fbba56e8e72a9d017ea3eabb177a13ee3262650304f40d44a0e22c8c9aef327a8c17f966d1ac3f13ef1633eb7f8696dee446d994b34a4f789e057e4ecac354da6298b5d0dc514cb4ae975c273c79ae535802a20980241cbcbd21b29bec84fc35d03fd4cccb28c75164ce9ac54da771bb368ee1978ed937276682b10fe389ba8fbf6bef6af26f46b3e4a0b9368cc41b4745b33c3756c2187981b748bdc1a144debbced97b3eccb613744e4c0685ba643110c441b07ba99308fd89d1708ed518697a0a71ca0d038691151ead9e8e9c016dd0372c64d560d6d347e65a5a715105ae8062b5f9a23003adcbc83ed95fdaa2e811c9c90b383317bc564c763155b46419be444bc953a94aba98359a971bf9ccc7305054a19c9b498fdd0f9b4276bd1855a410f57c2ef30b4fbda7993217f9d31bdc479f88a72a386758037d87455ab7cc6bdffa5df1e087f03a64a76bd2ec4736530c896110edc8534255a12450db49e7909462315297e6b77034c6803e3078be27385dc47ef93bd83e4445fd6eb09c26e6a7a3a94757607eebfaa197b57a0f7df8e24c8ccaeeb3d81d31ef62b2cf2f5163a9ccdcfebaf569cdca251dd69f6a3c3d55ce58ba49310a4825d2ae36f51c3da6760eb867831f90d12b2c5079c24e4656fe089187280095f6f9f736e38b4d3b8f3cf90398dc602ee4a9ab4e09407b58cd762820679af49549056a6a670e74032723690df9f8b953f2434efd09dde9cdc5eefe93f63fba517ce42a7d9e3200fbb8f60f7e2a88a9175721139e6247ccf1ba68f25d59800645284bc235dc0b79373872a8f70a467ebb0bbef2a433b0678d628423ea9ca11ff3604cf18331d6dca05ac5e4e2a151d7c0db9fa003d19474aadaf4013aa6a3ce7971223337d7144db1fe3e3d92880c85b4ddbdefe6a988dbdd9db961ef9305d6ca067b9515318ec522b263c6c02ee861963c4ab53f6b61d93bda008c686964447232f75b510d3b5cf8e0453be3e6c616253ca86a7ac14318aa5c44872275eeb72ae8b76a752451f25fe1c9ccb388b49e9a6782dbdb27a876341647fe61608b747181045a0b277ea371ba5818bf0e025a22d0c48d137518796b56f2092335dce0a0647ca39bd54c614f0e922fab785eea124f98ba13527ee59952912d7f8e77d63002b1c76c62e870399c6620af897d935e866ae451c2f11e285aa237e6476ac6806ae17250337ef98cb2c465b83b697033cae264444beadf6c55dd3d3809c5f383434cb72be090709c6a1a2ca0d88a044dbdfc08c24cf4942578265274232504bfe15b1e8991f063c98c01c3f69358f6e733b0043958024fd10ac4aa6c3d18ef6b620ba6b8559cb67a6b5764319ec8eda63b95182db93b6d57543d6cdb45a38cd8f132801847ce52fbbc32e4da85dd73d9d840e86d5eec238c10245edf56f3ad40c24386461b02b8efdbe6dc8ea3e3905c47b4edf611bf75a427a1372f7f727baeb7b9db63300e605a591454305c7bae548b08aa207c91ba6c58ea4f86c9e1d85d5b5cbd5e3de85e354ba3f529c35443483e90fb7f849d56b7ff5375b4a52b668cd63111ffe9cee8a9dc93b080e4a5cd6f82241b2c211f4ffa4bc3039cec70fb6338982acbcf34d1641e59d1bf6cbf4dde709aae8653d559ca3c1abb092f4de17601fb354938084c77e46cd1287014722d681e19e150953b0ae3966443a94948c4a9341f784421e0ed7bc02127439c06763203c849758c57d312af42754a691d0764631e0301edb23702767244044fee8dab04ee1133092258af2466c80ee6b2a5a0a154cd65bc78181fc811ac9f7db5209b140242b3cce8556be83c6b17242cbbfaa09578326e4aff2c7f757cdac8d85acd6eeb450496d8b85142aeba7fd2135446280e0430f6031a8a3982afdc0cdac6a84dd3abec982d9514a8ab7629f7959dc63d92d6c8b5f8fcfc4be84e11319819e56fbbecd5312e8440c8ca35b2a9f618151ce550a7055c4d9edfc34838bfbc36e9b33532c2170fc63dac435c8b7635386c4acbe207beb89b9fa1cf546916c4997d6d0292290319db287de73051fb803d1ca60398bcd7a4b69586eeae3923b20023daf4620a3ad6348bb4d89918361092c634f83576a14bfb2886471b37f8cabcf3c3f34df810d4d5272581ea6c5bda12b040139e9b91967b0a0854eda4d64bfb4ef57669df3ecec797b6c199b0c98b9ab159c837807657e87c9a89ef4764512a6abd93477e78a2357fac5c4b6440d8cc1732f0b5a04021167c28420ff77dc95a075209865ef8939c3e76592f59c808917efccf705437643ef5b8eb13150bc88fe8946112cc340831e8d58f8a006a0780ecc730b03be990ac2fb261860072b5331d5366861bb9aeddb0b4c1f42ba7803ab45cb1eb251ffd5241b1b753f25fe80e87b8f29ca558816f0d6765d9ab39b224b05ad8a753bedee48c970f69f37876aa653540e6fb5ffd21335ceb7d03052185bad7f48d7a0e58d062686a5434e905e836671b734208d4424c48ce3f035e2edc55161d31f09bfe894e87f6a7c80685f68740d673d8073e04cf208822d07e3a4a9fb211447e14cbb0fd9079f246500c55a6faca108e35c0a1283db9014b4a18eeb270ee247fa970c69565bcad69a16568afe21e117fc9a24c50e56480a89957ba54589e2a507a732bcd0a960824174aab3fb53c3daa1f737657f49e2eb99e4b4083b78d1641fd756d54fc39f9c7f39e810f5cc5ec84b159571971c03b13df8f13fe07b7cc741c4fc26ea16e335fd392e83af1ea7d6995f11be6732bf3f8f21bc0d82fbfaebcac14d555001092d68693dd79f140bf4cd8bc1ead9a0766ced4df26061ffecc100e32af5426bc393f257330870b6b6c24da953bb2737014c7ccb5edd32dbcae68b93471b16b8ed17cebe672b801a239823c26a2c62d4e493a31d91629ca88fabf6bfcedd360442efb71cbf406308deae7e0b9ecfec0d90c18ce16defbe9148e6338e54f99fb307546a62047b5e6bb38b2fa6fda8fe25d326a6c226dda7eb523914a616606350951a27af1bb4ec94a2d22def3e83235211c7257f8c50dd205329d309cd9827b9bf48341d20a93d4f492b1160f271bb51e42581fcececd76b2eb28b7e8d78a59f90e15a9ebe4ab2bfae5f9bff640379542cc5f36bef478361910abc8483f12b57de4866e85fe6621998932b620f76e630c615fb7e205ac14bcdeccbe07e661bf39ebba971f32c991fd14d483ab41fdcea610bdcab202ce553160b7fb39a3169b5105b797d6d24537812101d7990160b8a33591e82a64c397d6c4c85227fd220cd1132ae384ae1c5ce6f65f7fe3e177740789a0c0fc72f786fb90eee90ff86dd64416d25e6a77dd5c577a2c8db3194f7e0094f345236745511edf1ac6717e75201c4b7411bca239dba6f0da71fe3d5b8a57aa1eac1712ff2f2229f186e877bc52fc54f650c182aec5655c84e3b1f15b9467361c4df06ce69881a966fb2952c8e8410ef7be9e72ec4f38fa19bf00a834d604d7a1fdd67c947348c4a5002b2d49ce95b15704aa9daddcb7be30fd3c6449c292473994f00710c2225c23ff30039f13c1d9bb5ab406f21fe38565258d282217bd53295f57b48acf03373267b0f28223f05b5c0faa5932e6068eaad7890ce2d25ad57ba8a7109112d505bcf183185d8d179b9893218dbd720c0ee8edf8f4a6396b4ef74ace32a969c86246b8c4a955186a90a0d08785fa33aa24508ac78343ad2e04372ce49ed8d57d4b60e200bf8df7b1281cfa69b664db61ecb50c1c1a97eb798b5263f134ebbb3fac23080d656688e9693527dad7242bd0bff41a78fc2d0964d63483cd6f1d7fef5aa2d24e1f8e4d47643a019e84d267c1d12d9fa5e682119206653179d080e09a1abc646411ae36fd083c615419a4fd527a8ba6e5ca03b76cbff076a036c827f4f529f6ef61904a8746d5514d52d48e3f3fcaa5b8c5b38fb50fde55a15bbc6166918bf8629517f5c3611e3cc260d504b539c13e1234109e11de8b6f1220464854f76128727ac9cc1062488288324d95f5b898395b551f072a55c057cf9a0574e19deeaacd8eb3a6f7b11626e1cfa0913f2832dc713b40685a902a0013546915bebd57a43b6cc49bf973a1180ae723cb91617674c0a77b63aab301bc99bea7668fbd85afc802e507cf3473b26be6a809b903d2ae45195401d3e394cecb5175f3c85bd06d839bee8df987f096dc556175aedb595265f13378237bed0fc126919b53c1d5f9e1f923ee13300a810df54f91ef9916b05efa00342f42a85b8ef7b63a106a528e7b29d95d36c9ccaf9c21a4fa01a2990dc72c260be2ea40ece2d55736b02408ab202e2c25488d222ca25f3f9f23c588f12be28dd381f5153b1cf4f72932ec99091c3c4ae8ba605eb78cd8d092d31e5174997875e79e0defe7f6afafbf9314eac5ef306d8b63baf62ffecbd46d8b1ee791d0fee9bc45284fef53fe6a0fa13f530860b6ad00a9cb847159962aa34728d2c79be5d9f22f7990872f24e6556ed45d6de0331bde0b48d61fd679de44ae32d4a724e2049f57c0e110a05ae43eedbf03912a850d8344dc3f7618e2bd7ce98b510bceb24cedefb9dd5af99c1a64dc32759950151ef74675c57410b2e19d2773a62a7878135f813b754651f4af004a44a1b31d897e39e9ed352eab4ed754619ed44b3080ed9576e664f81e2c6f4d393f4c71a8b122aaf980ebe0839306a500adb62adccf1ebe75776cf03b1c57ba4a06e889cc4fb4e5fe84af3c16183a969d45889621ee6ee8c702d9ffa04eec354282df04dcac76b34f8e44fe1317c9a53f7362bdf80e2399ca8bccdf566ccfa516f4371dc6b18b5ddd892b214aa47d8d93e60e03ff2e179a2067a2d8cfa86c70280a8e563d2f7f0e20c4e02394913b0641e23df1188722c7aef7ad6ef6610c2019dbf0657b27ff6a2abb0a9b699c3a75bfc32894307cb26b2eafda2928d22f727d0d5f6f7a18035a275fb12ee5d27f1579761dfa4593410441d59b2dac0466867602db96096b32e29b4532fb42d56fa26ef2b07f802afe49af4cfe8e66c75887538bfb71bc97df732fe08f6de9631e19c5b79cb91f7de73e9bbc76c636463c04588113b4c8855baab3cdcdfe11210f836bb56198a7672dfd01550286e4367ed532b62f666b36434ff8752687a6371468ee30a847cc96a215794d94c8b9afde0be9dea7c7ae61b37f7b817916c283c059abd9cc1d80be8842de941caa9d356dd328a32c507960728eac655614fc06c24c2b7c62bc35b5b28476812db3703061da43a3dae6a9d5e81394daf5f69c64dc5c1775e4085164d80bfd6ea784717dc4cd975dd76d201705020d23c199dbf4ac059f66a228e0237d410c097e1c216370bc693a241caeb4bb7d1a885a065c82d1fd36e7e1703894cc09acb401ab162916fb5d2a0985119c26ae788ee0ae35521a051cc6c0ae0606db378cc70698a2be9804437cee7844a1634203581eff65b1f36f67e2207a84de971a88576da9c8cacbb2c81ccfed56a99726ecb41ed706a8f88d1e232c3a5d32d92805cc2bb80ff3291defebf91d478ac9656987f254b93b571a6a2b011bc03f100777372209d1971e6eb4942b8866e90a59d87b0bbf62b3c7e7cc0056ec6c0ee7d37e73360bcc57f924236977051acccba478c6f59b8bb9273022ecc768920d81b0a28fd96b06bc4e3367e091771a8e381e69927ac14a914c7c01e1b6c33caa6c289a9ea2108b803195ef5bbf401e583bc3709cee64c78ea231c7e58b76c7420adaa62b424d2f56ce9384c4bad986e61c21382d03bebac60e62733615ff8e4f8ed10d8c706732cb8fbadae53ce6a656afbabc403645fa0ffa4481cc694e413abe5b6aa0ff48873c56c54c74c490753176519ecf11fabdf07a2a6ecee274b54d4cce3e4d65ae02ec84d8ef135f19e4e2e123b0460ac1ee2165c70552f0fa2a4962e727f036e7824ed2173a43bfddc316ea8e40c4451252ba000ed243ee69b88496b66d918a5979e96638a60d68c4f603ddf5cc4add8212997f05e45096a980e8b37d51b103cf00bbefe55aae21b60e3a5072cd3550c5b631e44b6c594fca4e294181ac419b0d4cfc6d3acdbd810dcdc9e7f3a33a040f8c8504613566e240d9193eb5dcec8467c341237e5d59a330d92e13f3fecdfc70d3342f418e2410b7b5aa0606d3afd8457b1f072cfde442f62914e4f7e7f937db96b24810f3347ffa73be879dfe420b674f7204180320e16a7b4354dd79c223c4f4c16d4d6c393f39c0a7d0f8ceeee6af475e954eb138ddacd01aaec23fb5e6895c48207a930d7fc82cb474d522c0bf48d48fdd14d66a1d546ac686fe66dab6411dd1de9acecabd5039f95332f5924eb23c2c9cf6819e042e9b8c0324892351b64067e7d5827715c3aecc214a40439aa68057edc8bf298c16b06572268ad1f78b6470879a30b961333a89a186d77dcd0925d1a786f7c77d76cbc983ff995d1ed351e66c809c3599ffcdc19bb870806c021491997df274f6b0b632c2131c3e8f0a01f04085ee6464d48d2694c899c0a22b268d02336e7d94b0f25b87006db687ae3b1280d6c461c889a790d6f4549dd60e2c37b78a5ce0fa5a1fee16bbf59df86c3690a5c6b3668640b00719cc457992a471602e75283458e95da8462d02c26989de2edb141c2d61797a7449d8a1ea5f26f16e53341836e7f40e0b33c0a5b52333121bb79957a167afe494e142d6e7271fbe87c160b90b7d2e2734bf2181425627dd3aa59b0c0428c3727a7f3398afe20bc3192bd6dc62c49a0ab7d1252c10630adffa3a531bcf5ef6de1ad016ee8b8c0ec74a648a32c4688083bfe3dd3f12c3014f4a2e990647f6993c84e5c3b3065d374babe4c8dc104962f1730a9f03b0df53a8a0a647048426580d7a6f29f34e2203da55050af56c1605655fd9ff36da89bfb0e1188bffce16fa40470064ce27c3394700653f9beed362306d0fe28a88a3dae61927cad5dc2bc46c817b2185c9bdaa2856a44621cd04c6a0f79b63f3a967c651c719a6b0b52a056fada5fcebf19a9f09a2f895256af3b8646b04ef75dd0ff3ad758f1bfccbd15cc69c44e3b4da777f88fb679de02ff7d404d3f601e2970edabd823c367c1f53b98cff5129ae9cfdfcc76a7e2d04cd8f449f3d617e150617405c55e084a8e60d225ff3414483923c0cc7def8385a07463431e05b767984546e11e65ea79ed33c16976510eccc917be67ab399393296ef3557abf16c504c117b2aa5947094c2d9383af98912c06debef29d0439486b41b05b757d1b353086206007ce33ab5f50f5edd5397f96ef536e8cc1cb11f17dea0a9846ea46ab56299ff50d457326de643a3fc12b9153d4afba11ae56adf6ba7f550005ce28b1f539555bcadb0298c027d77957e2d2107f4558229f2512c7e15a49140eebe694198406251f4d9085bc87e7ecb8e2217598fe3e35381e3790ffb882463f33bae7b1a3fd6d6634febfdccfa7a4d70fb40735bc26bd061089caa3b801f5c3e6724c874585c5a1ebe3156456fb6a5c5b9066c9dc115d74646a932b45a9f179e4ef95ac8a712b0ba20ad66321e857fb6011cdfe8ec12c1b52add72dcdc39a2eb2a8358504951c1950f221b0f518f2995469ea216f3d0dae371c4e52997a83368924fabaa84aaf47fe4c101c7059dfe8951908262a510de756f3411d3860ac2102831dbdedeebf386c819f271c1e25532e798076860bde2d04622d7abae84b8ed0f6a3a2f9622dfd852a7889f6fcf86478368510403f57e8830e240f6ce9265c5e430a8a6e8a25993d967f3e6d2e210c32ce359f9a040ca73a900da3492179c2fb4244d0e8d081154f473a09ba12e553451606946935ef9f89f4af20b7b06d0ac8620bb9b4a233b02d75fe8a67aa8d7ea5b5747593f20729ad3c8a00de9927f06c257d629af0c0fdd889f660ec1c769eb8a611dec21c362537db7e23ac445aec449f091ee0afd00da1a3f3ee8a33ac10e8bad4e50cdc06954b3b4e68a2ff71e88afbb188cc58c7e4f01610c20fdb207b1c13c4efd11c99bd9937e18581784c1fc0f696fc026973dc0ee621b59ba868301cabea15030c929728a163f46a44812c8b62d7350ee3c9bd6251fd4062500c1bfdcb9ead439f217871bd2eef3a5c3a8cf1a5be444ecab2a5394387a2272c69921d94189e130b1726f0c483d9b79b9c0df337432748a9e50630e097c055bc21bb617958834c4d05cd43631690b241c0f93c27d5a3060bd65a62feacfbb1201e55c5c35dd7c0a37780383967715a7339f1e0977c3fd503b05345a86c51199f1c19ac0cad9bb90f3641955cee26d7b830c2b93b48691fa564a18e9cf77d2b24d207bdc4263d64db99b656d51579b17d0bb96edf0b35a78ac69be7ce0b0579bc9a4c7fb02dacff10f20d131f04b8b393a42dd4de6e3f9850b6633be3f64c9b19ba9561651de3bcfdf7b19fac3683b1ac144fbeab4a9534185f4d52b7583599122c044fd11055d4b653ccb560ecf915e10a70394c59b8fa0683a514d7b5d31d21051577b3f8fe469230c33e14609e2e2fb55581d9331b2dc5d7233f08da3f52e36226781e02151fade1242614d375e8f5384bff02a1ae9400739c8bbe0a9a9e9cec79aefa4c86c96496b30aa2672f1c77bbfb0c87335a31ea46c250a609b531b0e3d60383f2d04edb1e83ec71cb5b955efa5438bd387a4cb7ae0c641ce70df22bc42d3ec85be0c67ac05a022ab2ffb73674f44a10f7bc0a210b1299b5a4fb653a75df752de8ef606c1acd34ea1f0b07ef20e1657b3cf92be46ee991b7a74034b0098a0eeae3abbd1f1ab859c2837fd55dba9318113dd820415b65b49cfb671982107d4789ce0a27c6ff1e2f36a24e1bb8948f7878c16cff7f8c3900936498914b75373b6bf19866da729040bf4f3cbe1d070ee569a5112966d65d32ff41c701c9f090ef4446309895a68a5649fb4c9e0a647e5032a629a5c210401af64075de5e0971ea9bad565c758e8fcbb5088b5ce681dc9f34819a4de6139745c3de5e2990a335994b75bc3abf22db2658fb2c2277d31fee64621deb26c5e82f64bbcaa289222bbb052f95dafab4eda87a7066a569104548cea2be485b3315f30cbc5f369718b8e9ccd6c4f370545c0ad9e96d48ce6db4c40dd7261763c3780ea2fdac7ead871e4b77491b81b042998923688176996606afd52d87319f6c773a4197cdb5e1b1f476320b18c90e78a6887fd5f351221469df6a1e437774058366b62cd20cec79b795fd5df0472238e46705408d5da516283b2667b1f6ca9feeef0551da2ead5318cf2ca8d16245b66dff41e4c70ac20d19caf75b8e9690f520d8e5b807a8a6c4e82ce52a9a1117cab514b407b4fdfea0eec2de84f9c436e5f7403f3db5037d74c532a5088259fcb3556e53a38c664118b46e718c18f431fe6ebeb2ad9bb87ccde28cbb9b6e889af13fb406de46085784f3c9597eb9d57a1ddb1c1273ce64122d9d62cc1cea9d5c22c91e2a57a6626f774858d5558e58101a4d13e501ba6e50a4ae0b4d99f933f2fb38131041a0fd9f4d61b536dcdb627181edf9e51c8edb6d482c210c3a4b87cf4ed5307c4b17035d05b10c61d7b5272564630480e7bd0e4313ad25a8d976f3aa1a99fd265e3a2b07a853c7508822bd333298b05bc87f31cf533c4a14eb4a55fdb209563c23107f5d42e76d9179bf4aaaa76a12cb1b7734b4aef92718b05f4a89f3dbffde61f99b026d5bf2c59dc1c11ac922655ab6f1b8b9ae88a0d0bf6a32d0845b43a12834f2fe64f95322be6a9018f0b8d3483763b247e5b1792bdd0c2c9a30c63b0dc8a9ef3eb426d6f2384e2cbee741b9082b083cec9ad7b26d1d140471460da31f9d9127136aa4d01210ff9f281912b3befddaf1da0f2005ad5e6c332410af39c8abb24eafe34c7674470182f8c769bec8bd4417684c7779580e389548569a8f6b094e53ab2492cd9d9318dcd25ebc4332ae33f99081ca81ac3fcb73b2f44ddf6317fcee84bf3e7c27584e93e1cbe46140a9814470f91e8d3e1283e8fe7ea6e07b8d06d0194e3bf640f648d250edd803e852d47cc99766a07568b34f0bf50bf5745409df2f7a5a152a6bced0d20dae4b551b615b83ea33180cee24bf7d87f1d28230e434ed956d1eb93e8c141b7100a665113fa027caaf59946edd023a6cd9e2cf3b470c60c423bed8c17ef065f00f692c8530cc638a72e3ef7b2ca1eec24ed4f7306459f8b17596049d9199eca6233b6f7a0e4c8aba22e1d66436cc8789878d8b858eb909dd9f8bcfae53a072ae069c2aeb3fda1176fb1e557b77f4b9a0a07c8e54d41b4c6eed1d5f3ad800bcebafd50bdae63d13459cfc4aabf9e47ae0a57dfb18dada5ba1d5c3ec651f4312eb6648f434ad1ace8cc4c8e96036ef873cd8fc3c3883ff38bd2d664509a057c3ccf77e7d875b372490dcc13abfdeb1def8e3b8b45c310bc69c71d64cccccf49f41c8b76f7da37c1b006ed19926047342eef88c37a0a4f74fc764557595ae3b7b97df974a35c8f05e1ceefa360b5bdbbbdef6f68d86c611fc188e064b48ee52b1d5338dc0620e20b9637b60931b921d13da1fa2f3c7e9f360fb4f7becd3e7789de9cdfd38411eb5a0d470b209b7f73959b2021b99eb482bee2d9f568891a0c621e396c14c56c17fa9bdf0d843b4e71b62808ff524b2cd037e1e9a68e5f2ba7e2241e08c4b04a6cb0d1ec9a8c077bbc34338881a2e506b8cccd18287de80c14c084ef9e1b8ec12b42b96537f555a368dd84dbfa5c1789525f95bddd9df29634a428c63d8c822b41406e832b3a95fa8e74169d6bae0379ed208e9b43b6be07af9c0327888e82decd2c20710e2b83c41825f2b6ffaff9c0416625549e79789fca277b9903236dc527e2abcb84551ae4d045b0200ce8635fe575356a78f8c3b21b4e175044903e54bb22dcf4165981f59e6200ebf945157c5b58a32eda908cc6cc47a478d5c575bdf355dce0ea984a06de6dc1455d8323b769f9d0a3eb8ddae66e84454e58a6df03d926f5be192622cecde83874a146ee784d983b1a507712420d644d5af7732069bbbbd0c529d5040b77c4c9660473cea154e18156f783db8e12902f3fe07ca914673c7dd5410c3c1a96e3255487a4ae4c3999c36af964e0f9aca0db208110cce303dad2fab6a0235942d74f069a337e39b6634b3067fc706e80675efdcea01be411eb759655aa8433a6915f1add31a8c5a7768dcdbc95d22da844f6d2e60687f2edccec9d5a026930a7c5d917a64fa1534bca3608ffdd94abd8735ef80c4356db62200b597a83eeb8a04562157f35314b1e9b7836dd69adc9460c3664e120e1272c5ff578b0689acb2e84f7be75397116ad156e75d09100616171b30f5d2cdea9d50a3c7e998f296ad945d883145abf9ab2b9ae393044276407a625e813a01f98a3e6351d27939e0c03bd5d45ea366544f987e6fc197495beed04118a3516c259f72e06bf384729a4c3fd4346c0315504c5c62998e5bd3b0f5536504644086720f0e5b3731c2836a23a5b7787922fa9bd5c88b8f5726a1deb474fec56dccf8febd4d3a8e6ba1a98eb2b3a331ab56c8120448a03873da09a724b44489f3e523d8e5e387a5c595f334b45ffe85d03b29355dc22e5c84607465c977ee9f90ae979c7e2b703d1149c6216634f3153f2a3874be6f255b97aee19c1ddbb92daebd9cee81a6c8613f5257a67ec4f3ef96a2074c772d5a780e5361d64ff5e7e44186acb67c7385baf81334e7597f2f9560bd1b2e9c2203b19e1899cf6d6cbdd2f7a9f63516ca406cbf2abc7219fb1f0eb7d9eb3bdcd23e25dffe43f95678033c19b6dde87619b8883c9ca227b061fe0e703a64d163bf8a102df1ad4510a98889c23e7739b9164f0f9bcb6e6b14b44b2c74fd68df955c4b52e65b384753dd9c443a8d0f02cee48b7d1211fbb01204491f39dd4a16a6724a863cfeda9e72d794237b1ea4df7e8297ef02b5fa4e7a9a7f1a907853e05df633ad221d9dbaedc74a2643ff685478d6bfd044f96ea69e3c3a2e3a71412c96061665178a1ee44df69bc239ab41ba8504f71b8ecd2f99666906bdf48dc3073002e7b1cdc60e9f266ae720ff744b056932552c0fc6675064733900f030fe099699fe55f6ecb5575317936836a55356f885931da9ed1753afd2689ce1df0b8e105698baabe76a61cc998a1cdd6e5e5d6bf18ebc5f1793ec7ae6f045254f76bd7650546842fa1c45adfeb887818f208c0256c907e96402e3a2a36e759d5dc06a06b8b60f8086cc5f46718e392af2861872a0eb3f7cd2df7da85a472c46b3dfbb6c59a7217b8210d153e4819200f42dcfbac53c8842ba7bdcd9d421c9ab40b9a4498df3f7a41492b9b3f88b6010fae8b55af22c170b6c789c9d8568ecb7b1ac31b8d4c98dc05a8fc93574a98baedf3d7effd0cdf624284fd01c72ca6c860209a2a5f13fc8c776591f5e945bd25865e9c7248954558006cb45c5bebb5b34c994520178cc4e019bd575356a26962030583348f99c6579958c1c5e87f219db7306780facccf3c8f4fea2803515b8f2192d449dbba2cfee4586aca8c3ea7b29ae8ba9680100adcc036066f96634854e8bc0136bb70ab1850c7fbcc77f62fe4c5e453b70b50b1cc0219d87628a14b33d579bd37d5c992d3dfa9408e79cde0a1d2dc7684bcff6a6c8bcec4491da4b6c9f1afd1f5629b88fb3f0b418dda3234cba599a4fa346ac20240d9511c6aaf06c1870572200c8f8f8be002367c1f0bf1214adbbb9bb317f01ccdc76a4ee21232f3a62d183bce088a23b74f9d6b0a37ec905be1bb8a8a2a12f174e8156abfd8327b365c62fe5bcc1ad31fea17a3a50f679fe5a1e0dad4aac579c6c3cd13134b6119acd43e6b7f79b84b40e5aaa2e5afd6064da1d670b121bd5d6b93f0d6755e54c261c8c9ef0c535ca1dd2b96aca4b2d932647e46ad491ce2a50d5baa0ca7c3d4867ca98b95dd051db85f52523793667b12edb348e7bc48a0b50d6ec5d0d7f6fffcc219004d838d5de9be45aa4a65c4cada4fd42c23c5177f254ebad416dbf3087938a548b48244b663fd7e77e5bf8c69d360736ee9c8da04bd759e5b130c2baa96342f53aa60f619e7dafb0a99def7da3d662ad398faf772c1907120cdb224993bac63c5415496a0fd45a6c78cfac63dda03d382709b24e9c0449c683f40d9f8eea5e22ae9221dcedda2328e2861e296ddbdcdb7e0f1dde6c5adc27517fbf81d1c61956c8d4ced0fab823c2179717c78b42dd8e088639c4a2b3836e2f74d038594c43baad4c8086f402cd6e6a46e5f8ad1213c0164c38ff6d1701682be04e11d7d9cee6a670ddde96e85b3582eaf6bbb8827ecfe06f7b74b227d2f8c17db5ddacc872a4bcbafedf2bd1d171e464dfd445a0cc90ad827f6a202655934ee3d872f9783a720a05f633acc90453ca39ce48f8bf28cecd57da0edd53c3c2c9a0c928b5222403e12d26a96f66355d473bdbe5e0f3b6c366b863f0ba6dbba4d7e67b5cdefdf7beb82dfdb36dfa8fe8bf0225b7f239eca00cd6e3850247ed692691bef0b593e63ef4081fe50d46f8a0afb2cf9487541d0b2d8692a3cac4d5d81caeacc90b4ba4d07247b37c88ff8002c0ba6692f47e099c49904907e1bb60cad8dd4d509b81a8f8f2db51d5d6d6fae52e98018b2e70da5f02998bd24273b4ac7d228bf277c4eeec44a5fff133d718ee84ccd0a07e5d0f55f073680825cd6001d3fbf6a089f918174df3c5f82c48c21a9c8465b346ef0cf24fe6d6108d58aade32e82850198c1998d6b9b3b241704c38f6a91bebd55998a07b24533779cdaf6dc8a4957a6dee9cc0355b820f15db806c63588b344d0c583b3694702d99b5b66a1ec7de9b76024298ba34aba12d0587aef8f1cbbc93a79094c59d9485964302a9fd898db58b3b5158f356761d468f0249564ba0785b2fb9eedc953f3aed9a358e9be70dcd368b0af1be136ede2d5bf95c3eaa589d35a72d84570a2025540a6993185b886c116bd2852f4cae6239345f5c7b905427b78d3b01c3fbab88b5fac7c64ee3e91fb1957ee93ca8b9cb42e57690168d9355ade7ff06bacd2286b794c348f02003f7a340c32a4722da5e3ade9294de8db12a62cd12a70ef8a9a8d45ea4cff5a9280a7de949056990950747cb21d73d6b00eef0abc8eec4c2ad5904568b245dec27f0f0074b6d9a156ad476bf5d1d61a82dacaaace557dd96ce6bf342c1a6373ab54602379c08a3a4d976ffd466fdada108b28af4fb53efad6a91cc4d89c608d9b02f3ea704b3dcfec6c30e650c6389a38a13bbad51c5932a43e290128a89742ec2ce08ec51fac5d849362b390a106ea33728290d18879fe3d25d00b846093e64d1cdf97517a1640e6b6d2458091553954b0a2ac6f0e62d6cac5f12cb0ff4951dd9d9484c3b1e7811adaa9350465c6297ed1a2f9e3b227b229c2efee0490bb91763234ad7721b7cfbdf53ec22591fff6dcf7a8a126af08e4f5a72d341e6a5decfc3e0facd9af97aed83731c2d3d6f1c0a35924342a3f8b6a150b6a21d70297788d2ca9d986548b0a7e736bdb961aee3135d8bfb8d5656d18b55ec350e1f97ba476c9e67ba3350fb824471d3143e15abb3a8d7e19ff71d675347e658e7f50087a9899c3b140a8c2136541720489e2f3c13aea7b8cd02cf96d71fdeb11713098f9e100a7812822ae11040c500e19f5c6a519673e90695e6ce5a71ef291cfe6dd872d4b1d217408cd3965437e061204ff9e1c476a9c0564f58d93b99e5c7eb788ab5675e45b7f4c91fa497d036413190d393cb302e9235eab0b6b141cd62a98c0cdc7c620a8f2d2cfd853a171b98fffb90b71be2f1726c0bb6cbd1aea5e01ca074ba9313a9d642b405b1e43462e27f404d20ba1508cda6fc9e1fb48db8b20ebc452b9b4912f27e9358fffe7750721861cef8781482d59e79c7d505ef0cf63f7e7e80ffe2cc9b35b08ca07ad2d9797736fba6df6de3dc73324a1a78c62f00a698f797573e3278e02573250a5a72cbbbeca137f673a689194c6b17d56b8c8c024f12ad83c3ba237fc1a95166ca6eb419828e0c9fbfd02eade0d23d051b550cf6feb6419a5a5a0e6986fd9c03fff4922f4bf1c606ab97b0327b97698e75fbda1791a3b870f0ecb5772c9d8e907d7c09506e15a8f7d614a9369d18aefdfd3859960a0a7be6f6b211f09d1b5cb0e8def1216967e28aa30abf173aa9d23b9da7305f007fc35ae2dd40da774276a88cf871188acb94c7b9b860453c0aca031fea41d61b3868deac2cce868db4ed22d6d8d36f75446a3c84c05496feb4ae91b464ffaf999a3ec452981ad96660d9bad982038ea1e91786b219d3f5d2f50d287085369acc19f22b5410c18ebcc228c3cb3dd4dfda2d3302b816180a7dd68b38cb9c89fc9a65680438470524fff88b9b85756f8ec0751aa34b79a323b6d829d421461c40fce80ccc078f9f25d0a35bf2052b8fb247fee9f6e79695b8aa80ba86336034bc7f989d06cec577b88699aa85eff465fd3613b7f84c8ddceb7baf4be8a595d19bc61053f91f5221e0ac68eb5008905629a8958b30fb862324bccd91fe7e757dce05057ebf8d2f2a9581aad397baffdce68dd2dbfbbd0fb682f7c59dffaa1bd65c88fdf332330b8f0c762567ea943e4b642aa970951d79c91e27cf67267b6995472a66e5742e70e6a9b4bf5411c77e3b0195010099e94ede453e07f91024ce166edfecf61c1e0415f694410fcda9fad9383a93ed4bb4db2ba6a0dd3d8f3d9f9dd72a30b835a9d39c9c24e84b753655685059fbede957bd4734a64dcbe04201de20a7bdd136f8aed9e1a9c7d0d18a8a9ce1852386916c897bd11de9201f14608abd8360841e3996bc3c17febcf86870bdea26f8a031d2817a34c4b231f97677e733febf53a51b36d8be9bb5b499b6c69ea8f86eb5062c59695ad1c83df491eba876f04df6475574d0bd12f79ae0ffd7de208add36cb61076ad26ddf461d707198074285e48c1f0c7fc18f4cc305f0a907edc09f49bc1dae1d8a338ba9758587c99b7078217cb63a5f12d933c5b4dbbd7589520ef6f84f8dce5db55ba56c22be5b900eb016b9dd7b1264ea605bae376126254bc5538fa9073493f7c5dd6601900d9d40599d0f5756100071d0f86c1d8ecfb18a3a67c024b70292f2de1dacbdca21d840c48e8e6e38d38451aafd1ecd5be3bd44a4aba07991baa113e6e8125687594ddb32767b6f6414c620f4b5fc27ff6074802f8340a81c6530b99fa168b1b20420f4f63d842cede89aa9138cdb335b95f8998607f575b3352aba7f52e416266b6e00e1f19478c2d16e6a2119e399212d36c0a7f42ee6d93d36dea360e9b04f10f389fef0a0aecd103b170bfd7329833a28780ba88ac7a1a740639eb3fe116b93aeaca828802b1ed5251586a8a35570108c181b6e69261f289778101c26ab492ebb9ae232bad025106500d2822bb26915ef4df79771d7ec9b00c21403af58ab14c91a0872ba8105062b254b154d7be0ef08958e13c8f0c0ffa4d979ac36d22636a906cc8371a8e9ea5208cd26f80e27ae5dc40380b34e11374fcccc95c73ca224bac986a5d9b2761f3ce0e4f0726cf1cb358364f89d0fc8528e9933bcf0975b2101e8d4e32c9145dec216d4d8dfbd653714ccaf0e1816084a80b617d33c4e81923776852fdd38706c8dca62563fcabcb7f4702cb1948b01579c8ba0b5a8ab5a631b9dd8b187222e1ff54e82f2f7827257c7910ff41258d669a7f5fd9c975f0011df1c0efa2dcc80c8aaa8bf87817506b4cf2873f9cfad8e6319c582e54afea465acc443def837f05cd614862e8c5c4c55c9809c71687c1d0b9e3f8f6c12806d24cbfaa329aa6757e4d980317fde8f4c6ef4b9926d86fd74ee089c4bd141c2d9272339e471abe6c007100ff113328db4a97846057e552caba1078832227603bdb47f8d13ae594153ced69779748878d7e3dd6f82587c462e0e1aa46bc1de67c6033018b968c68a4d5bf72388d5cd963ef4b4d0fd7c485009c4dc05f6890b7799435f9042a7a8d413bf0371345622d1c9e8ecd8a6950d90387f7db3a27f399f7695c4bfc2e0a0bb4b2d947413a5cd386054bb4b0f8b77120e53462bdf9336af73c61902009e44d9082a65d32937b47c309370c1a186b324a62d88de4f49bc5b44c71dcbd29c9f5209803869ae7a207481260b24685de1da6ba5d1b03a4090bb9224c620d490f949021203c39cafd599128adf36221d348bad424b79c1f5d0cce701a9195cdbb868fcd861a51750ad8a0c253409acb5855141b74687ec3f94ec23599765d0d42b5dc0a188859b32738a81384385b4f8e68d39b68a1a855064628780d5c64281db57535bdd0e01d04ffd7dedaee5c369e262a95da9e7bf4837d44a395e905fcacb8d83083b9f4f57d3efa8e4153a9298bd357964bb96b7a734cec7c8222eac98b6b784a667ca5f799f8d74e920e6adff01334b7e2e44b71dfb4231de489d1566e0939c9aa0d54d16c1a9deaa142372a1fbcb6fa7f102558e2003dc46fdefce239e61df78413cbc36063ac19bac7a4ddda830ea4a2e545c4b554817b390ca7f8d0568fc35712de6faffdab25cc64c5837ccd88474e9b288d2ea30950d3ae7c2438a6f1a16720a8269eb4677512f4d3b0c6efff5805c0a10987d3863270893f4477b44c4eaa8eb73486fe2ff43b803666b212514fe903cfda728c672ba423151b36d39e571b0195069f832fc368d4b9591cc8c1aadcf9d5a28f9a2b104b9b563c3a2a2d5e7d594ad138162fac9b1d5a9bd8abbfdd8be752c064f77b27d422ac4956058771e2c8e4877bbbf17d16ca3ce597afdcf72a0813b577757e568042e262d063962abfaeef44fea3c4102608eefb2efe335a6fc71af3a14346046e571b7995ca520ffaecf60252eee03da02c17fb891beead7296b7942d5683764f53ace5e48037c779dfea6d0b090ed193a3c907e5b2655408f868eaf9a2e66127ae1e76f873ad9bed07fb131e4007d82cf52367c5a762b943350f20a5d26473bb756f75e9938a50ade086c3efcc0a2aa9f2b42c0464d286d254991e519d59c6289990396486fd10a3584c6d5e776d1166e434596bf0ecf648f66ebc895232a4d604016b68c53609caa9187004f6c9ef171e9e8550c614013d68e8ea2fadbdad230f47975a7dd01910d02ef898811f207d8c19456beb01e64f2206134392e7c9d82f348976e0bc01d6d5f281a0c76c8eaa843654176ab82f3566cbc539b3005d6d4904bd6ae5dbbc258608d3ca1d18158bc39723b5cc4ff6413355701245a6f0233af26dd745af7bdc976b8fb6efdc24fa54565a28fb4b8e6943600a431061f96f94adaa5d209375b964267fd1af9e56dcc596055f0f48352579928f055f6dbf677ee8fda6878e9b2471c2d485a65fa7a403c34c42845209f16813e467ece59f8533eb911953926aaea8df51cb10856451f0ce73ecd2979939482d79156955010516f7effa6e0b945fdd5568ea24cb52edbb78a3fba67fbedd223abf3473825c563cb7cf6e78642a2e966b4bf3bc0eb0c0c8063a8988af971b6635dc093072219c82ed55191495c23986631f6b9952440ec805e309463208d6331199ed2363ff4278c8aa207caddab823173e50522b3201d23d877dd83fb8649f3faca8e5e7bcebc6930bad1ad24f15ad633e2e1617326d0a7b27f86c2896cbd004bd6c7f9c31cbe5fe9eccb1c723bc695a4e4f36fba6bfca0bc7ca5bbe1293a268856ede96d425c9cb52409455725e967a084470fd76dda7767416f4de683d725426645f13d09fe6111a0acd7312c7e76ae597f28222d7ae6cdb7271b0be44d31c4f1c23a01e27484e375c889b823faa0d468b0439f0eea7d0a265c0d2928e9748f1f07d0b56f62efbb38f69497ded1f8398acd0e2d210f21de36d8cd62e82089a55da823431f181e6253f0331d9e4c4599f09bdb45a80188a6002c4b19e52803a3dde087c2cdcfa5b3f8eb2af61a6f82833c6521500e7bbea484663af08eb0e4a98975b457bf3aa8d2c0d43dfa38735c72c5fefa7ea0a4a29360dd90e2da34261cbcfd4699680a62a44dd4b408b8f0b7aa0249a442b660698eac770a67d4bb362644b33e89cc449e9f90c1ddf473532aef3dfd004926fa1b473ff7a6a49f8d731981997f1d71ec41eb8b2c8a82057c7e02fa7a160e2b00118dd22826b7dd82b4969e1952582cc2becca8bc8f72d66f32fab6498ffc18f77d23c2b89403a5ccbf883b29e2f04bada066fd80459ffacdfa60fe2e3a1e8870e2e75ea7b89c7faed3ee05b0c18bd9a404c71ba30a2960563c29e10b875cd30f984810fda4b7217f1f7715286283fdca1c4644a6bfb22831916f51300f510ffa4a0bbe6b2930ca6848e90e649c40c2c9ad3415fe48ae3dcc4a0d02760aa6da3277633c005f1d7fcee7605afd2d23ba6691da115110b97115e9fab53fd706aa71f458b4e3217698ef0958f62b35fa49b137c59a2c8208c18fa382296d78a0710d687e42728ec54a5fad81b1b0b70c9a251e66314991e68242e542ff6e1332ceb6cef745998aef232a4974d5091bb26f9c5d0121bf313e9ec9cc005be972ad0fdbf7d019853b05ac0f46248c61af7a71e61eece6b4db3713deab4a14bf8467c7b405a432238c299c0a7f2e8a8421e8977f1e1bf4210de66b8689eaaaf2dad8bb40f18fdae2ad855d7ec6f2c7b606b6ccdd2f01a239c801e6bea02867852aee6388077f056c904dc2c4607e536b57b2fa7bb7bd74f310b767d1e0dd4f60d0e84bf565bdf7b86ed42a83834685bbef2510e92f9b04ef708191c56956e05d48cf05ab7fdfb81c41b18822dfb221f90ecdb5d0a4dd28a373187f491504a8beb56d87379ef88c4547f3f101127831ded95fbc2938e13d612c6c0934eb003eae5ee617445c21ee378566b67f32c29aacd7aa8274a288a036afb19b49ad6132012bd22e53294ec09342e828c1482fc5067537d639aafbb81f957fed3c4b58e7ff2cf1e601ca75b93287cb8e35cf99d269848410c9aeb52e517dfa33fd6e267ce5126be6b5d4073854a64de72c39b336f9b6ce728a21e41f9585119c43eedf33b5b49fde1a35b30118b01a7d7f6a6eb7ba24690219061576dbd5ca1ffa5cfd012c8c870555ac7ca4fefde1732f3b3a2079cba567bd7935b22514a0088679be26df38a42a584c257483a8fac321f80b32430a4e91d126e0c9e7f1eab038bd5c050cf9804965fde05da911185688e3399ae325ab1e0bd0fbefd09b615629d10d9e3837c3bb4afb5c0c81590e315baf25fabdc51ed2a8cca962b049a30f8315d65587fbca6ca555161644e9a6eb4a05b74934764b8e05838498d3b4d436e21b2bf68b73e3b00b6f00309eeb84d4ea02a61e1728cd9f21f42d67cc984cdbb42a660e837103a2bc807b9e22cf19826db795a2aaaf8155492f2efd0637500cb2bc062108ed1d2429159b5eb9c8a9bc32a8fe41794361291edb21c9f8d5df0e6400c546ca7f02f36d8cbcc1044d60f4914ee95d8401e7fbedb038a8fa3d93d0715974314ae5cd5ba09a6d755d9874b5c095788a6267092472fe03f6113a8629ccdcb5b85f83ea83a1c6a231082a5ecc705e442482dcc255c6259557051f55bf23d67a5a20dea05f06eb3d87b545190cc6fe834616262aa1a51bec5b1b40299b14445759c32038ba84bae48831717be88fbe086c2bf11a0cc2e9b54bca77d5c4445aa4624b98a6f13f36274866f7975647590845004f6e88592e5bc271a002754bfd72e61037d2474236f660796934a52d80b78b0633c348019a5eeaef1d6cb096484e8bc31361eb69e6690dda184d3f0d59b4e1def5d6b5fe740b7785522c5ead0bce671ce7191a1346d87b8a9ca98a89fc40003ad16d866b1574b5042a192b1c47f9eb4591d4fed2a93b61cc5181549ff54dc5c867cbbbd06bc04eb99030cc13b378a4f193fa2ca938e9019ed42f7ef3c554e74b3dbb84da77cc95cff714723f3a99412f9497db276d972a2289f9dbd161025a6338294945cf167a43d3997fa27b1e8c5bdcf9413598b4d32218e0774ab1b1adf4274a602a1dff79881e60d55c48e531e9dbe2b7325e0d695bec86aaaf12b6f3de385433619e542e2dd482440d27569458a2477b8834c3ef1cb327bcd3d575f13981c7cebfa33449b67753fa29d4a7026f68a13dcfe892842be196ffc8be54ac8d784624a4133922ce8b26f70ebdf109a106ceda12196bec692c589993856e98467bfe7e55e7f629af1b30e2b2850ec5ae7c80dbf3ba52310511c364305821f4294aa0ec7badccde603270ccea814e56a46a41a5bf939f85574e37a99057681ab588e89c2107011df863afb4e53efaf4cb97ff79f1c07ce7a6666191ae1706d6213db879702931e48c428bde10f9a438a26e30a9f030c13e39208f96aa207fff4056bd79a45d0fec00e9924ee6254f8c887c80944a2716eff4081452f66fe4ef7b24ab6f798271a2037473fd3ee958f3c4f4136a19bc6d18eaedbd66edf56d9214d90258b189af6cf9853494a29f448a592b57b6c25cc9ffad615974e115e51e2b0e6c672ef751468dc184c3b657496d611c5f2fbb45505a673d46478a2adfc627409d5b82671017bd5016886052b5eeebe4d95b870cb674c71a1cb6ffffc7dfe54ed4d0c205205a22610bee61872047c2478ff7c218f170f1d85edfde55bc5365fdde9de813d2f011a472253f216f291f2a7137932441147e1b43b900613463d674b378fe68823aefced3f9fdfea3e422163c4f8ac185df5fc98225344a7e018bc09865e9ebd5cbb718ccfee23283fdb9bca7daacd1e46b688d41fbc1af0856b851dfb2a31d495f5ec1ba03a882046abf6c270cfc0dc145955701b91d207d2cfa7b2983598d0ed904877da0c7ea95d85e9c3ac3a18100c017daa6d009a0aa5d39f74328f4613d7886323ded61172ead1e674dec9d0ba379400d8ebfa22a407bed5f8ed047d34f8f0c0a4ab7eeee2e9e840b29cc9edfa88f14abae6438295748b1b5f34845b2f15d995b08c56903181bfb6af418800549b557b3d04699fecdc8c8dc72d547dd1ee68df8ecfae52309e7788ac402c7d977ac43e8d0bc777d72f1861e3762d5cc91d630b589737ccb14916f15bd8d538bbdd8fa51f529dd70fc4278a4408fed0782e425eca9dc5cae62b4b0dac0df699d5ce600ba51c03313d8d9d6019d5852acb22e96f97eab5c32222a4d4ab2632868c8bfa9323c7b4b96c0bc13a82f5d0bedd08383bedcf9c2f34c97ecfb43e479976b4b7c8bd143e39f2a7430710a06c39022a66120861e373f3900bf29590095a9beabfbde8971abc57e343b185610836d84e699888952a899fc4bf530f69a2473aefb2604dd186b1624257fbeca3bc4f4b8b00e62adf5bc379ee1a013c128479c52c1441df7d9ed915a8ee166b81fbba406e30da1ec43d88419c9f5f811fdac20f4903c0d7d21a4ade5f773799f684a95260b6239038e0d9e30fa2daa93f83fce639ad6fa600d6cd186827a43590009ef82e25ba40a4768d90ec3b716743be9caa01ab6d0c667cdd947060573d0e25871c5a3be5e2679c83abb70ecce24bd3b0bcfebc8bdf8690f9895bfc09f49a1a1ebb5137369407333fc5aa34789abeb04de7dad33b56b39f3502a95d7be3bd65fc46857c6cad5c1e00f44f8faa182b406613e3f77efd3508a3bc84089acb80be174bdf1c20ad3c47b94dff3c493099968098a87a96649479406c4e4a9a965d6310b2d82e3b580740a625c55258385b0bf1cf9c020597ca70b515fd858da0327faa9e81ef8de7f27aa84313e64b5b1b9e738c9c7ddcbf9c80d30f3b411c78814c2044864989ad66bdbb970d76415e09d49cab321570380dd15adeaedb0dbc6a05fe58793e5800c4566f3e85ee7b0a907969814ba030d63a497438d7d24e8a5391caad85ab1305761f8d818c6b1392fef6c38608c293801766264997048a0ebf876c6bc2057c9d6177f44f14c1185e93de596eab601974799d9f5ae5ade1d94e4463db7ecb670e241e7c567c24a0f0aec6972fdaf38804615fbcd89fda55abab166294a47cad064415d1ed954da756b39ece03e972e0fd67062390cad5cb710cc013745ab3c9548c65fc53b1cd7f911e4fcc667dd20a44a12f17b193ad079e9a3102a64624af0f424f86422c869003c7ef8614abc936139318a46406e4147cf8490573de7e916da4f335b8e1b0996024615c02839e7ad4c8c5c44356209aead7689288026edc10dc6863c92201ae98920cb475bab00b3285588901d39d49e89eb029a833959adf708254ec43da5cac19ac02e470157e45f048c3ca90acbecc93b9ebe2474ac1c0e6b650c81970c2fb6112200a53d7d717707c7cdfd05b6ce0f33ac3bf5df2b53a2494ba3fd6613b36f640a1d17e77bd1fb7a8dba701777629f2f39cab97e9a1bbd03e9772a31111564d16e381064e741a562bcf0119b3c275896b9b938eeed400b40d39e6507dfb576e1d1d9097da63f8c117a5106e672813d9db6695847ad15da531963fd3be81a8982dbdb0a7fc90f6eb4d230b2b0ca398fae19d08d52a8ef23dd3fc58234cb6e9bb3d6ef7db8a73601302dd6b3aba1fb71e67af89a6b9ea6ace18af5eb4322007d64ff18584ab6015ac52801aec9b9d7276d257e9dae3d172e057ddc86b65fc10c6b7d2d8f8730ff5e2e5060e20773c1d8af5a146d489ca494a8d192bb7923c809b0e35be05cb7834d2433189cbfaa11ebb9bbff4904efb24b0460e89e0914ce73f50ca0e67a82234e82e3426acafa04ec30a699db70c9299a8f45df548e0fb37bd83951813c9b3c1976876a2a03523908b5c0d07e36a5f80e476e2f43537df703f165958a83de6bab70ae12e3edc63414a8e8a5e478c78ffda311c11302a341b130e4e7dd0530150ac95a2d6e9014de249d31af005edc640a570335874b24dc8ae37498aec45ee017433ea4f3c56803b8f2ac964714ca6fc717c75d17e83e352bf4314a2ea8b73879ec7bd433970ad36c593e983df24dad601b2fc0691ae85e8055e076837928cb88015e0ea261e6614c49d8075589efa05804ed18921dac8e1cb46fe70fb5220d5cccd2164fd73c27a2a719c81cc6b6cd545de5d42429139f16d1b33d07ef22b5d7af2fa5a3218980641f6cd5be77db3dfaf0459719f55a8f7a28ad6c55c4de3ed4d0ade7eab0312d2dee70bcced855fcd99057b77de786ff49c5083ad591a067520f50feac82bed1c127c01f68f3d09e6d1dc4bff65524b70c65d9c4d66b45b3bf41667dd9e81e752cb2bc329dfa00b523e191ed1485a320f14ceef9f50fa7ea4ae4adedc11b0004647c854035f3c85c7c9065b5754fd1c87c72961d41cbf89648ae244be42055d084b1da27bb45ecaf3581b43cb69ed0e2ccd91048af8ec387a7bffd8885d0f70b99ea23d7db13425088ea70dd3520eec3664b65e5e6abba507558ce081c04ba4ff1a0a2d6146b81e4734062a8a8dfcb6698c3e23354f497c7724cf3248a3755170d6342bfb2aec6941bfccbde79b1ade9683d0bc78f348b89b8a464a828276643138e66be1fd3e38d32633d76fe96220e19bb460815468e736b21a955a1340998da8d8b16824466807820659c7786e2bb33ae51b248311c2a818d5f60999cbb3e82e58f9baa92fe2246bf8b2ce5407b7a2e21fd54cf195f949416e0f12bc7dc409a82ff38533c9fad295ce83ba81f52fa994481d233efe55dca1acca5343d8a10dc1cca6bad33395e931c5b8ff503948ce24155e3dd1b5874c51eff0d6a0c980073a463c440f675f5913f392218d2e7a7a075399358ac6a8babdd372d48a416c9d4006c6a195230389fd9bea383221b647973e3c3e8a88f9a39a5fcfeca752e082aa2a1954632b6707c8d6af4b6128c028f7585a4739b755ce86fb2ecf8df18bc46a71015a90c0fe91ed4ffadee5fc66381193d40b57b72d0206f9a583d33a5ca329a26c911cb9b77aca52c98a735a58efe8c4ff1c67b62b980f76d1793d5077942059129b04966ec8795d733f1594e2be38bfbe8f56e581f2f019df17ed1a8f6be6293bed0c45fa36db3eb3190a1860aebd193a7e9c4bacc40aec723da5946d11e29cfa4ebcf9ef89fc30ff9f0d5329cf1e186871a530dd02c89e7dd6c0c077fcef54e0e03b066ce23a01625ea765378c244f3339866b5fe506dc1c558fd06d6881804c2513363070dd51e054f026dd879a1692b20d87a6c864ba57353d581933f01cc9949600d0fdbc9471a52ab191975518843841c66f7c53c35f3b08cfbd793528101e2959e52e5099b9bb7ab743fd3512e21618be0bddc94e9463e8db0a6de9a927b36f750c2d77d48f3d7982a6e85b41dc22456a9a130e74be0eb442567c9f5161d753ced523fa89c4072663d72eff207f564e946ca942f6f2b4873e46776185f101d43cf7d263728d64e01f63818c428de85b219242e91ff03cabb5a56398a8f138df79bbfc17c68bab1ab10536a58c675b0831add675d6039eb4645c8df0be7f6e171dc1b2ca1e0289d85ec78041d280519109ce9f754b311137c58b5896ff22025609394339ffb0ee4ea69029a68d7ad953a1c7ff35450c3501e9bf91163150acb17abb3355e3f8e0c912e6ee3b347e284304f9bf3e128fde55571d506ef7dc281e8cb070c7b5180028f39e09e3f56eb038b137a4943cf5fa1707a6c32acce694f8ac3a108920eed99b80c9d204a6b6475f0eb2043205dfe08633347b3f189b500c12794021c7d95e1f7338f733669bc57f0efd57fd141e15454c7d1a47d8e70e78e477b7230b56a458e9ec4094700b66b13e3a354d8148739bf96dc575ac4f7e890cff39ebeee13816711effccd6fec2a5a46da402ee56082f1c41b1b026fbbdc5dbf1cd90fc86a62e8f4e6def437478ea3c58d725e224f7cf48aa6930378c33a036f1cc9a47efe5be3e3221a7ec18824b657135b9b282f30282679a5a86b291af96c9ccabf4492e79cac42e2218bc378e5c4865342ad4a6d1bf8f4eb61cf987f27ee49592e169cb809e888b468fedfc88d0d47517fc5e3a36ad45cc2f8efd5fbd662548d236fc7beb0e65a6825a8170ce05678aa0205ae8faf8859dcc943339aa684f3a7351071f8da75c2cff0327b49b89980eafa3e53b2cb61766e8a031d1fe1dadeafd08a2e15fbeaefe7482c703e784c8bb6c4c7ee9d17d1a5e659aa9096cb43d21db0a943e876f6b299354f2c5520a608c202b2920f415bb83422a49e32c78a119931e92cab3cfa020a95c93beb20e629d30f66c1d9e1a9e9a2888fec796074e0c3921b1610ce03787a71b3fde79d4166d7fc1ef69e19f0533ef11e34c6a5f208186c056524ab14d4b902ba25a31eb4fa078d4c1b5cb4d176cf56d2c4125f6557d3fa363003471776243dd23b1be4f6651ee29513c3a7a3b762f979fe1a01e31cf40ba7c057f0b9e53410a11b7c5f4fe898ca3c3165f12bdd6025b0d6c87588ad459a9c15b0576c4ca4b62975f582ba0c0fe4edbfad1ffe9537953e57f313b7e47c7144619f9de7f81aba3c4be1e6371b96039e79ac0f31f0912fb21e6d60f99e6468e88ab012fcdb708c14753afd9ca53596f10fb0a971e0202436dd6272003b1a041eac2813db0c7ed8862063cd65fd6bebcea6b0a665bef3a82dda0915cd215a966a9599a8b6457bec334b3bf26dbac29b28588aa8e8c760dae96d9e00a139dc28cb4844db5b64f2df274887d45936737dc1af072fed9a7d7f4785b2c4c570c0c7467b4bdd5fea3e577341a65bbd121bd9a72ebc33b2bc27b5f624cc3d90380bfd3118b679937f9e065cb1988ebab080d069c8ae768d149dc1acde53b04804328f12bf4171b36cd156e7e756dfe7b867299167d4c5c16fa492865ed3e2c8cfb7d4224305afb71f4bcdab87771acb9475eec8665ac9b7fd45a870e283d1b296d92e4c29e37eb6abf219080826351bf325982b18c11e77d2136e7fba2cd51bd94a7d102ccc2f0c4573ec18366973484acfaf9c3fc6cc6c660245c1cc5157691e029d42bbd1cb5ac81cf308b1b9a07f4eb71ebd951f5a6315d1bf3913b26de56e15449e7908d0003254a5a06de8547a7b379bfd46051110a002d4631c16f742d7a6cc47bd3a0ed92594bb4d31557ff390f1e58e748c39c0ab6501cf876e13e2e8e0c0fc106b227167fd76341875b2841114c973b2d154803d2db2298c42112737033a46b126266762771a3f12a9e905c4ae0bc58af3a28e42995551736e2a9fdf7e644e14a88b4aa460378a6464c3463417cb4bf7dc7d090c2ff7ce1b32aafffa22bb125ec94a2f60cfaffe24a3691d315decf2ebdf5291ce81547d748f56452c59cb8cebc6c91dec619064beb0a2544ecf9734cb1f4505208bb3f305f9365b18729413c9d12bad419f23bc723ec4f7628ff094eecbc2987e93fbb6665012b9e801d4a73a72ef607ee7e034d3c982d808377a6f212a973fc9aad220c7343e5bc94cd11536ba06d666f418ac4953d7ef1f8cded8257143d5c4b0c16268a1994e6cc379d7e747d366dde98137906475684a45051b3073a87398eda0091fdccccc878fe0692a18b0b06ef604e237678bb670deaf5d68184b24503acfebd5b3288e523073591529f350c8836f6b224bc71d3d7d0b626ba9bd765eb9f57a090fbf870b2775a06bec90423cf7e98d097dd96ba0e5ead3cae9f78d141f5a084bca8ec572b394132a8acb8041025606f62cf07a4b7e9a978902a8e52c8a01a9c2e6dc714da0aa80fef297f6f182f65ecbe4e444366580a7a13cc66741ec250da7081564e51ff7d3cbc2a24d89f40246ba6e16ed1d94fa887f8bf5695402c0ccc845cbc288b86d1027f31ee3ed6f080ccb0b292cfd142d1c82e77ad147dd27610644b95d0541e227dc5dc803213c3ac059fa26f69b46b28e8078d5eb65fa0d42d7a7d06533ed28c86b0fc493d1ee1343954cbb08fd305f8542904181d8eb4e62c4d2bc126a6a7b0284694872ca1cef91020f814ceee98d250375b04532916397ecf16d96070cb54a9015373c7e9a6e80f7bea0997e09a082f5fbfe13e5df6f4c805c8778ed0738ac27c7128c854ce10e2011367d802eab18bcf3744ecb3b32404542cb14ffc28d3a9acefa781788fe8efe0738120b938169f05ed7569e5f55f315aadd80a72d14b46a7b96e192039ed2625addddfab08885a07e974f81d5e6da5ec0f88e52304a05ce4e1a03fa564db2f629b7e46cf60f46caace83e75fff360a1756cfcf2278ddb32fed0f7740ce553b409fcda448a1943e406c73be5af8f3bf46c031c7b7259beb345d2fbca407fbb6c54bd67aa20c71a56e838841b52c2a8813aa775660b6036a38ad1c1ead3715b43d300f088dfdc74fd34553bc8a226ac05f2dde5303803620b4e2eb567292e8a2fb4f8430d530c49aa73d81480f834815205fbc0dc440cf7246fb0c1f67828ff7cd0d3d52e72f952a1b852633b1c7f13cd721d5f6d50ac19dd3d9a1cf3ae28ba4a43e0d620b32b4b1b6038d5d6b15e74f043553f6d291aa1595dfa95dadca6020c50fab4df2f5d70c4985fac9030d8f3c95b8aa16a3a5f5a6e76672ccd39157f0a9981f7abdad9283dbeb4628d6bb4b87995fe0f7386259a26ba8ff815b339bee0a54e6886c2e0079b832f7e57aa1272b73285d29fb598f641344609990cf9eefdb962cab767394fe6ed52cd5751f5024b9ac9ae8631ac52b2edf773689135ed25fb4b1d49206f199b1ec264601a8bd693663faac651587c1edfb2b03654a33cf0e777918d8c64b52beeb37d25dd36e201c495bb7f98a07f60b32854d6ffb304481eeede931b31607a564fc4bee797389a1fc87ad7fe7478085a575dc537fca2ef712da5b59f7a7660ad3abc28e71477782526e87851c01dd769c1979f88b4f3f75225d80df3bc6a2bafe7a0c15fab454a0fed530d9d357844536006a7e8370247848503faab314a1c59668ed0b50597cf424c49c8aaae37fa62bae644c8a16d1d22de5a9fe1495ee593c8cefbc519555f40ef20df8c2de25894c69e7eb2ed6f87f849226400ec8431ae60dfce0dd2ac62e166880a3bd3a7ef3388d3857d3a20e8299df873aee72d1ca764203b648339d6e60bdd073f5dff08528cf92b0b133b7a2552f13cc05188b748c028154231e7cdd734b21771fc4e1c05b3eb6599de75c6edae39f13cba4e86122067939ae280ec2fd93b7ec496cd597cee77cda2da242dafdc390b736ecd01f47e10d4438986eeddfb9f0624c67824e2e15c234f40c563a6ebbfc31c393216d7c05b55ef3d141f1277627d5e2674910e8ae6c77377cfee56fdc79a9452f1a5fa23b17cb2d25ae25c03383223c466ed095a334124790fab110fe2d119730f031d93c3111ffe672c1d948e7c0ebf73355f752ebad2c3a34acb1e0bf224396d78762281bfeba0c2063f630233c6703b9c3ef276800083adcd42a40c45cc55c4c1834167982ce8ad0e4a46c83526abea09704dbf867eccae0d78981a5c3831e38ea06b0e8e1b4ebcf6bf26cfe37cdfbd96157aabe13d158e357285758828f9d7057261b0fb70bbb904cbb7fba1d74086123e37d74cb03f384556584b61874b283d2135031a4c4bda913cc36a595f33af80479579a5b8e5e490cdefee410fefe6c465b3028bab73898adba717f8c49afa9611d50d6e2bc0123ebca0c3c9fe4b751a60f328d375942bf202e39df0bdd9aa3f000b854ddf4d6ccb472be75cdd960234441726da112fa25e90bf0db59094191ace2fc78c4d1ce7862ddab99f4fdf66fad3875e6b8f4869772284f540b188caf82e89b599c2539a1369c6e100fc74ccc8a93456b75fb8b27ca7bad18f6d0be261429473871e69fbea2a794b2b7f54b6f152a3b6d29d65fabd3b6b1023aa018710d6db768c25176ad51e66f796708e56373ad7cc35e7b42c215864b480ae6220737864c1816001377602f818f676dad355625c333ac4a89ea150ea6eb001988e2c5b98b5658da29abe8b2929c86cc81176f0fedba3a076c5cb2268b7c1564c799205dd25da9f6864779fe413ca9441cbba86f9d3145492d14094fb0142897b8a31d73c9da6512084bcd7a1947ede4b53b86ea4ec943a720f36a1f6b6caa9d5f7455ace61344312f27c5a022cae3a411cf793efe910dd8d36f63ab0f6711ac70b4366c35c11ac73e4faff5dd5e2ffec39734e31f7d829599d5f563e8655bbb77751124ae76f9b0cb34fc97fc8ec5e416f1ffd13ea203d3e9f614af37a17275319c977a976a5c83544e7a60a215de796fc5d70ac4a81b9d75f837eb85b5bffeb84ba444445acf38f60008f403a0d1ca84f8724032f75b0ed7c5e69a0784c494c17671e6b61a4d516d52ee425eb9717bbea6a4c9ab7de1af72e20df492a15da8489c6d328fe4da171ddda2b3b94da40ab54c6abba617fe121ce0ab0ee8369d38d010160727d8e13a3eabe14471d790fc5a7cf419173817523b0f29637c4fb2fca62f9bc646ef2137b7c6acdea965ce0a561afc3677df0f853d9c2772f7a0760c031be857aa526e56b6ec3aafa302dda916a01e7814a3cf7d509f7edefe4c8f599b61869e3a77de5a4ca7e1ed59bdce1f1e6587c69a3b9adcc45232be18dff613c2a026c67de6cdf94f0a9fd31b81c9feb49b659b3f21fe4e2156b25a77e73c00ca229fc7f670eea359e231724aee390454c151587340a55a463091616bd6bb4b01e71d290eba0d9c4351f1a4721ee3109dfe28e73987ba66b03cb47c8e836555e9a70a56a890640c66ba5cc533d3a2d95ed7c4c5ae455f2e6b392352fefa32a8d33f1ab81cc45fd7a63119294c38ca211c7baa27bf6e1de7499d4f839cc89391a9973ef81a1076026229241f416ea7a785362b0edbb4a47dc011b08eb36412031ada86e2b691ad6c72bac4ca18120cda3ccb32808902274208b844577ea27438cecf60fa09cf12c2a0eb7357f943d521490dced8e40dce3739e415dc8f51c09de87f9638df121626a995d56e08d13ea61c2827ba5e14fa38b8e17747c1c07287545ff37dc8fe503588720f6412aba2f88e93e21d5333936d58ee125bddd4f016e318af8cfb59b21515dd2b0041f0d1e81571d0db09dba0f51270bd48726620e03dbd2e3bf1a45cc7b661a2d5bc42bc4df3a1e26dba1d59a13bf1744410e325dce5d410cd47b09277fbef622e920f8095d6fb8b68849f2d2ae551c8eae3db31c0ebef52a7001b2071e8734b2cc9e998488b451e7ad325f6c068a425badc95f393e2f414236972c27558df7080c2ffc5a0fc5c7087dd0ca8c509b4774455cb20999b42c10cbe30be2ded4385aa1854e3a4be5ebaeeef3c9134527fd9fed7fad0a512bf050c64805893ebb43249f65825a0be4029b63e15273bd06c9d11dc257ac95367cd178367341b5a536c726aad192c9344376a90f431847d026a8882b372eb5b5f351722ad575c277856b2bd6347ebdbe5b5cec2bdb626a36203b963ddc8bed642b38f1e24bd6116279383b7aecaf7f38eb355f253f2fa8adda3afa3d5579be092f12a6ac1ce9f8f3acdac8acfbf72e6c09e3a4d2f6ed6f84085e7f1ab96fce13bee87433332be6bb8e5aaf26a8b7498cbb35443a6d4c90dd75206298a1c9b4ebc9611eea9dc0632de342ea52ff45add57fbcb0a6756de4cb7fd1d4d3a9ef3a41f8b2faef0cb58ff54317bcc54f646d17c65cb275c0b13f2496f2864df294e68a2664305757cc2b61991abd1caac2755d74bd35026710da78c1e549e5a303df90e1e110d1f8e2a6f9a5658c3e689427e76281cf1e67ffbe1718ec2c2a63797f4debdf84118cdec1087a4d1d4ea43465c7dd517c561fec00d44b74aa59e19328a6bc8bb2ad572eb244001adb6abddabd0724b96790d343b3d719b9c0404dcd227ffd8e47d5bddbe380730c1d84d75eabecedccc3c22c0cedbadaaf0ff3dc08a4750ee21009ca2abde1e2b5a41b580e1405e0a8fb17b60b56a2f64656f5472f4a1b816ecadcabca05bcb19d20d06b9082031bb9ca48618200366cf4e8094cdb9ce69c8150e53396d3fa1738eded80dac5fbacd402f5017291d74c94fb462c16682cdb0e9d2f57e41ea1a12c8e11ca34eb831a280b74ce76f99c3d97d87f2c24025e6dc23a00b26860beadec991cbf4ec463ddef61ede52f403bf8805f531461ce78b2e3172602a4daf7f099115b9c636bf2e102ab88c437243bcab66463b32c8f0bc307e3dea9e4709af13c5fbcdf6e77fd082d152cd2ac9187169ecc5158e75876ea935b262dd72d038b4b7470cb70d219a459208fd54931a4d10a7c215afe37122510404db8a6db0e6f7fc20ee7c2638a018e0e5f790e92a005fe77829208dcbda749c4df98ad4f2ba0fc08aa7f7c007dd9b389d1d077af2a4eb4ce61dcf2d9e45845b936c0b54b97f5c72fd922e7533ade334849cd48d6f9600424cf9a3c57775002d18946c916589a8dabca2203b2093360ba9fc8e229297f0dfbea30443070600ca8fce3922f6e8fb723ed171c06f6f3ae0a3efd2d4cff0536171a89a3eb7789de7eeb764ebadc27d454955c6d4f55a9c4fa6e0c12ea0edf798e2b919c0ceb8215cdaf81498c298b4c694e91d65df0d64e673a7b7e2e9211096778b895960a36721830aaf3f615356b422f17a0c0ec6fae7caa2f035e612e36da4475a6d36544acb9d13c7fb47275b26ba4974f19275c72e691f7f81cebe0b496d4c133b09c7e1479cea311981bef82b29b7c8c4480fba183c9013a222366289cb4989c3d726a072d83f383e3591b129ce374140bbc9ad76963636a587211028ab67cf62dc7c42fc40762b36426c4b033def2bf4cf4002b418093d0af51c6af7bfaf1d95a7cfa986544ca61bc068a61eb5baf96d74fe5eb424befbd225f7f1acc2d023fa96dae9190c228a6708b60bf889025f45ca0752c65203787ae6aca745ad6bb69dcad385e66ac20edacc60e34589913bc36c7d11b7d070e0d432f48592a1bace629ec22bb87c77689d16db07e20f9fc9eee448d30627e7cd4ea292f3979fc6d97a074aa2f30bc96d434ba358a90c3a76eb92333235c036eafad47c91aa35614cfb48c433079a40ddfb787e19321efa0825881bfb865f035f5eff0f3a7705cc07e83051411efb20db110a2cba1a769b62c1359225d0eff034b504f9f8ca86e12729ebd77c8e1baed1aecab60490a197b3dbad2c08d043c71fc45895225fe57af9b73747b811b88a715498ddec5dfe6c798e747f1139348fce30115a67db7fdbf71a1c163fee63e59b3afe0da5ee45236daeb64830f9b8783e35332c99ea486715aca3a25cb8f70c49f2569ba1e88cda5091c4736d1d43acbec9611fd2d42d37528b9d2ec3fc62bd03002353cd90c49b95f713e3f8561a4fb99e52b165b21e380a7e84497539113060704b4ee81ee04581aade4c6adf7f7205619d7ff1eef6ccc38c0f0373eb872143e4ab1b3d28f942d617290686d3446aa15562e35cab47f957f17e095ebcd544d2d13d444426587283a08c7b5a1db93ed9e24fe4ae32b7d9d1dd3d4a93d48e926b86204ac4a18e6fc8db533b3cd10fea2625d8163553f1ea87fac21241f4dd8bb2ef2d2cbff5ceda03ce9d759d4d22363e46562bbee137c01ba76c2e7651fbc47be0bced5f5730ccf41a09b1583440c32b382a21e2f3249f47472a08a8555a5af71337fd077923287e9bb2147ec8b68f2f13fbf6f66ab7f52e9822fd611446bf8c1bfa7190eabe38e143ac8c391525fed6f32ac222354caf0ec55994b11468dcdb6c334e0545062e88c8108535f30074c5193fdc2f06dd8270fd17b9b1fcd94fa96ec4ca722ac79626a340033758b54a24778ee6c665a9c56c555d82d60633bf32835cdc7fec4170b873ac793c2b686625aaac35285312f5ba7ae319b4009134e839083eae78bf5b503f65b1970590f643f5d6a3a6755b41ebcb7fe340b9912446dee78926a6cff79dcd35a5c1a698664434918fd3c5d2107d613887b491a33354f3ba69107590e308daa6a6adc407c83c2e920089df13f83ff80ffa6f62e3e21b33d828fea8dc6fac59843432a9fe2c430e229de21749882f79d1f208204092f5573f088f0831ebb2f567a5b7b945490b2cb6693588c35fcbd32eb07a2729a29c44f3b4bfecd25994028c61cc5acbc3345ed8a53fff35967304e33aef53126fe917eeefca6da208020ed4b62c5ddb75c77a0add9931c5343b6ca631c78545a40140b989222d4f39e9dee94fd1c049ba1b16474f831888c8ffd0bb68cc26937a4b48fb15a941824e7306f3c323af92ff90ee37316c2efe850c70752eb1ef98e84d1ce3472a1046d9212230b3a138c1640c564dfac77902f11c4eb155d6a3b4f602e57a94f570a02995e072cc2b0e634b61d7e4cc79f3a0d5e80f2b4b9c73ef658f415cb37624e1d3d4949626a882aca80fdef868abffbc14861399e0822dee1b442f843c893456781632fac46a74d8526fe012faa027e15e4b0a9a40a7f36b55d4ffe546fb90c19aaef368b60ac82791316d722f0f97695ccba46b1b12184b7611762b5f2c27478c545d73d3393b5e819d17e81bba9bf729cf872e1a37c91e3729fb360e738fd7121489d93324ee7a3ea18c56c7be8ef8a764efe52bcf4215a68c40c873a0337a2d0e790a83f317230a08d028d370d974d7f570f888a73020dbb9bbe54ab7f49152bb916c1342c81f8366e6fc008ee6b355fc239bdcfc9a9c309641ad14de893604b98bfb35ed5438c0c11bb4d9c2e0e7fc5bd53c403e2920d960538a6090aa57f915db8cafc43a96067ece967b4de32a0e174dcf6d0c2ba51c722dc3d194b030b1ea2cbcac4baab87ee2ebccfa66856c3e89b059b9461eceda6b8df418d017ff45b9427da5f2277952ee81ee5dbcf4d35940cb2a6e244c443614fbb38c91909f273dc5955e953f44417c16989523f5143a172fb2301f795a4cbba17a9050d4825d490333d723383757e446bc91ce859815108119b6b489d48da6a86e9cc27655bb23a87cc7f9aedca99b78564de2fedd1e96281cd506225e1274cc3837e20cc7584eaf2f08912dd8f86825eec522fafd1324f41bf11a98fe0713822ddcefdd629855c28d67c5f5d7ac428e266c680567af9bd0182c56e64461af709a8444da04a342d656f3ada3599b7d19e129b5edbe98fc0a677b775d798a8cd9e7cbba9753dd41d0def169b23002861b72bc711fc3b4995f1c8e5426ab846a0fed8e491a40eed6898685a743ec0c31ff15554b0704d01c4af9d8340966540a2b0b3c28e9bd9c926486dc1bc125998f39d9ede2b6811934a013c76b0a8a38a962abd2caa99dcf8c15250fde939169b2865df19b028db61d98794da6e3a9f3aa08d674f35669dcc7b7ae332fb7bbc24c92ca94951ed9ed01f91e97d84ed610aa3fccd29dfcaf9de0040e979b3c3807086a5c4c90a5bb6c9288e2f61895a68d7332e9f0f1bcadb870d232e5ea46f821e313bc4850d272639c7697d3ed7572224191c4128d7cb8e6822a3366308cae9b2c16b197f5ec8bb91129063fd8b0481985bf27def823a60e348f2b37b6db15a15f1c69d4dc54e9b46f3b8483e5c41f97be69f2cb4654a4b9fe78b7d8f2fbca213611886990ab9fe52bfc7fbd33581cc3db8aa711c45e5ec08a721a2fdc26069dc547e44a08664f9b1e6518243b21bfdf0a229679d5bf4e1cc65903e13cfec2f83f96ef4b4ff6f0be5af9ecb48598b0ec99c0535039f82ff5f3dcf2d9818551e823eee89bad82b327304325851b34195a3a0e4e4ee56718978960a51443c28f1343e7623ad8ff1eb644d3416012cfba47b1e60875103b9a9924d5d2344392edc5a36548c198cfc56c55f49abaec4bc5ff5fb6892f59270ef6ce7d988b7cb7e5c97107c33a03d1e2c2779a824e396e542d624fbbaf4e9148a89d5d54c339718c4f47a1499798a67d71f4c878ede26fe5288ab82ee3eb8ca4ab1caa88f402143c05c903d7bec8d215ac71c2a4412862ff29979947a17a094326e1f5278db9b453b2111d4d8d7a6aff23bed2ae2a6458c003c90dce397acb7799046b6daf57f3cc1752b33372cd634c01dbac9a4f53895e0c6d863672918c889aa516627f2f717fff2d33676706d16b912c757a40c08a41d32858fa30c631859928523e62b1a0d0a13ae00e43e0db65443fb0fd2309225bd9e007c70d5b984f3e5fe90306cf303ddb9844130c2f3fc39dd4deecf5520ceb6cc6f9581ebabd4c18066c51e7661cce0a65209553f9b921d914258bea76731aba2c18f040a322d4e08b6aedb7ebd236305db5f898aa8d8bb1576a8e2055c68b3aa4f2014c1044dd9630c7a23ac56d597e0d127d88c290684a03aef9981d2ae0f920e1b7ff4428d113d9271429ebc7f1ea9940e04c323fc1a72447a63e1f3228ead4ed5165a68e0a1846e0247e2d9c80eb2728af7c172ad554a66f5ee217f10be710cf071ff93491cedcadcfd7fde0a3cb485291eacf633acee5d3602efdb7167f7d0ebd2c41e86f86de85f7b6dfac3b84c1ab3aabd12ed6fa7b005bb2efd90497f2228acda81e3fcc043c46918e852a179a8ddfbc17b3740c29776297413f2d6e984300d4b8d7b6ce5c9b50fe146cadc205c9b079b56ac5210fd9f7f8b0ff27f3c0a791f71ee50c39f1c50079dc5e4d1882d3ff3c0a6ed246687c81bf3f4c2202373ecba7d06069ef6e6aee9141d6405de89fcc4b90c64171eb757825ffcaf526adfed04f11028a8b44184c783da6436b38b8369a4d4854195121ef53486d3b3860eb0fe269a4811e92767231bfa70d479c3bb1eb4548189ab62c6b50ddda8b30b3004e2c7455f1668fbcb467f515b9a0990dfb2ad9b64aa46ff812939c823163344022e54e52da24719d9364aca2db01f077aed696f1c5dcf2b42043bee372a5e0c2d4fa1dea0e4637e8942876cb4f5dc6d09e6559be449b383105a4907faa38c8bf3e054e26506d97bcf1f37396ffe44414802f48e03c1e37658f5cedfc425e516b2a06af232cb8e7ba9d1cfd4f4d6bece5ed45b4ceb7a54d63399e92a0cc731f6944cb2b8770aa9d45a1db70110e3975c7f17b3a619c980700ce7ec24c7526094fed2502d736314b03a29ccea21037fe2ae97bbd051b2e4bd2477dd059d8a044053693c8de776fefccafbde3d93b60a52bce7443b096e809dd953f8433d77d27b4c5bc1a0e63d5b7509f633165e20306fa866b3c321945a21af6f2ba68db125df35a6fe5170ca62b22c1002b7faa9cd38003c82324b1ffb6a7ade9826989b7b86b5bd6893dddb0e513f94f915398022e1eba0be09a611f0973a9217a562b3f613022a469651369e264b4eb835d14c5256f4018aa381244abe721cb1691a85db3fa8093451ab99168506ae5450b45ee65ac1439ca04f6d1ef9167d26773d4516f941e3a156b189bc8f3522d5d168ea492e52bbbba6264b06855e5868adabc6b515c19d50fb71d69b9f310ab722fb2a4dc57238476f6e419fec0c051ebcc95369932fe8dbcc11f903b848f66d424df3a5926fd7b1258f9d094504c522e51fed086807d42b5882e6070be793d931cfae69df492dfe15bee66a7a3222038f1e930de283e70140dbeefd8dbc8ed62deae795b3eec748ab41206bac2bda84a7f28b16998e7771a34f9d029e50e4afb9a829894e32ea3dbec6d7b99a03fb1bd6930965ee29ab62d4f95174b4b5a3a47965798a2f92e19e33a1d2852138d4737e762f611d7b240f5ef77b762b4fd76cdd8ab440aeff51c1ea11ee05c6be26ea928b3a12dcd5ed885b8fef6f12db62ae9aadd53623355a10fceb6ff152878f14377879a88b6bf3321e5dd18b1232f487f78b835ea05781884733960685058036322fd6f4f87f044324b9f438636fc107e5bb538a39e7f43b538ead87c79338cb3156edecac4511475fda58abce72b3b3e911daf4dc3fbcce79a557b700c12003dc20132ccdc31c9eca69a559c6eec705dc0af5b7bb2c2dfc68c9f3606edb893fb28b30708c6724fb5182247e8774eae76cc2c65342501b11f07a4d4f924a6096c320171b2b2311e60757bd994f30403c8240712da162f26f1eaecdd52e8a90542fb0c4b4873ca03bacf9dfeea047e6586127fc0ce588290cb2e26c4853d8ec7577a613633f2d14a982fb2703970b6f82a90cc19d9f4644f2d56b8d8078e8302ea3ea7f785e36683579e2b8234b71fe791e075bf11fcce3bf3081b65aad2d5965316efbe5a089b3b49eacfcde4882c2ec423b5dfa3aa3412243f3e138d38de334dbd848bf422a6354e9db798712b7a801c3700311a4fcca64169daf80ea8fb76a26faf5c160e7f3134dc4ebb5b56d9dcfaefaa673d51f7adc3aed5a196f576409c8eadac8e811e0462216a5c26fee56e3847c01bf88bff97a1620c0f1c4fe1ac9d607f6581ad08614dcde894f62b0a9bf0c562e7abf34943d83051879b4224c94cd3198159730edf90e7a9bbe57d0a75601c0ea8b5be26ebdbd6b5227f7b017085ef9174b035f5b44c72c26b797eb59928be3cbbf474398a2a38920f3d34bcef812a4ad68369c02982f651da3cbd0b3cf8c7601463065fd2a4202d2dacbd49bebc232ad7cfbdb0a6f87d81834f3b8835db9a6c234cbd171a7b45b205f56f0129cd2d30ec0612aa19269f9b1db68fa7e01835db201b12893ff5203e605ae94b978476b190c9d3ae2a26280b651f6673b74d3265f09c5eb772b303987d6acf5e623aa1a0e5e76c1c693d700c20ff9cdd8e39644820d2e40c7b8e87c84dfb85f3bf3b8f2e65a4c7d892b3cc455d5963f5a6d16b9c8b1dea50d49c15547b1c0d3ba73ea3896c5a30d598899657327022633a82252c0fcade7e0121590bbe247761be2bfeab60948fc899764e713cbce43581d0efff9daffc5ab5f144ab08a832329b2e7684ab5fb99e79ea43d924c561e39c777fb919dfd1dc80bd3694dc686ba37093074f2fab89034b36986457a2c7f6298e59815785e8fe409a75623397ff17d4349e6112f824d6642936a14e8a3ff2ef52eb12d03c0e290dfd60f7c8601c5da6e58d0614dc2a3a88ae660c06d62d0d635b70b8cba8eb2322698fa9dd765d4f7cc59e9d3733d4cc12175cd04a4052f6425783f597e7e18920b5e01a259343df35553760e639121fd6c53376b6bd73b8e87dcdbc7129361a4d177de57bea1c7bb3257166903463c6b289f0ee3679221408e6ae2809fb80a394dffab74b91b761243561c15de6d50cce4d5bcc07f36c19e633edb5430ec87b3691713f185e5c9b49ac26007a1d6ee46847bf9c45ded53b3a4540efc37f4970aa17ab40d5fc44e8bfe01b54c9ec98ba3c0109d32f1feb47de0a4abb706a926dc8959a4ed07bc3fd869b062ad4c5c6d8da2596cc6edd545151c0cec5457cdff824589131844db564a8ab260e7c450e3170cf4959949149eab5107756ac576d4c10f02807b5d181a884418285246866a91303edc94c0b779b8eb3d89a7510655c5b4ad06bea03d37402dcf5272b2f99d80168da51f524e97524bdccfb3ddb061f51786b0bfdee5c00b10256ede1056178119b36c23c13a78bd46bebc6db497934bad03d534ed885e776c1aabd90815a1986ad64b5114093580a569e34c0071a0ab00dceb272598c158f28456346a2f5c878c979bd416d3d7eda3d8be4c53c05a082d106803e4aaf12d0a024355a85dbb87d687e042d7b4eee37214b80b1f9f50f6654b767d0abb41a44fb98eca57e875edf4ef80a62a5148a8b69f75b93e154bab6f91b8607d71008d0eef6e63b208d41467e6fcf626660c3b08b1bd066a013050243d59b6a6823576cc66eb9f17d9f61d00b6a22ea671a226743d7588c69108f9f24e0f47f31afed5bd95b33687d5fd016994f972c96518d3fbd285e6534c2e8d5a0beec1535b5a374da5778c2aec2baa9ca85c72726dfba61243b4be7ca7e06773e91919bde57d1f9b456f3bc91d6ed36961802ec8c0d4cb211db25a8b32c57f64d1bccda979f572f94ff83453aee5b2f51bdce8b789bc4006f9f337314e1472edaad3f595a033fa6e39779a58e55782361a32cb6ed8b4018f9116e7027574e4df3bafbf816a56453455e325915ea55578c9af33e0a7be6e03afdcf7f613c64e9f564218733a38973ae7a8dd56eaaadcda58604143f2dac99ae10f7eeaf26164a75897e2f5a21db0a206cbda26fe2dffd29feb5a93d56997000cce3a1d2d8c0205f906e09252924844506f1b23b5b692a554dbee10c899e8d6bd0d9db3b91dc0db660a025c1047cd2a693db7936079c4f092e099bbdcd81b315e639b3684a6f8ddb7c25f69c8027512513c4e60a8b13322b2d334dd177a6b192651e00f158a73d6a68f7251a8f819ef841736993b1123ff6b680981d300b70bebabe111cad89523a24f8eb24b155868f1f5bcef10249a1e53d091f533c44f78b9de42ef9ee763362a23c3acf8145258b54cd43937ccc96c99c5b44dacdc35443f1751c185e8751480cd7766ec74274cb45799f0353789a826b864e0928d1e4806a57f844f0569d8a30c1587b7919508f9217cb4fdee4ae4fe16e6c9c59cd1d07e158a9624bf442ea3820bbdaa847603e84739c568e87b0af9efc5675f4ae71ddad6483e8d94c2d8370a573a5392d0e641dd758a4bb1d855c55fb7dd7adab4e4b46b54220a30642db6906331073cf110f620900e1520e7d4438c239308e341cd91794ace3ca1ca954bcfe05e82469f15c5e8227eff266dea2a2335b3b2cf74f5233ce734a062cf0e8d7da37c9247c73ab3eeccacd0ac6fd1fd968dadd14d416c69272d27d12d76aa55d72bccd38c26fb4a6a088f2a8dfe1253949fb3709287b895c283df66e0ec0ff58fca947d6d6086af9f42c16b22d50e6b69df3e62ac7f50cc050e785bf374f5ad8ca69e3e599a3d7f4eeeff1ffba078165a978764540022abc47772044740ac840017b529363338862affad009d92d9ec19330e56edac0ea5ef20329b4501935dc3de2bb2df6ac140c8174f8577b60bd3e642177aa61a692cb6dc80559e2ca26bfed656b8d8d6d62da7b611046876bfd1fd4b28cdf1487badd371070e1bb1e1ee87800ea68251aaf143da4162718ef73b64b98d69c05d015991a8d97c5238ef4288c4319b6ba924485ce7cf5489b18c253fe5d4fb3672abbe46fae8ff38d97c3d42e620aa85b65ec7ce17d3309d15a92933405147cc609f3763e58ef3d183cfdc18493f2459a32772c05b074c30204e8186119bba03f2f5204a80069b835340462c3778f68328821af26149ed3577ce69ff270d14367938e68b24f098cf63bf142846b915183125946f6f1bcd08cc51dbb81846161aefebdb45f3d1f771b94cf71c5171fbaadefbb602ca2326a2d0538f6756aa4cffdb02eab28291c228507c9f7f3f1732282ca23114bcab5572e92357c980dedfecef6506373cfb2ca663fbffa6faa3f1b87eee2be4a3220c7d5865c238c94f7a16fb717f6fd1404c6f9804db630413cc524c64b94413990d9bd090159f2eac3c5915761612b6745fa9bfeba08b1ae869bf9fc01b40c04682039a71903ea068c2b2273aeaa4bb10a8178f939e9034a6562faa6fd554ea04d77eb3a7040fcc3936e4b6743159c36ed2cbccf87b81f158dda9c2168c31e3d22de5a524bca67a9c76b3c252c592f950ee4a1ca70a0a3e09f5eb2b09bb1a9d505eca11b2afe9df4e4a7cc9f37be6355af728d141644f934b3d88b9d435647c458679596c0df934d4a40384ee5299e993ed5ce9fc58c1b0b40a5af5b67aa82c1b2aa22b400f1d45f4fabeb9ca69ea7de87dcf97212e3b1c5d839db621d5a7e561e5581d7421a3543aa701a284ccb122a93ae6958fac0d0562cb3e343c50f95e48ab4df41e59eaed745ec2a20c0e1ed50a640dc9377fcc5f6441327c6179f7ac279759bcab3a18ae36b4eae1c7b29ad5a44082a79ee1f8310acbe7e19ff29264f84cc64559d02f2057af620a0e6b29dbf105e02d5e9ee30d7b312b6638d5f4aed8e9f033bc108512726e4adaae1db811d3503125a55d60b0e58d035324e41a698a842a563d95c69767dc138c67c23d580d2e88efb443248bfac563468d8ed706f53df89e96a01712d7746ef06d0ca842c9b25e0f61dfe9c76e18beae51c2dfb3a2e114b05775f43bab18371beae4e31a102542249568b4f97ab9cc4c848ed0183f16d69b65fcda1f8df2ddd97ab58a7b55b48a25cf4ecc425c7f21a6eb95563d1e1ed72e75bbf1546d6bf4c5ad82fe25ebc1213aa0174f2fd59e4e748151e13bb6270bcf951b27a4fd2ed5aa88d6fe648ab998aa2d8d6ce5a977ba04f6228cde212e99bfa4ff826e36409821047b00a4ce791211b03cab952271702d2b3727c89d692363d5aaa4cad11dc5ba8e04d2e2b84cc118f744ce468ee680f3b7388a7f0c505ce830f7a654cff674c9862c57542b70fbd4e4743632bd23c23f65d29a8e73250ba711dce418f2eed6e7c0fb64c6702e28f6ebb9d8e683710407b401b1e97e1ce255a74458d74173489905c50868b28607e997de23523c320b1b553943fffb06c82b76368d1a889e730293efc3228365163b05996f9f430edd71b15304235a9677f1d6476342d5ad258c99918602566d117567e9ce9779303d116155dd82342c9602716617b0fd84cf69bcda7217024fd8fa82c85fcdc5925ff4b4ff6c77c13aa43fc9ed4fb61543e268954eb3d345b23366302672668e5a2a322c33f7cc603569fcb00d25cdf9e6792231cfed263fae3f40df3720ce434596620122ee941311bd08cd805fcfc80308a58f771d80f3d17cf3111732e9d32400c29c527f2b0055d10b1e9367b36585f0ef2646dd6dc9bcc94edaaf66af356c7dd91d72ce5ff1ba5b8c71789e47f8844a275c976dcc0f47c7f765ff7b32e2cb8583d6f6c718d55f442ae9f21bf76b2d373a1070fe4f733a69b96fa2902d56de83fcce92b5633d84b9c07f490a81eeaf80d2119808c18b9ab5b1cab3b83092e41e4f4076fe94e0dcca617f8e1ada744873c8a484bf6f4d7df4ffdcfadf5d9d924fd41962bd061773ee5e0736cb24345222020213e63a9773d2ba1d6a5375feeb91af7d8a160c611511dc8bfc7aca286c8b99d7129005a88137a49ded8ba14430c627424977e3e05714bd541d824491ad4961c5a09a9ea5a7d4467b89140b7fd7927d73a029cddb24dc17fcd8cb1171eaca63df50e5eb15faa9e76fc8a5bec49e5968ce8ce5ee3add4db7f34baaaa0f0839c52e2996a2654a07fc417ad0d8aca411b347284a580d09e3a5fd94de0177618984306cf2f4665fc9a24fc2d13bd595988a839c684f0698b128b24b0f47f73b64f453339665cabdd5f50b766a4d60e94cef5ee5ca07f9b10b0efd46272ca44bd6687fd9145faa0017e77bcbd35b712d03646563a009631719b59eb356e4d347fe8289354c4603c27bc2b8572b52b363a8645d3efad9e1110cbea87ab070a08f28b8c1a4f47c3413ba5c6f5df055d16d2367ba9741e8b4f4d3e403f89d936ade0a2e65e537318185d96c636b6cc0fe894729298d5160442d6f22eb00a3f1551012ca2bc158e693c2c58ce94a21a86edfe9d6060bdf439ae33ed36f3a2c7bc7f6f35dbb87b559f6b46be36a3e96d7db1a6c23e66e1819e42afe4af3bf6138eb3cf72f3359afc69a402389e48f1ea0011b4eca0ffaa68ff4c13ef1ae1938f5315ca3b9a1404093538b674d6a6f737021ad980afb137cb8c2638dbb2d833919cca0ca5b8c1e4a1e59d51fa39f213b7882593399d95c14d0e6a79f9cce81c12b323e20f36c63e8bfc9e1fa1e3f7ed7eaeb3f77360c901a261e732d4a46cba422a073ccac2d9a4e0b21ef9e2197be15a52b04918baa369460cef7578d1c7c7f9e94c0b036b1c2fd08c83fb0fd089c991292ca8334fe2902347b46c02b2b9e23a551294e6b4b7c5a6f8432f02788be1d317544dfe306e61ead4f6eaf5924ffe13f5e8b7fe54400eed601ebf6c5207be378adcc7a5df5ab2454992df28f0957a7a007027a7c59ecdb86518a493e6fe2eb421894c79db274c5c44a324a92b4be2716db5ac5843bdd56bd67f7ce42251fd4fe17de8be358b33ed178ea966d4e7692242c9a1ed70ed91d479b37e52c89246f2b0f4d197ab4e732eadee41737d8fb878d928f760aea894cd029579968f4ac768ee83e5012ca0033505ac16a60a5714cef687a7e025d480898b1e1c31a5488ed9891390231518f21876fb12686de5470e84dbe74f0e8f5d317b9ea0b24aa3a0d3f7a6785b932c8d99ec18837b9fd83695aa5a723287da794be26a612218cead8cb18248be30c57c4279b5d5a655295d67a164044b708209116c790490ea49faf6164988bfbb30044156cb95d21817d66e00b33b781e31af68cca2835c42ed177ea5c675825ad152463455907e802869e9d34c245c15f66a99845361e977e443e33816aa8f8b4b4720e5d7ec172a093f8c804cadc2b42ce752daea37303783b62d0587eae3736dd70d58dce71b1fbbc138732d673af44d6d93f63e65e7616f3a830e95e0d10d851823dd5c41ba50631f1c8394612a79144039d24f74cf0e671d2f64394ba4eef4a4791d5b13035896d7775d781a4a84a7cc18194da6d7589a6894af66ea2af05f949e4554caff1a0c5b5a2923d938f04a8782abbb96cfe36d6c95297141ffa5ad7e9acbdd174163a38a2d82ecf4e3447b45f0d868ae2e1345b13753f9c94eb61ce6e309db6618e9b964b1438924055101a938f4648457d63b309460d3bc4093e266ed027e73327dafab9677f5cd4914f79c6cc2dbc553ab4de54519f80a8564f49eb43b09738a1648ef79f95028cfc6a039f78d4d54decac0de0ff9da3ec87a056dee933a537c877a0fb4f52ec4eb5b8b500f8b89351e6e69fcfec2fde50d475840aaecd853a4da80c72fc51b967310764d88fb87be41a8e72c8ea81da8da1c4524e6c42c910ab5df198e972f5e15217932cee99a3650ae67e62d4462f895db88dd7597729b4f954b6ae3956dd39351d237f618249d444c4c8e00332ecec952f20e5bef62aee361a63aa9fe63290a141cac9c980fc5ec9129d88f77f3674f13db600376d4de134954538e7cd21d470205f9fad03557f9c4d20323969f57e06a72fdac02fcc57117be834f8f93395be4af33be55f3da483072dec18b28ebe2a69e22e22b1eef9d2d85920c91e566fb3c5bd8dfc4dcaacf36682cd2b1fb30d4031ad66870ee9cd8b8cc143e933ec42da88d580c467a3b5e5a2424dc9f83ac532e1d9d0d1844ca32acb4783e8f2f5aa6701bc178baffbfa738ac1bcf28c12205611d0aabaf5074cf79b4b16e85c43639790b71a19091ad1d600a3bc29ab36800b8a975acc67f8c48f59e99f0c33b9904709fe67e3bc71f5977849c7bb2ac1a629cc2a87368409b01e34d1615f7fa51d4636af2ed5f44aba987d942af2630531698999f8ac1be6c16db16d83e90a249c58539f9eb743fbced52def0a67b2bcecb328fb69d49d380569255aa779e49260e390f1d6821caea6a9b8ae4979f942d5944331b73ba07b15a23e294991df16c63893821dd357bdbface4c0687a1ce3cb2e297a1083605ab3647b7b9d6c1330cf05f322a954b0994d398caac5ba86335d105cd31d1e39a154f8af65eef45c5e9405bea18837484307061cd6c509a38225f3142c3c112ebe91bc8227aeb2d7d53221f4a670a422521c421732ce986453a6f5bb673e2566d192e8f031ba8169432d66095e7abaa78e921a921f2071bee99e2670305c50a94dded7c87d88df38a170c967f01cd5231a8e97b6b0f5d52be739498499387ee7282bf92e3239282f1b6f81eacfebb84527464e413777fd27f7a191ff912a74b19c18a6199bc6fc7f0303de62d1e3cca342a87a06236484b48bcfb91c27314abe76ed64d66fcd5918d70be9141751b5342becb1683089e1d7a9a136c8963dd468f9c0578369a79b3452f7c63acb977cd43a9a85e3e65de718c693852732bd622c4cafe69529d2c9cd94a8c27eaa69f5c35ec61c8467631306b1a2830f108d742e7cfb045270dc0822576f2c835e322926a081ff6c8b1e04b193327e8c8fc14fb20c41cb58a353ff362e01b85ca8c474853cc40e5052c7ef2efc07375aaaebf9a417178314a46b4f51e0a6d1135b926c4b4e52e9fca0075f17faa5646e17c6b311fec6aa4cf65a047dc90f772426f9c04a055dcccb4f63c0084ba3506e3d74efaca8f8e99636a5f70a507d7cff2f6e22346fe254318923a22397c81534f5bcb34a58766adee0cdbf0a6938cb8980929423d00d653278df818dbd09a84f1ceb8166066a8ae4662dafd62c83129b3b7ab6128b1ad7e846fc29ef30ac4081f73d1f9e55b667bb96e2c6877b789882abb045619a7b95006dbe86f5632c014265131634124d564c511a477db18b547d1bc388559849263d4ec6b5d245b49f3be0355e8d42a5edd19027b3dc6b9a4143825c5d955739b6f42812d4bbce34b8cd51f85eb5c7b2fb5abf25b50a5dc2e17afc3b5dae0b9d358f54dfb7679c74ed88b40969da8d01564b5d1f4a33316d42411790ec910132280ad00455969ea55db4862ad9cd4cd633dcf7f204c582446ba7ea800b10e8bc5e7920a10387d3530ecb0c2e5084d87676986f7003f4deff063104f37364a8b6ef807f35427f14bfdbfc63f1cf9b01fc06de8e686637da3e415821cde3aace8b6ad95988d549f2f127aa643218ce054f3b63f0a8bac934e14808ba4a736182306fd21de959b1a127feeb54705180903406ae351ec6290f8fa2235b257eed0e0b8acc59af7208f8529f0a6b40ef374b87909b8d96999a2e637bfcd09ef517fc7672bd2a0fc6a377d8740620c0b61dfe03bcd98ade9ca451e977dd21b6e88cb66290affa9cdc5c6ccd3611520f01814049fa6c4a32889194e20f815811af59c8c9fb3f8f1acd0f184eab1137fca454b9ecd42bea3ce3f1e581639a1e666a75a10dff1f6867561eefbcc4221f4b6eadefc77d434f20af168b129b4c276b8138f06e83946daeaaeee4fc0c110744ac45abdec0b23c3f5699b43a4eb0699c4edc8929b5f8ee2ab868c8f83be3fa6f2cfce480bbcdb9c3d9d3224c24522b5c7c03e3f49a39f11e2d79b009904c48b0e952478acebac81b7a65da54dbb4da3a6f3018eb5258cdd4cd92780eff398119d3ee3ee15bf76ce6c4b0e8e127c9b96fae4c861825ec1ec6f4284d469f474e3c511b9b9701d1010b085e38a66b7a58cc7123f335a29c02423e354840b74911c69b46be4e2feee0f4a6d775771525dfb45be5454f93a8a42c72b51d72bcc557a384815ec124c4582db4cd0353991929e7eca5cf5895611fb2b72ad84728781d0e1903d694a21ff3cd7eb1ff5c531baf1a6fdd23f5d9ada9060019a5be73cede315ca603d4d89f0b85783b560b55e341c3eae880edac84158e5e8bb1dcb96a7b4f6a68bbb56837b626f6e6e7ee0fa094803b87c81e0c759e04b68d169879a4e88a86eac690a1b0f503d271bdcc0d7097047e5d08ddd5220883bbb0d5feaf06b4ccf0f4751f7b244282e45f4cf969e9a17ae173920fd0bf0c78d189e5b10bce5561e4efbe7431835fad9c78cb278c936db95db5a58710971bf1a5324e054b705340cb69ef44d11184bdbf46971fb96ce0c9ee72a6531d8767ccd0c85557ee1132258595ec6375e6cecb7b99a6096a62a78cbde0e610728145aee7ebbc70480d39e49cc8039b706d0e2f87030f48ffdca0880c95ff7df50bdbd39e410b009f7104a569fd4a3821e5d63afb5d99095032172cd70fb8cf50bd1c1d97f2eb817d81e7ec645291f9b5e3e85af640660bcc30cb0ad12e7f8a08c71ce86a2d9a2895341fa37189df34becee7c4d68da23a6197cee2d9b473e2ac68a99764a25d9484b3d4786b888860f962603a0ce64b281b9c2e720accfce375783bdf0a07176faadeef5e19d9c95c7c2e1f95143205817e686e673580f2e625df342e5894dc3cb6c6b9f3882bfc20ed16d717341dfbfaf696d1f61642560e1b1b6fd40386a6f302344f12eccf53ae47d117bddf95d86b4ab776de74649bd60ebf19babc9f0b2ce721e3ed4c3c316a596b94fe9c4ecbf8e7d141e31f9f7ff420eee8c496a40dfd0e3cd162d08b0142e2c5b9ce4f3f989216dc96101f90730671e4934435def5d4a3678067c9fbae3a151095be8c23c02f7a44e4444c3693dab86ea3006e9a41e17e3a15c7936ea14d04560a8a8af9b376236afdd450762e4b0f6c90c139a99ed7e27ff48f07eb4eb55df15a84bb8519ca612345504097e1f33956326fc8e1f73142b415e0591a5c0bc312fc9eeccd1fe9db37a8af06c73d41bab47d1e7899912f678c25f96acb4cdaec3bbd04059ce39507e367797e94019d10a592a83f31362deb6e78c6d46fc484bfc53e832602e4f3f458b4a34a73e1b886591ba5d68872264bf5cc3e889d56a4705bb83cfcd18690863917407f3ce0393097fae18584e2710f6ede4b6b8fe741de7d0f5c7a22ad479b21ae39bbab3746b7384ab2148e8d0a6d5dc72134a29c8943fe3dc52ecf5776631b35b094e1d0b7f8aaa9aeb81353f87ef83330b2da5cdc888708fc87f65b2d1728e1b920e24cf5696ddcc0460467a9b6aa6417d05be160871205bb247c275687bd0a96ee159db3c099b8942557de4e6a92004bddf15576ba6ff1f4538795fb8f42a89a05c72b225b5765c8aa834b8e7801b84c198989aa03c2593a6a03d3284694986654a894437563e9759d086b1ad100b7a80db6ae1558038fbd761f5310d883bd22874ced744e9109f091e5df0452c7e71dff2dac3d687ded6e2fcdd36c7a55e6db69ab9d65004577ccb32000dbd147059ca149fde2954f73eba045724e6a418a3fd3a1c801eff94e955c42b1df5ea27d0146872259d5eea6374c82cfc5fef55245936f441167bbe6bb86d854f4b3ad24c3eb0ba24c6aa1cd80e1c3441f0211778f0838238d37aceca6c5f3c402a475b6e7fb1a9818ca04725eb40c28a00eaae48bce85cbedcf82d5f5eca960fac4b72901f69ed70d50863a4e5ba320f876c83ba06dc2a0f3b3079b53fa9228b2f366d20d03a59a4ddaea0f64a9938bd856e898ea15be3bace397ca72264beb416228546b35e2c219d8ca9368562cf063c47a2f61342f33b62ca6e30c3bf681a4c2da6445382baabccc0fe7df36818467bf64b45a212c7405383806ff6cb81cb5c7085f2a54f9826acc7ac0b2dae8c190677d98c93eb12aaa8d67f1dd023a9fd9a675a4aff9eb48be021fd373c67e0911371313b49b1e3a5cb6e1519b01290dec7c99d04f764341b4e4e4847b40a158617b66fcb299b845cd18686016f2b70635791ebbec12670b444a07f553d8740d180a2a7881202749032f50d0b19317558979634a9f9e6141fc4d95522f645ebb023c0970e7a2f6c03d36040e49f90487dc52f915bf5360a8417bf482622d8b0ff68c1fde88971763278a81cd1e1f3b9dda5928d94b1835f113865a181e6a69c505db7ce7630f5c675672d8e89e81fcc65adf6bb52256d7087a815398dfef2dab1624a6c607c3ff5f5d47b18e0d9e8d2ebd3cf044a90adff8f74b3f12553f4cb6fccc1486ce461ba2ee7ac3c07d8fb7d585bc197dd8380d58b0b3d105ada7022897f1b48aa315303ed43c5d22a92b4782c623fd60d19bc14a13a691998c44cec19cf965ee20c9a388be2ce016831c8eba29e1e783655382133d995b212607f4090ab27eaa0d253b322bc74330a174e2d1b0eed65b3d477b33663fdef429d0f99f637745928693a6f0e6018eedd2251b057743fc638628f91a077f1e968bdd2ba13e91791d5c4ef2b6ebda5fc9829216a70b4b400f534dee69919cf3275574171dc1bbc9026fb9147e9d726898ba2b365d3538bc8a161c71c0e07c883403bc119a0c005ba21561649da7a8ee37e5aede6b30313f50e6603c1fc5e9dcc32236afa9c4684640d4a61fb3fc184c8ca71ff812ba43cb886bb12dd848cc44c22b1a4e8d23d4180ff265e41cbbc46ecf6a0b71fa7ac7183faef7c50e9462e7b6d1228cea4e21500ab5c03800f8a8572f72fdbaaea25510622457acc88f9141ca4407cc8fde3ae5a04ed602e3b2039bbc954b492dc6c45197aee22b8eaa94a11c1df0b548c70cbd2fc3580fd4730de5a8fc8c168153a8aedd6c05b09a6078aa6ceab4b3c21b48374c56d98f23567c73d5701d1e6f2cabaeff94134e9d69fed60da294758155f3a2de452996ab813b95b12509e4323abd0bd6b4333a09aec4ee2e65686de3172ac30cde5b9ca8546031690c9eeb4a0c74355a0ab44ff665ca9370590553369f1b53f42fcefd21c08c42630fa93d55d42ecd88dbea8b9103b9b9cb8d9fa3db4d4b7f35f5177ae2989c5410cf16e7fb50862a14f8f3901dcba826c825851a7d0953664d6af0b700dfb10a09ba487af17951d7e6cef824833a73f80245763c515aeb9fc029e1bbf73d72997c9af08cfdfe0f1376c133b171a2071c057533d16f7ec9b0155218734f478e4aeff2ad3519ecdb4fe49e8ac50f93a0d8697f996436e3f7989f80ca2ed52979fb48c4f9939b041cb4f3706f933bfe2a4155f6c6c3bcb6a946186b5c2264e78d9f3397264676a7109bc804b2715b31aac63ae5548259832e1f0b22fbe63d4bbdb9c9e25bf4814efd127cd90f58be08e6a3f363a1783607977c0f4661f6f22246e5942a7dc6dafa205e7e5e9b688cfe3a6f2506883ff5b9d029d3b94633db7be750dc6ddee94c20e873c9ab866147f7f58ff860a9b863b279e8f49a5f601ee7723b43c2800fd1e1c27ee31d792adbed3fc01e21908540de72ecae8bdea64c06a37cb73353eb18b1d25cb17f5a4d3ee13f91296b978e0fbe425e5197251087e2a0fcaf439db92ba3322019ac6979a2a5c3e63080737dfe16afdc8c3bb01038d0b03abba149b3120cf69174d187ff347b7277c9e0a241d94c90f58c6048f85be115c65df7409d953319a9d3c0dc566f8c464e4b68db8f0e56c5aee2494782592859c740e1653a073588170ff35b98f7b5cc4d681abc5cc47b5b003fcafa496a4dc876b06f0cd4215c10beaa2d3d02113a74a6129da964fc577157fd43464e93382da16ac8f8b8f390883488f12e789c410a61b398c0a7c8c318147e403e742dd27629bdc309b7dba4bd44691de4fea84c57d9b07836e68fa77f0c51a8cc03fe1b4984f49eb5a79592b76c426ea14094f4b63f4546562a62dff30830e66229df24313b1a1e68e05603bd605dc79a0bbba16c16a597843e5da5ea43c97e5e74f898876d03b7a2c0faaa12854b6c43d2b9166ab575cd5a30213410ef59e70b5987f0e24baf9f78273f4dd477a8962f2a3df336c89318f7d4549388b018a522b1377f589af5d9a1fc107e6d83ac11c94e6290aacf290214413ac0f61f70ff2c360ce6b1348596e9a6e86e282d8fc5e39ba7d8339e1670575253a4934809cf8c0bd7a92a8732892d93365b01acb95c32156dfd7cdca35f8e25168c9a2ca633b63142f03f1496087db4df656d69c96cb6e1042c53848cd037a0b10f9540cc3ac81368a59af1fa1228ffc9f90b8462cd8ac953a4dc66539ad6e7e19f9ced3ec1e2fbfb8b22489d92b7ec78ff494fd206d2bb01d351a7e925ffe7d4287c0e4a19789a02fad6d6387bd9f9b979d6d08102264a3a51a03ca545fbcf1e7db875dc3f60841a44206ce79a4426615b68433867a12bfdeb987487a52b91be7aea6572aaa2b15419eb724f38797a15a8bd962d4b7ebafe148c6bd38d0366321c8219d0f7c2fe9c51055f22d80c7c2d32ececec255173185f8298f4f198479ce7fd3c8c0ddcaab2f0e142ebc3e0ba9b87141b072c1a7949e89040b5054874f66dde6f6a6cfcd9b05f203c2d2c3c46ebdf472e957ec21247d32300c5d2d6f157cc21c785c50515271ff104d2ff43334c1dfae38ad81d289d327b478f88da044961df4d4b1ac9c8c173b861e8d2b31db032a92dd9a334ca5e0701d96180ddb92728b07c44f137c282cf1db3b26e0c9ef6807bb360a06da96635914de32fea5e9c6cecc5b1694ab989f90c6b05e5a5da29fd764fb486299b278f4690d983ac071ec86b0ef537a9c46c360c8ff54034f4ca319b91ab50861c0d5be260405d500b879b602fccd441e020f3b037d72e57962e6d983ac5114bc86bf46dd2af2aa185b9761c745b69aa6b72b4ea128c35be6ee9ae16c1598970e2016a48cd12d8b3d45320071e0842a2ac0d4d52e2215b44ab4c267fd19dd855d1073c6c8dad8e911b752fd6e222d20a49179ca9fbc04e2784f25303c97406749dd09b5b2174ffeec291691f6862926cce54563ec8ea7127b15a202d3b133b4e76facecbecb031f9f35756b4cd2d447a2de59bf047085beff096799b6d71dd6f2e2f98da27fb0fa61da4bbd121b7630c39895c348848ab9a81074aafdb8dfc84ba74c7525e7d2dc47585f311eae5d2bd2a7ccc8231cf5195ab27dad4b37faefcb40d5447ef8b7d3a70524e3da01107aa1cdc6b57fd329655356aefc557594f30dce9a1168e8bf7854bcdf2872f8c43cc8fa26202384c84a4ed253a99dfdac9bd92a6d4b9c42761888358ee8878fcaa26e82c3350455393412592e4b394f4539e5e1f521e6bd9b8285d3b673acf883127ef540545184fa2af30d354ba76194986698ed62934bdfa1a5ff6229f7ed1b9b750190cf0bdd3a3e2ea16746db7610c46013dbcf321ba3962d5a7d23d41bbe6051ad6c7a9cf1093562ec10cfa3e7d1c3719c458b00bf846c54d80d77ec9096c56420d27e601308cd15da811a02576c33bc6dc91c51da6617b1b47711d39cae72621bfc721990322b4dd61e0bf3c430c7bc3c00b825711a9cbd6ee8849ac8d8e78f252db57f0044b44989ed04b842ae8cdee1a93fa0c0b4b37d93989cbad73345d550f6fdcf3a27fd07997a254b4f3adc5c987a4a3649fa80bc36ef777bc047dc7cf221b6da5e0e3e1f3899ee7b6ea5c4ffaa53357db1bebe6f71cbe84d0dc3b2e483c5ac89b4396f5794477bd2bb24d3acbb61399c065c3ce1df15bded02550ca8bb30b227e127e5e07c34149e3e53a54aacea316b7a04df69f99686f19e28415c941e2598de3320298db138f678dcb5faa95a090372553352dbb2169fb0640b8164ac1ee187bd79cadfa1bca6f5e09e601983d980dc764803af5f1ff3c670a43ebb14a4806fa0119ccef43526ccf6cc68826e8cad76b87667cf623bb936d3ae8a2491f16505c0b52ffc9b061fff968743ec755828af16cb66fa1336b2ea74be9fec493328347604851c4b152804da104c535c040ed6473f2d3406f162d1ead92a043dcac4229ad685542f0ae78113387e3f3707b1c88e4272accdaaf248410709269f05cc2a5ff079fdcf2f027b8bcaf036afec705a92f133b3c87389d2c84dac107aaa94d5637a0487d4529ff000d77b297dcdaeec7173892efcfff94d7e1207b54e0f7c0d75fa96e8cd8d026eba33522542338ed0d10bafd1e3013f94b5c52f5b3818f0c29ce25b12c69f2f8776fb1aa1eacf16f93404e1062a29231c23522c710eea1b8591b18b887047d833d6ebc437fe94fb276ed95a243bc8a961ee9726dc413835d885623aa2af8010ac9214d0df719cd4a423a8d4ee6f729f2fb4fd6c80f111529310887b672c15883d1fa6cc880b85ca0a3837ff3b8dda208868fb79b235cdc5dcce0818b09cea3193a29f45ca14c936e71c91f353b00d10b24d6f210b9b330cfbc2ce7e0711ff9aae479cd5108fce44402e3f8f9d1fbc225f33356303a5a85698e675ceda3238249542c6857c07fdb06d37766edabec9ff21018e66e7a5dd1a8848845f0b9543ebd1fea10c0ef5ed66c7df6acfeb50cfb02b63cc71dffecb61d79e2dfaaac71b401344a26012991a281fe0394df9f6b8c5a200c5ff5d70027f009b16cdf1f62c6c2e68980354beaf8254d1d1c89f0f271f34dc8929207d3e45818ad95da3e178ce4d865ef99f2f1bc8b406585b52a28903a87ff0cf57bed7a24974267e6a8fb91fea83c64117908a8bbc6d8e264015f668fb917180197cee935a94155d66dc417d975e539a286fbe6116b63e2589c4daec6a1faebff466696011025c07e1ab5f4f430e40ef6316c12bc2c8e4798ec346ba9c62c441bafedc412ea475a37d5a89603c1e5d1dbb0a8d440a5c2d3f0100fa17343d9b2e45fdb3a85c1a49d5f8a72296cf4adf3c498db81f57e5cfc6eca75c856629c6cd01ff6788ceaf73bd7286654c843e12c46afb2e1853b03dce6003338f7061738b543c85cec315f1483ee1c24686454e99c4adf6b724a06426f57ad29ede894c35197f5de80dbd0c2cec4c6a6ceaacb35244212438d6fb59fe4d0a2b1459e4fe2c4d53512b4f0034f23ad9a792f3d5400e06e554c8079efd86584e3020176181a11b2f57c0adfa3d9753d3806f815a196f6bbe4da464861aad361fe9494227379a06fe0659966e967b82c0c7746d6159131c7e88aad232f6d5706125567346be5bf0ad4b189d8f21665ef097a6c6869c47d11b50fdb61027ddba033db5864aeed1cf1018098a57511fa634cc171720a676ebf6becc65db56a8fe61dbb36aeb5bc4d4a3ec7983ca39b788f6fd18f98af52bae92848bbbd177366171a6b463b07e948d0bfd5ee385b6ae369b515b68fbd2dba7d1751d8098566961255e8c11cdbc3eb37d2ba15e10444c6ba476206006d53eead11ec9192474b1c7b123453d6ac4075293d5019f5a99aac0529055837da8476f2aec02ea7018a36ec6f019eb883b80bc961cf1dcd2c68a886a646b6ab68c89999e10b4c1c9760e73daa6fb50bfc9a34cf509cd5c7b316ce3e5dc2df4d246042e88505582251ad76d91679a1095b965e315b370c09a4a8b7102452c4ab0a6b7eca840f176c36e1ef1d43d3e0a837df575adf132088c5a401ed6c5c0ee36598ff136c5721a5d10da0d4eb58e1368207d4f25469816e6a84cd0eef379563a34f9c509e0d6138e08f9db5beba89c2f443ed39d9f135c6b3824048acfeea8150d2cf247cd79766a3cd3efc1720b15636169fedbdef57784db5025fa39aced92629353e29acd6accddc9c0b2e95a5043b1149c3dcc1939b3b48c278c10eb980522012d4cedd067b46a917235da0a6beaad42f70b1c4ac12938faf97e4fe54085c32cab77dab633a3e63d222851c6902596bfa417c3cf77a376300270142ff50600469f5115afca34c903560ab82975317f03647ef8add6af8e16e97bcd94c2c80011c04ba7c356793186b8a52214b866cbbd30728015d00a84ab50b0f0382cfb1b6b68603c845201f0ee29e3a5c97d9fa63a5cdf73487bf04131715be331b165ecb2c9c8f84b99e00bacded7af3ffe19e8ae47cbcde02c190c5d87fc2c0469473d619c0d078ef7bd8afa52e287dfb4391c2adfc4beb8730b078c32feb996579d9da3b3b74eb9c01cf45a4a4f8b07a3e15e57e2fb49b631ba9e2a30c4fff90426ce9f4235932476032d4485398e15a057ec7be7bd142f98e5e27cbfe5f55113dee41caee361ccb35358dd6429f03f1eb949933e4fd6e3e07e17d8ffbb044e6d8954fe78e5565d9638792d5d45bee875faa298ab05a143b2d2eb5c921c12243cb23099926f9517336c32b8f28e316d9b1dbead2a5769a8fe5b97055e65d390d01ab8fd347637b72dae8a5938f19e298b357e101cf5286ebae9aae94506ee25f617462abc48bd6022462713612a5cee81eac876ed507d8945073caed4639960960a821da0ff3fd21a548890d04fc23e1d1655755e98d19e4d873b948eb78fa0c60a8a43f2d30d22314e80b74a8018812d98dde563a698ac8eb0bad9107cdb81fe6411c5c321c973102b7310ae7e2d8f949cd32f7ff3cca436128a33b8a5e632d9b2514d756bdcdc5e13f7d331025ed8ced41221d1287e2ea3316acba12c6f6d51e4c6afb94532d2f5db41395b11868b3ba4db99711069667bf64d7088e8798e9a932be34da284813fd24ab9b7a22a5cb20eb9393208c6af74e61ca21e3b409ef80196d97c7069be91e9760f5308cd8242250f8272b40f8ebfb05fc86af89e5a336aeeb570274007c3b731516b5cfe8843eeae7c9bf8fa72adc7573be13122c73becb3051141bc36c7d05a9b5168e2fa32dfd2ad777532dae8060afd18f2ec9024f3af081dcfcd4aca70a804cb9a445ad696eda11f0e7c203c4e60dcbef86c0a51499978b8f5003044c83519c88123ac38987121a513d4ecbe8daa53f483e67e9b3db5a334833599051471d273d9f177a6cb948fa671f6c3c087603c1027a9c0b9c0521e52789453fcb1386b5b076695a32aa359f4e3895a0d0dca36bfa0d427e290dcd2e07b282541e0d86d0422a836008186457a48f64d4930d5c59a8df4e6998103240b6f330e978ef99474dcaac7cc25a3c5756885cc9a511112d60d2b426c746fd72cdbb0e6798670cafa809f814468ed337f89e65070b8c488b9c4efcc5c85503be0d1b3c10e04d00dd409d6f7fc4afdc74ac6aa82577ff219e5bb6e78747f9a942511c8176bfbd356db949868bc537d9ed9ca252529b653a928b58a963b7229abf0bd9a00069434b3f225127f801fb0d67032a4dcfc4f1d41787b5368458fd811d5b6ddd6cd89969f4bb328efb0d5cd009c264e773cb1865e5a1260cc8098071e5a72d734729b3dd75ccee17bcd31777e08b56022650383f32c53a4443eb7647c5d5bf7b773d05226d834094d8dd36995c9d4e54664b77c63d1c5a3ebad02eb81004fa5bb2b3543dcaee7f39df71ed4cd10422f4c5ebb9e7c7d0823029842cb23cdea899042e782a40ff1d42d77a74c7f87a367968c4c21295d730e83dd89948627c9fb84de8cd31c59cec66b74325166bcbdfa38a4bdb2bf0ccc2aedb8a52d6c1d3b288c19e5dba16cba4dded0162a82ccdd1aff7a89d2124e1edbd97f7babf3ab7822066c4e01d31de5c3fd92f15091feba82891d4b9eb04d512f13e9aca14d567329b99092b78d9bee0be50b7d1758b3ccb56cf04d0b8640686684d61f4573cb572918932ad3f163f1e6319ecddf89d2d2421396dbafa9e8c1ca477d0d6d93bae7f30bb3d0cb2b27e178e7356b34ae4b77c8eed4ce0d10e65903222f3dd6c46e1f3c384da06c5bc5f0df58e4dbd06d62a72e71d4d6c1196af8d73ea1e41aff32c9381922f2359090bd4520d6260ae4a844dd88f2f80b71bfb7a121d8606cd7b206f6864f1dee0ae8483898a98948cfc583dd81bcdbf3d05a24ed03e9af6663eec060fcdc85b8c8750f69c12749ccb0d4db4893164cd97c33603eb833ea93e3ce2444e7509c24eb426c8eba5d0a2f5801ceebd5137910e45007df157ca16639c867bd158b8d3bf17b8c650b78d49a8be33dd43907b66688086f21ad8db56dc2930c4b545e269f1d7820663b208737a13d802b1199787f7ee2e2a495afa7640cf38618d6042d6cddfb60ddf7267d0f22ee0f3e0aaaf53390be91eb5e208932e23be054ea03863795f2ac75fc7b2fef4a5fc8bfe138947aad9eadde5ab889968d9f124eb51106c7e4a5e2ff2f7c03c3a6f901188bbb3c3535d7daff9a5ab65274bfaaa9f9496c740d792edba39f003426819cce51b0bfa6120d9dc79f1361950f962c134da007f5201ec4ad93ab10da6d028cfd840849cc1ae12a5e96635e064031d81e56d230bc907a639ef9ce18fe4ccebfb3d4d4c275e1a1733010f7a8866b10fb4d9e8f7919fec595e3a72f6b982e4437b5ff736e01a78b8a60dabf2965fec380894b973de87f8514df6c6f6bce0846f3255077de50962ca823f2ccac4fcc25d561aa6ae0c4935ee6dd41446e8377b42f4c2dccae5b5744e12c9385b0e6ae54ade10455924f0f21836b2cc3ea5027eee7099e69e5b8cd0b0968eabf2bda62da59a7b82831ba444cbaf5ad39c5b7da65b265d2cf865979b1451d213621ca6124a0e5b1db3fbe380a17a0c02b88806ee10d3dcc75498bf7eb0b8b63362b1dd32402de4294a2353c37fbbb1a68e93e3b6ac2d2f65005cbf263b70a980c53d55d13743a58f3dddfc6a46ac33fe9f275743ede245c7b2bccac467ccc9a77ad9e7549bf983891bc9259a96bd17c19620053c491c62437ebaaf8d72fd25055fdf28c52fdf222762cd56b9a67ca095c350572e18113d420e7bdc3bbd2d3a2de1904a524513e7cd258e4af326af009967acf50a4b64c5c335f4498a7b277566739b77ce77f688b307a2cbb63557cbdccea36ba2b450e8d6cde9c2bc6f53a62bcee94b2fff02d5647cff5e366491afe33ab6c32fe2e5fb22e4586016b72221a9026b7c0e902e043f625ccbeaca7ebb279d8739ce83d7056139aa5d60a63512445db9482acc77ddf9e7792b0d2eadd595210322fe64a148de0f469869b6d1fa1218cb04e54281a419c3963e1e32afd80c8954d023f50137fe496327818078875edd16e4c5015cb37bb6811760a0d6c0e253c28d07600d2cb4d82b7afecab1fe271ad632f5b4e37fce6806205bbb42a860bcb2b154365b0c0a7a9053b7a55a559e204f55c1574eb7168baea359a3b4e1418165861aa610439fc765cc8c960b8eac1a51381e75d10c2a4f11baeacadb5e37802cee4f43c9632495a43ddcd5676969cc4b591ff87e52372e2b76607b0cb366202dac4b60a018de9ee7950d9b6795a626ad2f818cbb753bff77b55369926fe78cf4ca48f6dce9278880260779ef7964a96e8c208ff0028b5dc9c61f59114b47bf68dae81d02dbed29be65b9ca46795b96fc777f8e5be64d5f136661d3a581055ae0a5ae1746cf32313e7d932f3ffb74f3e031455b1d3aff9ba9377ee9b71f17536b9283e4b1258a0d64bc0b181fe24f5ddc38baa8ea13ee82a6021c0ab275666e960c336e6b03a02f4f301862a118bea956ad4e3d006fc0017767a7e50dc74d88334cee3fec50b197eab3ba059cc0485a51f313b33c81eb7ac4b4cd6269631eb0f03bc7ce26850fb4efc370d11f2aa7de45343ec881043b0c37ca02a395bc9d12c8813852dcf79f8ff643c03623234aa79944aa3fbf3f5025caadfd16d2b883b4927c9c2ca59a5c50ed2ab2260a60d407927b545149e51d6ead9539a3a574fcfc478ab4e36da6d20591be55fe9cb59be9f13fe0aa5c2e6bfdebad75d407e42910b41123a96a01a3c7c19e3983409d2a4eab661300f2f554df53ce3524cc469ff9ae99f272c3df0624cc62f4ef8004bfa14e04572b43f7c8eb57aa3f4d8130e7a61eaf0da47a6743de01f49e1042b9cedd21fd3adea3be0bede5c11eb036f8fcb14d7ac34450ee38fd3b22546f26dc7c7b0973efe18f4d35166b7fa2c7d9c0d0fb60ab4acba3bed7292028b890ffa22436ad2bcbe710edb0c9807c5dad1defc616f6d43874962ea55d43f66f40595dd69d822cc9e9dd2bbeaffbdb28ae1ece398134630b2bf03a3de18325a2ed711dd379a715071bd68ff11c57059cee3358f4afdb1ca30289c5cb9a34627e1732a012fbb5374768b914b1c8b6d29a1fa195b9e81783c23d390d92acf3fab4c9f40ebfbefcfb6f88402cbad3f86fc4421ac7bbec277493e791f3666346fff92833feb6a0c4f9099df6959b1e92be017f4b9e23731d2c20fcdf2a33755403870f659c0c5e8efec462760f37f02e0ffd04d71a8e73421edb203c75748f5c2f9ab9a85c87077262f1f1ec8f48ec5167a6037b3e8d40ed2816eda8b3bc35bcbaf581038921440a3d060f047cd7596b56b0ac51af2372cd548ac281a231f0a4535bae4d10896395721e0b9601c5b9229312495d8b4ba0fba7d435bcc60bcad83d154c1b94a140361c7834cd9446adcc36875dd22470ed5594f306be3d1a200824721c343301f47e81f4d6905a612c47720edfd11af360f32fcca9c9d73ebfaab042916ccd7356afb499cc6126710cb6046b778546a911b8b5a65e4323ec526e045bd9f185755351e6129e0894a7d6d991a3d9cf405a2054c3afda50d5b023940915fd5a19ba0b1db02b33b7af85eefa32842603d9438237697580954aa525d874cd0a1135a9c74eea591380916408a3421b614fb1959b44686d280db1ff56bbfd6553e05ddcb9d8193036454befef9e703bf3da59f4a94be86f7947ca816e4bf9c2b6c41ba3911a29ea63808aa69e02a1fd41b7aff8fe988937d1851b07dac10b4d0043117fdd08e8e72587595ca229f63a5223f9af0a20f3e6bc7dcffe0c94167353e15ca73deec327472c7ebad3edaa137dce3419360de68001c283e71ecfe125a8902386663042d2e4e00dba5c8e0f6a340d56ab8dc86e6c3414bad8b9d8405957196b3af3c20cb00eb7f12c0f011166d731b4cd3bb302f7e320acfa58ef9b87b7dc7b170832cab2f7e73e340ba82ff3967bc75cd32939d14f68ce0cd703d92cc97dd922c56e8e67b6bf7acab5a5af406a4d6a286a95558eb15565de4cc6974af342829788c76c8795101b38ff481a3f139f39778395072615c72dd73f0096e6cf428d6401b6966cf48fd6a0f94bdd2833da5b81ca8c9d75c18740702096da8908ae6fab116d7e94b7bea274bfdef1f66e95e9f19c1a65bc307f06b718caf6c042c829448150a721493f8ffca9b8fae8a3df6f6a654b1c1e999649cd2a9a469d1c625c3880c0295929bb08d4d7a935022c0d8800c689252405972c689f539a3339e6cf47f9e272260fba6f6a0367f28932394dbb929aa73afc811a52c4b51c1d5d4b4663b4547496980c5a84a989fd8b18ed8b189c2de22fbb268ce8fa3d9af79a7d893349ec4a5c8bac47b97ffe9355e99fe48749cdc6b9fcf5d89b167787e48f108891e1f85e4da5340ef33a5c00fadeffbdee033755ef515626730b664507b04cfa0ccca623e4fc51c0f3a5dcf8d3f637d024706651cd0afbee87ec6d2c0aa3e97967de73e6bff1d060cd07f231fa95a3467779dda8b3aeff89863a45d79ab0072f18a8dd9220ad274367dab25ecd3da2f1272cf343e604ca8f3182d600e83c3a35ca74b3e96e32a095ffd0950571507dc6e85f330f0eadf4f8de4a26092955b5a3db1eb66eb1ff8333a22e38254de591f7643219c0b647c459f75498de174cf0b3dfb16f3a193eeb87b66c28a28bad3043b751ae37d02ee9aa79282e9a6f664fda1bdd6cf1778211fc7131efdb9ae8e5b3f116d96c2ebe8fa676f398967197a6c40b36425dc11d52265f96d094ae681bf1987063770a2c01df93b8382b634da71adc4a078793fd2db1e0beff8c3ea7466b239d04ac7ff1565a8acd627d0e4e8b8cf28431bea91cfdbba7be3ecf86e9142380b5f7b80dc302ada9a9890b3fa38e3464dbf8a967832d493cd024b2037547878e07e95919192d43520420af48970a907b55b05e51d831287bdb84fef9d331e9a44044896a482cc7794351d37cc1435d375dbc73938081ab134cae80f0290c5409682d0cae2a9659293ae987b6223d7bcae1104279504c650838828733988b2779a9aed5f356ae9dff3605e9d35122811a4a61c6f8cbc31732d6967950b272b2a6efe4042eea3273ad62680d33b81a2343376c1d24f71b00bc44e3b59a020c2eb54e3531b7dfc994796e8d4e3cde97a213170d34b8b6a5a5a59fad6aaa9ec0e7bd3ed54ecbf99038bb92cf46d7c770788d86fd0f91c0a4604267b5eadacb3c1a4ca62fb5093a923e62dd4453f1605c1f67847009331cc3deba36d44dc9b3d4f3e1b4f0b7f6e91e6f70d023c0fcf99c658b73f1684233440589ca8ed7da281b8c738dc2d2793353c97a50ce076bd4eef499ed30200a7259b3fe5650b3b47adc16e8c0049f5f4944e829e3dbb539bf0bd06465a9bff9773a11bc2e4a13c35aef769f9808aad81f9b13bb3dbd9063c3555d7199455e1fd8e24a3db931a4b6d08d6fe7309cc9f8b49da68223d3a569e07a367bd672e3b56997cf4ab95695ff3e28178563eebb37e9e816649412b5d1d8235df10949079328957184e3cb295677e4da328215b7f1e75c0a594b9042a40e00a576a4b267b8a58b0bdfc49be436df3a561be1c20be71ffe99f18c8d45ef6fc01b614cbe44c507c5a321f943e75fa4637e6031ed25bdcbcc15cb45e0054473d3f73b6084d1829b1d5f68b1f0065201862595ab7b057a0ad55b1f79d0393624f41e43494a8a58f42cf8a18a3272d891dba5430a3cc00b88f91a35ffc4dfe03fdea5ed9dcdabd69c42d911b4287e7cb739996854adbdbfb53a28ec9359a05f61b9c25e14e1ba2d57186ad06559570c0eb3ddf3776924cc927c633435b8be533e0d8cf244c506b4ed5b9c49a7b2df1cf042cc49b43760c9040d8794d14fbe63f35086b73ce73364937244bc656df8820bb572c3d411bf32c06ca3a54642eeb6606772efdfee275cd41756bddaccd3db053bd62d3ba6e2e8c01cf794af23fc7a969a07921c47235e16baaeb242f4af053162c049ba202bae527248a403e5886051e843c9ec896f50a9b6b158e34c89a34f5b5c4a40444fada9b3e5aa04047c1e87b1c574297acdbd67d79c3e6236e89d69c6f65a511f0df00afff58f7d5a7aec021f32a62c75b852e7b325486bfbea9db074861452e53de984bed3d2d026770812cd03234486a86139d8a22da744129274d2400aa2b69eea37aabf03d727cf3240903de9f385b7613bd3bdfa0184233128eb604086614fad2e9b59490de6be30246565a408ef706b778e6d8e4112aa6f753974afe3655fcdc6b0f2dd13844e4b3cdddb387a531fbf9491c21cecf83c0862b5f4c3c9c5c04ba98afa8d7cb0fed0da9a1c3b1e9b9d3d5813e1ab7ea2b4fb83d2b3decc6c9f0a53997f3ae9929b9122aeb4772a768ded34fa5e99f3eae80c46609ed330386b522dc7f85a7ace9b6113e5c36d87add7776c2e1e14994a65413c7ead6be3e9d95136fc54778df35a9ca660aecdf23a86acbfdfa5a03715be6688166de34690c595393e9badcbfa4f9cfb003c00f2525bfbaea0ef948807d7894e905c6ccc2c1393986f3c3088a31945947ac39e94ca03fc4a79979cfc9c787e39ac92aa5ea3bc24a3ee95c46ecfdb7aa12627f8068366e994536038d2e7065c0fbd79369c3cb00280970a438235f2a55fe0598ff4c45d206f63083c7ce509fd55ae2d1ce2447750a5ea851b6b066e338381da9abab51aa31d6c7b4e58897c82078b015d26927c0fe15933cd155c2d16b5cf14856a508e9756058b6cb5f784cd5676f66ea2fa5aaaf1fdb16fa18e9c411fedde6c9c8c76c8e24177632aab4d0684487ab84d06d36bae36132b9ec85a30099e5aa4119d1924ce59bc593c7e3417c14198162865e603c0f780be9b740c5d84f5593327704c3c283acc4355d369302685e25a54f41e06e9c37efa075e9fcc283b075318dc1cc6541c956f0a556743b64dbfc8025c366cde9856445d9f2580e11bf8f310215b1dec86f8fcb2085b88730f71982b902a12b04d6916a1b73503c977043610ab49bf2b5aa05d4eadc0fb83f6247819be4a37c125c87e32616fe2155f31f19a3dedcef6c9f1dbbaa0e8d965768f09c470efff509ab39ee8035e2f1710c1313ef3d290666738103cc5182734db225efaf55ff4929c592a81dc72fe400801b3e1baa4efea9b856c5121492bfb0eb8805d4ede8057d273ed5e333ae8487abf6bea3c90fafa372bb3dc333bf11c0596ec2a32f273448f3444983c408b52bb69fe2c52576255f7297c9836d0c7c57d2012d7f4d2839d8c47a1557b53072c79b547b076dcfed01c67b4dec80fc170131bd877513fc79ca1890980c2f3829acb5ed63fe22047bd87fe19bfbc45367cc45916ff69293c5bfe8097bf2cc6a6b672f56cce30f5ddd2e490c06500fb41ce6aec1845835c4049ab8df7c9a5d71e59d4eb80a8c41000b4dcff90d1dd524189d34e1fe347a91a374f40eb00df501c978a1abd764cdf17fd61a4c5da0a8359d068ebe0dd9b3470ed7d43ca60c9ffd64e95b3821017172de580ad6d8943b2f09b9f4a03204b3cce5511cafcbeeec304c48030601e1f166f6e5200eebb6886339e9b9da5c41c0426334f837f58447c644c131fa08cf816af26f5a37e1d5d108e37bbeed63115a54b6addab65b0508e370c54d0bbcd098ae477ad7eaa6c1a91377bfd261c03f12775dac6f054bebe7fc1c2fcd5dfecba42a8888febd011b9c8a3437443efc322abc0c51280046db864cca6a8ea717084b3b8ac547aa405641d0f57348a3b7add53acc9b0d0c568a21625535259850c082778653efdaf0773edf89b754ae100f6709e67bb61f63d13401ec3f23868259dbf6df228e3cb2425cda35dd5b73532a5855e1d53ef8fc71f4c0b5f53d91722fb09c0712fbea88db54596a0ecbc81e274e9980d946198ed5630c518658ec58eeb1ac29083c14bbdc4e81d87ac3581aae1495b386808afb306da2a98ae9f590f2e8362cc5af38aef4f30c6c532dee6817ba19c43388d8797a4c09a77a381ed21f8dfb94113ca91e7b0b0e5270867d3298f27b4d3bc61633f73c07c90f6a650b1ba717f0ef2ed5f0660971124fb8b6367a252de7372599f78bfe9b7573928857717ee92411de34ab1724ea58008bf210e4d47ba0e728928864e62394a5a38423030b5765bb37177081062400804f58eda38f4cdf6af80febed7cf7469d4dc729bb9add05e22bc63ed5572454ef3659e1c52abff7ee368b97ca303dcc7a1a8b44f7c4eea7020165d5968115c834c9f3b32733f1b2399a6015aa9c86a254349d95c2dc7330ae8d80904a5eaef598b1ac1d1148d5240311d15c1c7c61c9c6e8d69bec8cdbcb699a3de593575aefacda68024cddbb85dcd5145caee66725c372d1f9ed750bb66e5b0a9c278c10b096c583eb1a7b5c6046e46786a5885f379c00369664c320c4f5d2d083801a6bfb7b95db216a0a8d629c30ca7f12788a6c7cc9ae9a4de39ab5914c4f02b1cae43ed680c0a96c9e5ab2471be60d90a2299c4bde0f6bc10a13b266ae928bf21b158783ab7666dedd8e638ccbe44e3b4a15e98d3a11c6a49f6829a26c6cc4704e78054f15f5a02aaa291ae4ce889875130891fc0e59712ab97ea024707ca27daa382feb92570dc5cd6997a3d8e78b344ca3308dfa2dce992b102c31e4168f35ba1ead87764a46f61fc4c776709f0d7d40e7afea45011e3e97ffb95ad06bce790e286a0aad5e6b66e20df2cbbdce147983cffe817c39073b18c2c5f4d5032b123cbeaf0dda79c9e1d23ea293b5dd2e6bfd166356261682576c98f8a194f0b8219f9edd8d46d1d3fe29ce9eba82e5d279950d934d339dcf991ac41fdb154d878e5cc2a7fe3fa36e701e0bd57b6d4ad59e1998688deade29b68ee06963b090257125180d628bea9f64f0e29c2bd0e064ac4271c9eec70a462071ec23dc0c34687fc5f92629af2aa88bb46578842b23e2fa035523c0e5d0324d3070188507b3174567b480c140660eef7a55038bc3e3ce9e889611fe5300fd72dd470e38871a82e71c64c06deed6af9c93487496e73808fc42885a553a290063ed7615a5c69636483f98ec15abb5716c4e54f654ec69b4f3baf7f8bc551801342464be346f76da668f1c1f1cea72c377b73603f3bb9b29b147816b923128f47196a4841493aa6ff92b4dba175fae38d7e4756fe05b1ffa8bbf4911dc5a99eb7ec719674501741db2863995852a0e6169c9132341020503ec5d53e9ceea844f621bc17bbeae2463b898df94c730998f33b5ed766808c86d2f0b7ef08b4324c960147d0206c3f113fb0d9beaa95e3cc94dcc98e284f3818fbbe0b78344c9d357dbbfd6da0e4d7d1f935b5efe65d964cb5f96683c0aef78d2af7d632092a9fb105b3c1bcd3898a0a99e10e582496f95324e88a45638f1005bfb6c07bb76c5626f23c632d34768714db0ab807a8249a1360707d4afb41d4b160e39134f278d8302f393859ebdfe299d5431bd05dac063100327668817b0257b01af5413bcd1f1d6632a27833291c1b0c056bca6c090944f019ea5de53aebcf4579ca0d40631e6f5feda0e9476c256bd65548d0db1b66597a75ea498653b388fed2d4034630735e359281aee564893317ab26014692b972216c3e2f3b9e15049c36df16a796c15456d9d5ed5bd9676e42765f419fa647024cea6e78eb127957b2d6ba3a5edaf16f57e621702ce522eab31efdb608ff2a73be46346fb74c3aace6b19ee8a3eab10297ba1a263cc7c1cd555fc24bb52419a0e52c94c5b03a95c07d94839e9a126614af69e2a09315400d9742d830de42145bf195850f23ffb1653a4d105d0b6359dfb7796a95af783af6b136da58c71fc5e8beda1f436c3ff6018c2f8b2a32e765525a19085e6150aca842f4acf9d381e4809328313f6566e20c0d3d0f8d9af67c90b215517a3fe4bb972d31aa10ab720fcfa6e297425a9b70955190f4140c36706f02ba8b0ba601e509d65930da6b129e777dbae171936f21be05e3f4d186191cfa3303575847292c9ca4ca925cf2ec988b138982536d84a03c5c4916d84ff99cf02bd1b3494cae66bddd43bcdd2b03b514d7326c5601ac31d8cc7ccfc30d0c8842ecff42ee04690e31c89192677bfa52907220f0485e46e5f022502a2ef5d7399886925ff928df899023a34f492e24da6e67169ade50dde8f6db26e23a05ed2bcdde59b73fa8034cc5ee879197004f6c9378752330bc4027f0d5899d0e54faeb6f3dc1273a0bf0b0907e12d8a8b194dbd8e42db53b82a5d1f64d336b516b90be40b2aaef8a63390704e4148497a895ff8971ef6d5efc8859b82e2c9cb2b42230b74685ba6fccc2c709a37431c4a8d403e7446089dd035bd9f6aed4ed61f619d7800007a4517b2f98dbb88e725096240da7906494618a30e80540f2b793df0952f8b334173960565689f2a588a44a5aa0720cc1d176c2005b51cc1c7c01a55b1db2a6a15655565915779355b2ea78b4886340a2510cd82e84d54525c569adff33f401118f77e441a7dc4b420e92a2ab988c9fd245f7386218a4844fbe0c4ef88e3a15e390978d4508457f83ddcf1b1c847172a9cb6f6deffd6475794b2e97cf7fb268f8af4db113c310db7e261820fc3e8b91fcbb918053f2ce9789b49a304a92bf9ff4d458fcce2ff23c3056a7f60fc0c6ef9e10dbf8831f30cdbe271b46e919772b0b7e1969944172e91783fd264ec303f53516b4601571eb76bb6f04840dd025cdbf4818d0b1706b1249625cbde52b97a86ff0e25e7ab7f4e4653ba923cdefc7e844a17e08ee318ead52d44c9f18f2caabe3ed125fdd6d5714f83ee407cf0c90a7615f9a988455dd9afc2c17f05b1d590429c853ca5645b0bdd84691e9bc7a68ea096c5df6b69a311b13fe4c857027f5b6ea339bcf816eb0b3170e12941e7b670c1206dd5715bfb696ba3a3f6b6549c231051441bcb1f89e91a74bcb31b08f5e75a8dfd513e1526ab305fe8879b69fd986f63981e44984d3b9aef2a7a8853f4efc7e1924922aa6a1bbc71c80f7f8c6c6762bddfc1bfaf33da4891391068d55ee19ce134acc8c348454ddefc750c88e0a4bf3d935bee1bedd7c5824fcfd411f29eb9b28ee5da80f8d846571c1d9e4a49a8024581447db03491232b95c72a47a9e03f1a55a25c0ea0c0aa5dc14a4b6a65e42bcb0677b72842a1456ddab14c4921f10e5c9fd731994a5018e7d2b63a6dbb3b302495c21473cbfa1f84fe50f73023249ad15e8ce01dbd0dc2542b660fef6ea0afea00949de5f592881e08cc5f1b493b51608523f9c1b58a843ed288145a4c67b3e02e524242cdd915607249aa544b03c16331433d8ebdda2607f3858464d02f24c134b72201645c392a1b8d12cc82b8958ab2eed33af52237587604d347dc99b4613fe719f981f6b3767547c61f65cbbfb0340862c297fcf1b9361d0a0803c446fa8a070d1130c20be4c88721d4acdebdc63127d7d807f2275e2bd9d3266f07dc4fb39df22f142cf1d82eb61d156bc7d23c50cd79187434385d93c1351506d9ddb52b7fbce830b7b9f2a96e940cf9c41f41abed0c8880e2b06666894351cb08394786809efec44491c3aa5c6f5d7946776ec5a1e7047f81be8b510ff14bfa9386b0c811908a0dfe17e1dba103c1ffe4194ef35bc95606b67fa27c82f535ebe55723f9a635903611da2f57a86b9dce9d721514a4f13b9a98476556896f2134740409d302e6d0d6403e7340deaf14a984753a3b61575304377677089dab1841034593e2247aeaf16191c61a26d4a6501f9acde3d679e62a38527f80b9089e9da1de028350c0454b9ff8f94e92f5eb54fa0d77cc4c54ddc843f6ef292fb7bf2d94598b39003b7b12163691a97c8b5c261100278d57de2c874d4a86c04e7e8f437ca8176400420c439389e2be6e4334848ddbae41ed705ca59ba96060c86d72b90e99ab3b5eee3bb7e4b246312f4c8aacdce6beff2d4195ce9a364ac9b6ae86c741d902944f5bd59821f35f9dde33f4de822e141c6868fe430c7bf736189e198e90465f1a3741a406b1bbf360bed35454aa08638b5e886490e749bc4584e063be61e605f6f5c046f71095edbe76f8fa5d47844dafd535abcb7faa2933ac1ad3ed954c3dea927274c63e57461bde0fb0a1efc2539807ff73231e2917906bac85e22551b673565e74e23fb42255dc45a57f11934bd441028e6d96eecdf7f254af796bd2f795ad0d3a625550e759fbc1391cdb42ddef3b92b0dde78d250ad59a32cfd15de8845f9f0f8f47abd79d9a4b3ab64c407fc5cef45f9c592e6345a7ac6f1939a6958857da19eae7f7ab86abe60971d485f59f02221a6e37f83ef95ed60518bd6091190d02ca7836039a733edf754920947b6469729cee6fab95633e03f83870ec2d33d94263b39b33b6f1904e5f4122f1bad97bf41944dccd0f949da10c3227bcb14488138e7aba4eb8185210857209e44be423072deeb304186b363f09166531c53d2df10c24547a815e31b3a3af22d16eb491f778f689069e091b8780856a7d5a4de8f77578a315df926fe41aaf3a92ddbd3744de7c0837032c48d4223660aa220d5a1cbc08f6cff6e43944d2341a402cba86b2175c22b124975e50980fd0d4f63037f6b597db249e6b1073fd22f188dedb8c1402f91d751138ff3e8d5fa8b2b5018a2c90b12fd5182ef8b6131c4dc2d7e9478f27727fb49ff40125b8f2cc1f4b1a7df2f4a2064d24dfe1a57e13be7427bf3255610cd75943b7cc6f950d9375c91aa3e73640d4c3fe3e3415106815d11e52fd4ac3ca528059a7ad425e4388270edfc601a1252e4041727fde6a1ba78ef67c572ada720ddb0ed35640ef1f7c47201dd7bb8b6fcf585e357a5195abd37d88bd9d6c6e4887b7a43ec35c82fb511e36a72e07e1c2e02b66f83eb5ade93e1d2deaf4b71cb7791a041a2e6663c8991cd514314471f9db0688ac3e5a5a3fc57d81233b3f1475e2805509e444a06797fa22cbc799708138eb05dad2bfd00ea3920b3723630a800ec29e8edb311e704f322d8ac88642bec1ae76ee102e0cd03d6ffe71f8684ddf4a01a91dba2e7c9997a2c83c89690a49c2938397e9b888423edc4bb0fab99a626ebbf987e0e17701d866d5d29306c03268dcad1a95c0592d8443676bd66cb16566454912b171dcc69a14c0fcaffb803a617e00d0d417feee409a4efc7239f7a9702f6f6fea4a8a0ca7e8cc00b48f170c9e8869456433f50bf02b22f6f763a2bd2ce8d26571c3a0d80d176da31ebd5c0500cb61eb5da444c139245471a2df97c41eb87fc582255763ff897c1e30385f19168efe95f877f7f3e894118d5f08497f4e9150768717643b177557aae0d9bbdf5fcc00a0e6ffd6bd3caf9a8cb7510b04f73f534b2bd3f41f144e98a839d20477b70bfdd7e09a10d19bd88b4d03b98d08a3cf54ea7a98b1211e08b7434aa46f034f0d34ef1dc2f4a8a8c7064d9de4a11304eb25778ca5bff953ed7ccfa643337c55df70fac4acc48563e328382f77aeba09a47229dbcd29cf685ac57f2a16110e8cdb421786b2c6dde92c402878bbc178fe4e90d5048ac3d219626819bd05469eb560d4122fc1f2b39b814654b0e103b84f5a9d91296e5d6f563633e5984a3332f2f97e7fb593727b2ccfa6f54f13c815b24d7f026738708c8232eabf68c9a96aa1bf28b588f9d2147314c99ccc4bd24c32bbc6511a6c446e343d4b11c9ffaab662eebb9137077f82cacf5c202e8dfcec050b00d2153deb30137afa9db8c927a00d5e96a08c659a7465b4732e7c9f8ac29d9b6d6e2708017d40e2d20290eb8b7293780c0998da889b176dc2892b9d2112bd2267caa4c888480e661ef5e19e13629e538e42b42f14d3be3c828bc46b37cc42615350e1f67aa1e1d6c5cb4cbdfd457d6fb58a73a92f8e694f6b845329de949067bd46bb092833b603537cde1821aa2b7a47bf915410c6ac1dc71adeeaf60e09a52185e0db60e56865adc2cc09cc80b8aa4499437426fb0e2fa359bb0c6d4071024924c4b940465394489ba0bb3fd2891c8c5ed5363ec5889c1bc5a0fb9830be9bca8db5ccf085171baa943d2d8aba391807cfc0c0b3f34ffd2015735df3492a70bb077f604a875be7ca5f74275a3b95f3ebabe9eef71cbb9c5bda4a0bbe412c259f9ce82f3912364a93be9f6f88dbcbdc8ffb59b46f8d79cd2c87973eca657ea5d0b542799e21f43fda893c9c3d1918a9b2166b97f51c70c192bad54c6c901a4e0e6c0d87ef9229436ad5041bd7e3340af48daacdc373ed162a941e03a91032bed598d7fa15258722403e18761112ec241bc1e17785292c791d0d6a7ec3025aac8e87a2cab10e2352d06e9f24410257c7af5c3818d234d6521dd31c3920783c129c10b5d6e6c559e550f711c50464cd003ff5e6b1474b00a665fbef00d800d5d6a810550d5093acd128b658b36a2eed5f38b096400ce2b2574280a46d16605dbad5af669b7468c1071e814ece991e8679e82e039b8aee621d9de487080f48482353aaaa946f43d7d5949b4d80b072c832af1853596fb3c309095cd84e548f4495d33b72c0790b159004bdb31a02f40f504839d6683ea3ad4dce29561a09397edfa58f2c382fd4058c65407078254bc5a8a97510000012549f53fbb9d31b2ff551992f8972af6b9a1169b180f5d3dd06d820bcec52a04f1b3a6ed040a439d944e4abba7dd18e1daaf0b6bd1b02df0bba446a0c58699b4ca255d3555c14f99128c0893a19aa488d9f7e4625a9b89a05520aeacebab14f98f6e39a139c90dc10803e484d079f92fde6497f2c0ee322986ea186d7ad387ab52bf1eb15c423b2a598e7264dcaf25df86a202df74f4b778cc8434d9af7db993d91ce2a51229b197bfe53e8eb7ef4f5862379589c86721f0b9c6535f4ac99b3b20a20537d1a5dd6543e42fb2126f070d766f696d754ef1d0730c744611404831d592a96ab2ed0721c61e22c0e5fadf96ba16259a1861f47d33b549c14db3f8fadb3d9d52f4b2fffe7ccaad0ceb1bccaa9c0326f4b003a880c6f1eded4b288f79c8afb92479251a486cf352c9234be73a7c71c5a822ce36fb0fe30717dcd47eb66635f0ff48839e95f10286585036a27e2f2bbc94b5a44d6d83088b8cd52b282235aeb6b2093904ae3c9656db7a1483e57d85f15197604b78abfe645390f9f917e8aebb028c92a4b1cfa4aa936b0aec8ca3293f4156088d9da68db4bc6a90e915e4730c4942fbc13c03f7475b339f99add2aff32b6798d8c7a40764ae43051475c4a316b1c2ce061e83ec257da540cb99ae091e08a25b28cf6cf74008017c9a99d7e12f0c46b9dd42cf92c1b3d8d74751cfbceb01c3d3580cd71eeb190d840b80a0308df62ba84585ba0b6eeca66db06443eda32beb317b8052c5348fd384eb465524d4fd754a0319ed43dbb430dfd2dd0a38091d1e5fce096056ce91473a2ed361e5af944821527062f94ce558a2ee26995d2c5ac394a09e165057205b41b3f6e30214e270207792e9113c2f459a1d7758bcf99027859b8c9e812ec48ab417902d1517e6daded608a6415cb5e3fb521e249b0b9cfa133d49b4999be051d86ef345308f01195771b9910140b76042a227ce438bc0d09e7e762c052ccfb0d7b38b6726daa8e237e53ff4d514a8b033c02c99ed2acca1d5ec5c6a1d5ddead90060d819a7bed90c45c98f1e431bd0bf5efe1c077828f4923b242e8a6ed10d17005c0b5e27a95a045063afadc721e6a2fced5eef358b009a655b5b49f702da961d666dabaaaaa3609fa10572b99670d44ac1cc400a5f604b83012ef5dfb4646c5b0fcfcc644c797f3af21a3822d17f7906d2356f44d06c307dd574ffe0869bd82fa18d8e582e8ad9f66024fb19bdc70c518b2aed6f7eeb2401d919d22ed79a8224a0f6153203b860f1ec11ec8827482468f3a482c641aa04302f8380070b8a4fbdffd3e9bec28d5ec073fa79073e77da31cec949031e943ef4e4691be6d72d16e68dc495bdae970598f84d753978c741ad0a8782004bdb2785c86ad2593b35534194511d633fed04f5a136cf0f826be2cd2e5cbb46046e953011ea69c91b4d9b77b225955df429493e6c57b416ad1754328b4dbecc4ab48ac3cbb2a7864c40a8576a4975cddc3fe31a45eead43f32fa6398abe808c686146da840fa98813e19c467df76ab6a0c7492af82f7f15c9d6ff49e35a69440c3f5d9f3cd6d5a0fe23cc662405410707700178d7eafef49603b90a8c2bfe1e22376b7e886ead079872ba2e291c40a2aeed14c88dd78db4e718aec01c84f529c8e8903bdc9715801c3a3e083a40e3e3296b8ac988e886fcc1fcbcc2f016673bb4a003eb31b13731c7ce73897cfaab3542bacebed0b6b594d313007a1538ae7c40915414b3175f7c6e23a5d9f65c9edda2446db85bc80d0090c3199ecdf32214debe3a9851d528f187566d70be9ccc9365146da6d917d7cd72c09cc0d404ced5eabead0fbe1f685bcf838f5c245ca3237460cd591b2e7abbf2b386950aeb8cd38d28f52ce4fc3e6246501c54c940fa430df1fd63ae99bdab5fffffeded4ca236b0b9927ac67b0eb92bc1622b1f3977bc305aa269f5848882807e7342552cc2aa1c1b64d742dc5bb4ebb51d15ad7971c2a2dfafe25fff25e66983a30789279c29ebbf4ff3332a079a8158fad91bef5e1383b01c7406d71a911828808b77b84093dbe487f83edf3a19cb630e45dabcbe07915d6e6fb18f0191cf1244cd58b0fc86f260a6b835d03ba35129a41e6d6e87a0eac3327a9b76ee004a7a594817734aa9d0577a9fc4a2b4e0528b68d9b114ec14027f6a1451140115348662b5a0a804ad5892ee064fd57404e4372f8c2f5b5a6eecfa0427dd38d5b86d005be6106730ca962b47cc9208966badf3d96319307023966d9d66d04d10ff8e5e722a60401c7bb62f04ce31d5879b55a8dec4a83e26020ddeaae8bd75a7666ddb32d4be5133f48d5afa7d42a6c52c7425c8e946419aaf9dc41ce49d1ce7524bad3936b3107722dde212554e9037b8727a7c870953390e401f1ab6758a389275df6e081ed2cb049c25ae7457d6f0a04c6ee5ed2a5ebd3ad862f83060ea6086e62917b4bbf47872e25d27205a596d4b54eb757fb5fae56c7efd633e88775bb77e159b957f6127d7720e516e28271e85b4b0cd9987820fd3f55e4b2795205c3ca62ca03b5fab7994d9854e68f758d5e585cf814ceee9761e51dfb0a8b721ccfbee1a30c6ee159c2e157c33f8f938d77689e0d4600e59e56f958e1ecd6f8d7a4089f6812c96a8be3e74f39e6cc1ba60ad20ae1bb75437439256700d0bb9b26f5bfdddf347b7ad6eb5cc8a3a9fc732144608d8cf719c68b14954ee7a2c8b54b83d96193d9412e38962176509f701bdcb3c9dc2bd8f82e099264e9c70bff795f9b4fac55209413a004342c76ea6e4fcaab595b61d742374abec718f638eda8cdd8727eb5675ea1732a924fac1448fc6eeef666d155ec805c9a994a6f410194d53c1d238de1f9060a070f4c6ef6affc64ade188a5f7393b817e3aea5115b8c60a95c1597737d29532b0a4079d2f1b97d9d7c2e60a11cc64b14ce8c85cfe893fd5319184523f6b87aa2c0db9fc2f239f0eb9e62fb122fb951573fa2477b0b6bcf0ec31e8c1bfb8a5a81eac95b4cf329fd8e6bc02506697068dbeabba2cea5243eb7fbe60ab44fa67553e1382d077fdf42380f76f1f6b5d122bfd5b342aa539b1aa2a0bedbc038d6a4ca4ab4fc2da82d96abbe3d15cc2b1d7b6707363c2d6c2dce4a968d6e3d065c41fe44d19dd3ca1e341c5d5a7c7bb75ac281152e66bc0617fa67d5f08d22225a1d2f6d2ffd84c78b2cceec1862632070d309ff4423fd08ceacf7f1d618227f7b25ef5e7de518c1e13452c5d79c267bd59b18639097b11915091ca8f60ace8b1832fa3ae9148486b2379e35e5be9f2742f4191ef42032d333df6ee62a15080e368a7c8f405a021d7a94b0abed46abafc67ab681853dbf93fb4597b63f4325dc2e2995d0caca125b5183095039b1c0276d76de90869af9d6e4ee9d1f8cb5897317ace838ec879764679b2350c885787f9ab3b4551d74c3d3697294fb4e28d544bc6835193ccb6836ac9ee68d3e679ad13ed7af596f1253d79e073c02eade23c231ee96b2820cdd2ed2c15d6211062bae9f8c67dca8fcf1128bb702aae3428ffb29fa47110ba9757b9b1eefc3812f8221a056cfe62f885a03d46a1df4d6d76e522a14d351577ce5c644c5334d6a7a4e7a999f19689bea42fb78fdae6a2f7a979e055f0c7766295fcea20aff82634d40091b489985e1430e76955c1b11683df39356d8f534108ed288b21878a88d277dbb7c4a088c22c4ee1d78d542372b3fb5c4fb49dc5937941440a8ceedc2cca84e577f355f34c8bc8ca664360e6528125a7ee51834921e2db49e5d30ed4fede732af143fa0d5b8c8c139a760a14858a474e27061e93776d6c862d16a6d35f0ac2ffc6e7962960022daf32ee4a2a7bac90eeecf43ff9d3c188e0134a879d981f71c115c963f5e180606556166b525e90f58e57e62a60918527b164ff168017c1869eb3b15091cad081b220528df77085abb5d49317f4783b262c76a2ced4d5d48a750ea87f70fa140c383f6d23adbd23af8cb68db5b37f41c4584bebbbce7e570bc13812c80b445924bd7b2d76654cb63e16d380a2cabae4259aed53ff9831071894f7ef35643ba10fbf555e1ff857595d712528f4bb944cd3c73e3050c5f8dfc8a6e3048455830fb7910d1391f83d0e522ea302b35c8d7638d6c547780a51cdb372ef1ba8b882692b10c3458ddc22c428b3b7f95c9e0e9b240f38a0d4525945775fe4a54856ab0e1cdd896cab72847930b017816029201b4ddac07dd61a86bb34ffa8d43fa9bc7560339517d8a834287ba4de5ca2c70386ca154a7c841ff2f9115b62fd1a71ac657346dd2dd2a2762260722827fd9e045d98fe460bf65ca679b54fc27b9d141f6bf132e0e7ae2038e5fc0399e2bc3b03fb15a9c383db5c73c3cef914893ed4997e1d7f603a5335a128adff8363206b886c8809bb86f5d435bc8e32df79a9ccd86a5e2b43fdfd4c7425978ca61ddee56814978cf54f9895a080281c209d3ac9f245bc52efbcba83f89a1f27a5f09eca43b0ce3ff22f2fc8e51d58be878886e6343b675bd1cef04b681e7285bfe17de865af1f8a9c6db77dd905d8b234f5d204e0f631b7e3f3d5641ea2ff8b6c4d8cd9dbd55d74d066438d6796ece1ca709a0a32400f345f8ef620fbeff951d0ff4eedbcd4c081efe476c2766dd74ef50e0cf764b19bdcf431b1fcbb07d6353ecf3ee3f8d7641808410c5c938b63f4a4f78ac48d0895683320ba843fefd20139c847753209c979512151dafa785eb19e262c1b853f5d3dfa149a8cef876d20a3b64ab270ae544bfe4fa81de3b917452af13cd343747bfe1ba12bb5cb69e9e010dc9c28b1dacdaea76905d516e0e55bf4eeb8475dae18ae92331d43ce99cdcb6eef85e0a49de0ead2e7733c4171a0ab75b3d37881b9825de7db7545a52e64076e16c126223f9ca3535a902b83aa2620715d74fe30a0ac6dafc2090cfa9b3fcc68650327155af6a28c780c742be3d8175b62b5ca0ccdab5f414f05eec069afe9b0966e56f9994731d1112e90acf11ab0bb2365bc6b7c3a3503f468604b924c5a7a161aae2575cafd68ca4fa1297fd621decc870120f33761f4a4885c169f71e8d33495b5757d1f97f8005cbe34a7fa2d5fafd14996e22448550e8a168fe1bde25f4b5f5d19c8cb73502e5892989af5996cd52ec9b87da7508ebbeb0348d293e0bee86ca9957cb68ae48fd84a473d3ebcc7c83c0a40050a84b880fa3158b69b96de9fa62b40d2ab7b6ef385d6c8e0cd18dd9dcf905bfdbbbe69a35becde273fbb9eed694ebfa2db477c089d73c24da00d832a6c4cdcd1cd70a67185b3e5032ffb7dc3056f593f5a9aedb2738e7148388f31f0cfda4a6976ee1a3045831a8dddfd40aa40c81c66a1a0a177ebef339d19f5c4a9659a930a8f50fe8a39691c48027ac51bd12c2a0b0a73d742ff3b778b4dd61d9dd28c320417e540ff566b5433175a73a7e691ac29580d03763cc0a916e2f52fc96d0909b7b02fe80363be9481ddf0fdb09f6947f72cd718614592065e673f52c9bf3f4702207aba157b4cf8ebd53d671d90cd05a491bc91094ffe569d73d3e1ce088a1391d2f49b298f8bbc04c941770e4379144871d6a967a44503e21e248a62ba8fd93553673f74e2a30fb2416319e1b494d0bf1da39b1020ad0cd18621c4b6fc376b52d5700ce88be60c1d0fe351ffd34ef5fdc9ced99ab7dbca134c765e35286c5d47af585d44f69361eec460bdba0648f2ca0d8c89e7ed88862214d4209a4daa4a08da6e761363f253a7386fcc50a4ccb4ff657cbfe25c7b4b2d0950dc5cad67e53d76fcd87e66b4264306392d6e7bdf6d09b228413a32d78bd456cf7a65404304d243c4d37365af12120d3515034a141febb809cd00b0bc6e3cae83df043008125d2913732c1ee250ecf0ea5cfe483af8009e202b32ac36e4c6c577aaf55b808bafb06032d31ecbee7097c79f919bd6cf80701f88921079d869d42b31b8d516759d887f11a24a0f6701ac01d2f4e26e4012a021dd71586f202819423ef5634af244035eef8f5a7038901f8efe3baca334e081f46572189bf9ac553b6ab300f0684496564c595913f525a3241fb6ff1aec34cbef678f7442376b4c431f135c45dab75c10d673271784352e544078d23ac3081a5ecb47a7ee76ea480d9af558d35b6092ef5e46f77a8f757da7632c92b347eb6f7549fe7d3af61d97c0796b8b20da0b1c1623d1d15458fa8dd9d1f7a1da6355addf3057d8f0e3b908f5b007c0338f2efff37ccc11586bb8231acf50d2d4aac21dd3029a2e7b467d72bfe93b76e6d6a2e5676b26e6abef6d4662e3e7ef900fe7ebba6ae309a0d14f2eceaeb35e074baf9c6f8bb1d4118a2dbfd8285d339290f5a66f5242869145e49895eb3be14c51c6976867ca325c0e6ba0465256d18aa233c4ff14b1d74b3149918c9524981785ed6be03db2ad347977ba9b5bae6bc663041b08123ac143327300799032d204a75e54f5a160c332ecce258316e49b64e22d699c237d1e650b554ccc1f281151fcbead4697974942a678bc0705a16d63ff59cebfb8395d612d8be40f77a28d0a483d7b750b18e65932fe9e48342ee379b8bb4d6d9677fd4d8af9b7613b8baf13c801e334cfee9681462caf3d09f170c2e2504f3081661593cefbbc0008a597f5df9eb0ea70f427290de5148c31967e35a06f1f4d5113cf2959f16ce947ca97825b9ccedf9ac4bfced2c8bb6a2a65e8f3192e6e79339fe92a8dff4382f64db64873cad100d7a354f5515fd1960f53806149bad12418bc33ad56de4258198e2fd4fcc46f2eda04326b17b94958a4cf8a0fdae63e5193cf13f4fc6370eb0430efc170f2021eb9a079e0b8355c96c8f33f776e0993fbae4304ee9b4e548bbeae1aa2b05507caa85f9dcdd73804fccbac4c84d1c50f14723b1203df7a7d430ddc86aab6bca1722ca2e4a92a1d7f2c51bc50efde88d7061e175f33d750fad70bb53e399931162ed3d4eb7106a2d207b3e861682c4572d464f2a28506b1e2d14d7300278c165109e79a227e226544ff1d868891bd7a4eba198da3c5fa14ce9be913c0e9c9005fb8af464ba990b7de79cf9440c94cb2396b1eddc09211e85aaef9a523878e39f2d62ac8ffc62342f2482d0dfbc4f25be8d78ede6cbe3f56a8cc669205578ec7e1b85bb5296abe7a912ca7969dfb367073876d15aafaf5efdc500db7f3959206a6ca949189fb43e1136894d2ffdfd249d77f529a96d87c8ba1888f9fb2c7251ede815c033220265893667f986debb1ed73bede79dc1662bda221675eb6e9791bcdddcf3a0c70c8b1716f91fd2978f85d8b40f6a43f40d11a12a60789ab75f20dd777a464537c34b9ce6a10c5e66b1ba4c89b02ae149af139250924a2f033fc4808242ca81626d89113b620e44db252f5b626c015c462a30d944369cd17de1cd98d21fcd8b94448476a083d177b87f0b345b157f84e6036cb1d0040006791644d39db19b7739c41cb436716248192671749cdc681c2bef301015867035a11ab310138cf52d124626dd12ace46e3cf09f4faa6d8aec73e95ed859f7a811fda42f9807b88eb319f540d8b12bc751a4cca72756d318616b403567142b40e19030ca574b65d6b487d68e5ab69329b12ae8b88394b2e23fcd3f67f94b07f8e5ef110233fc729739c17d0d0b23bf2a0ac91c5a3aaee9ba2dc5350cb3e5aac9b38da164167ea50c50b34ff5de298412d3db28a5691a2853fab574c9dc4bc53a0a815cc0e16db98bf43dce3d2c2ead53d0de539ba9af69eb47230e11dff7ec41ade407eb79a83b47e4784f3859e580461dae363e9d4f70945d06ae820cd9d0425c230822ee63d6538f0804694053cea2952e7af3335f97ba5752deb4c8fa8bcb46fb4b78410a453536ace3b0a49a57fa09c11c99e14ec025da8e7ab32aa0d9e31b227d360d1f001c6b3265f0902f0c5dad0a89edead48ed1ac24fc791067796f08fd556df08f079501f688b538c6784a07a21812ea18696edb1b10678923662dabd2a6612377f9836d64b83e884fb44f71bf9e55b73251a3c768d3d42bdc24f0425b8dc74049e183b556c7a104e0483b8a860dce20b452fe3a785ccc6ff42464a660553e7ba0ec0bde99b484392173ee009b77c3337ceaedbee9d231b4e2c61095433fb38c5864f09378b41d9bbec62169bd3dc16252b865f88c9dd76a9edb2f2756e7fd13a6f1810b7bfea577751bc11af2053b1eb8f1898c6c94fb091ac008e83314ccc4c355a1034da097a3a2e5ed7098612cf00d3507f8f5ca4af8b11896cec57f0b8d82ac9db4d2dc5c7ba9abc85612369ffbfd5e5c2dcbadffe9f24283c55007d3fdaf6e0c7f8a3d0b07402e14ee5da2b82eb7c93273b67644edc396ae3133f0f1bf695e49efaf7e09f26596f81b5ab183226394a204e4cd35ed2ed5f8fb0a0ed37f0eaad436f38df6b535f6d0466dc3966781f970b492b6142163245246c7ce34534dfae1c48a096e0c2f6fd7580f5933bcf91c6cf765afb50c1694dd2b916c4ba4f13c6c4c8bcd6d5ff868231008ca8d77977835cb4d3d57bf19526a8be933f14731fc9f207e44371d971d4ddd7db97fbab05f5eef4923ce6479205ddd6b97323f763e6026ffb4fbce29788c86832ebaefe179dd3bb44c2ca68614d340e84e24fd569400f428e15adf3ec6e3f0e2952e0ec1879c7a44426bd7d89234daa84b80a2bd3640f11340f1ea51cc04d94afdc99577a2708dbeb8b4ab28083e8a0178d1ebadb80084647e5f5d83eabbebbe67bcd03be1399a22fddd2ba29a3301b6506236a6975f29cebcbed309c350bb7cad3cd170eeb41d08f72374960bad4f9330b1fc4ac2c98a1642bfa93829ee5b2ada73ea065db504c555aa499957f5c4e8c6c4f7651d3633bbc8c56ac29b96848826ba70e495f399720d94ae67ceb6edf29311bd33ed7b920b9049b6ba7ee8d0af6c3d9b60e98be40c8fd5cf2c715e8a8beec7951e8cd3f5c25970e83f4d970fb5bff0c4eb778766d8762fb82f33c825705cd73092ea42ecf644c9feb00b0131667d1e673564319587c750fde234b86cea346ee3acdb999ac5bf3855ed8e5cbc5b4dba8260b1b3fd076e23d55116a70a5dd8babc660ca66fca4c9a0dbd6c63a6b27cd614b88ace3b416f835b70478b5a516373eb3f1c58c191984fd25edecb9dc102250292d2f467c3a22a4d9e36fde4e25d3b5ab17fb1133a471e043f5cc6fe02ccf9bedad4082b0bdd3f6154e16ff52752bf47ca9342cce6cb2d30117c9d4b46b612b264c5d8c6e8779f6c7c87d2518fa8dee99fdacd7424abdee6e3c7b4d888c5ca04a9ff612e8e850ead1fb33c03af1689998baf8333a8bad935c9629a30188e27530ed6a23e44b3ba517fe29210775eedfb2374bf8360c1355e430978fb9caedd394125f583a31e95e04b2a93301dfa7d4476db65a099e3e94df97a245ba3fa8e897fb3cc55a7ff6ae0432ea3360fa223b577548d85bbc44ad6d8b64dc072ba9803cacd8c8c320d258084d15b78e0623a6099c28662894303372c4c0c614d42326ecd87d6f8df28ad80513d09aa989ffa8280016b63e5a21d414c99a0d48005146f30ee812e002dcc97471988305f31a06a0c3e962c3f14bfa3fd120d7823d9dbb016de7747b1f1254d4df8969a70d9b5706ccfe0baee21a2421bbdee191fc65a2a9ebb3a72eb4a4be241c5b53cb42cd6a0e13a1a049a0e63aae0f8bfebb7b4f8de53531b560f99b3369f7a6c75eace0691287e65eb61f00e51d17c03f37817d059011a51c89721e7e2e32d2ac84f7dffa927b935366be07ed68fd128bb3e538c2ea57a2ecb9bcbf44ff682048131715aeaea9ec7aa378ca5dd0063b73ceeecb2a187e2a7510ad6ad8ed10cacc17c64071aec098fc07701fdf58d43ac79f799f84a2ad474c44bf00320d8425744cda4f407b86f35e03c27f3545b8bd0e2cf5e2a6f46ca1a51ed40822f91d3568a76678f7238874f85aae5283a41a1b40f42c492cddd7cdcbfc3f07074d682ce2ceade8fd585a0b58a30d2810c4edb15866e3a3e2126861e0512959982b57bebe9be4717bf26993d7ca175be7b8328e24683c68a7ac4dffe52137ad74b89546729dc4d8ee695cc10d8f7ad794d68d8a6ed1f765f7a657ec7d868f732c9ce21fb796eeb5455b002c5f05c0faa33aeb5f06ee7b3b37606ce6b02d26df71867cb988fe1c100a0cce52801405cc494433558601b050211f924291d95a52f1a69e3dfe7e261ad5e1f7c354f9ff03d1150f8186bda662d58a879382dfe75016310ddb5f77d0bef9e40ac439c1569ff317bdcff7cc18eabc2eec5a79a92051aae1b103627971e444441f1975d145f54a72f53eb549afe6d6fcf88693348006cc724832cacc6392c4d198db3261968c6497de01f6461ed904c6c8be009187ff36261b09e1ea4762c284776e37fce428d7b9117f2f470ff6a1c530bf4f67ecb03442cfdbfd0217e780f537012d0138cae84faf5a22c5dbdb0416294adc6f1748d98f41f6277705f4d6e3433d3e25e698f39483fe41ad17558b38c56a47f7f2de5fda7232425f35ed76b0f20082efccf7bc073b5262deebf1768fbc0f9a6009eb927484af7505398c8be2194a963bd7365feba6959baf408230e5823ef0a0f3012fb2c4bb986865c256a42db2aab8d39f3bc8118fa066b8ba7fe4f06051c1174d176ecb431fbaf4a2aa42f11d256a9bd7a7e8a37f57c2798ee39d7c6fa860b7460484a658bae1f87e67b1b782fd2888cdaedbd78f6272ca077f0302faa42f4e9a0e56169f2cf91d9da50434e3e71f3bb34af4fea5689b79a9fd711b51ac7340421949dab901cf3c37d4f8d9e5654db3b6f2c9d5d85b561aa369200a52e886d93ca7460d01c768bda7e269cea6e306b47834b628f0ae2f0e622db22e97ef569a721a3a92181c78c9f07bdb67a715737b79565b88a567a75f4961aa110f79e4d7a7b296903772ec4e0778f23efde70ec9f5ad1a0e973fb36c9a15f1fb3b0bc7a993e4cf00339961d28be9face22712834ee679a301e923c61d87f531f9e9da72876cadf489d49aa11db9960a60b8564d2520247a87ca99d8130d2004f0ab1c2fcc780c2e88cb34d76a05836275eb7392e8d97cf63111a2845b10a0b48ef796a3cdd3afa36dcb90658543fb3d7f4d5738de26ba68c873ea1405a37ee89cb55849a95bc0c2df4e9440f50ecb1420927b28768e1839b3194c1e04e8305bf55eb0ae573af01447470a7d761c42d68aa00e9ec8514e17c02abb4f4b5e00eb519038ad3b6fae660494388820a7dcbbd60a1e0fb39e87fc5d6989c08104aafe58b5ca6254ddf4cd1057a71f9ce8c50bf218cb3e8a336e0f6ff630c900e65f08ed5819211bf19c8578c55a09047a2260ff50b52b3842fccd343a58de035c8bd5bdbe2990267b5744860c88929d7eddb26ededff4db5abcdc4c1b51cfcd343659c404e99a7938d6db52c69512534dda29755f2d213cb61b34c7019aa2b16561e0f693582516db253ab549954272de3d0da38f0388f6475118d247b9b6947617e38bb06daa1b808cc7d9e519fa7530960b29f287090d8cfbdda557b489c8152034ca54f3c89d08400244c777655765470192cd10d94410e54620fc77da05b268da42646d49571521ea532d4d0491578c2c4f16f77791f96b3dcc168fb235e20dfccdfcfe5913014d694a2ccd6093af03c33ae3c6989098f7f53e708f74f49992118eb1a4372b626de9f33b668595d5c684ec25d74cc2a159ef5aae0994375ff60caff62d9941b7d6e28c54297dd44e1f59a8f817bb182f5bcfb06757e5ed7030dd1a18b254ffc3cc3f245cb2c8f026b34f91a07360424aac89c0acd375b76f57a0b60ceb7cc20ba9fe1146620fd7fe39bf516a3af33ec1974123abac83957fc612363dab63dd2d2c7c20c9d225f3fd546a9b730c7e86cd4b8ad3af629fa2c3c5cf5ce685a5537f7842b8d4714783c7d712af227b2ec01204ffb3c4dc82655e66d89b87773185257236512858e481702ff8c0905775f4d3538f8d6c8062febe9789bb213e1fb78cc75692e155a91f261f6728bc7713e9470365640d3b591fc3fc4b332d6195365144feff451f625b0ab012d1e91d03ab51651d02297a1d727c97fcf5005a70e31dd0b872ce2150f7543ec6ac25619e0b388538f7074045e30513799a38fa6caddf206c7d6d01e7d9f452c7c6a89a20a12664c9ef23e0ff1509461313bf5492d3f982088e117ac8a98965520099517ae712b0f6f811f68365711d586fc8c569562b63f3213d40ccf2bd1a1e77917f8eb3850b26e195da6e7d10a503fdf7ba618ce16cc188448c2c04e107023e5ba187a32c312aa3d07d7dcdb58f02fd021a7d2304e34e7369004e88184c26681b1205964eb4d825a77fce64d94bc653ab6da825e56681da25c3de135e0bf96d4ea09db96ff46711ae3877a61b31abe784c557bb0175d9c3c688c2fdee9308bd63ca6f9459c24dedec5e3290e67570fb27e41df254b8d425f5442df9bed033f97e518fdf6fcd301cfbbecefab6723e1e18b5d31dd2f75980d8268f8a95bca904afc57722c2381db2e85734aa46612b50fcc7932c187158d771536c1f59b3c93dba2b07172822235f04cbb243c8a1cdff6fbfc0f8330734246bc9dd08c02b36d690b42353d7eedb7aff9ff91c02f1deea0075fe23b7718ea05ffbff0cadbadcc4faf80ff9323dd109044ea57a02f56a4b4a9b07b4ff691b13ede71d171ba4ce29c67b251c20d3225a58d1d493c06ea7f7b6b10866a66efbe0e3cc08a148dd6f6167173728952ed56e1e9ac6f781362d3df4f35f2b54c483d571b69ef8083358933c834448ca757d9b6b2a107589a2a6ee5b9e9b599d93479ce0d5faf107877ffa1b3b86befaf7a15dff378492e4911d55eed89daa89c500d8942484a0c644fe74bc696c66786a61efa6fa1042edacd0ae1de86db90ebbe1ecc8601d2896192c6ec1bbb313125ee36f898020a62cb75e837e6e75426fba3a93a33167d71e0bc7ba315e59fa742a9637fc5726658440506dd4cc601e9b2a8ddaf6f4a0992ff692e1f6629807c96f111bf203f6d0affdd3233a0cf0a99347a8a587015eeee6ddccf49cf6d74c24027cb634ea992cb70427e8825c4a4b7ea4b9cdd4e215fb2d37b2b928dee5749396df99c806516497fdccf20ccfbfe0cb24ea8b1eacd1e6d855087370fbc9504432abc66ca1a7ee4c43610cb7fde818d5b3e89d3cbaa208e2f8ad4384039cf75dc8acc89cce7dff2c642e1c2cc6a5d3855d80b985b00fc3b3bfa6fc9659ed2550bfc710378dcfcaa87d2e6e05bb167c9c15cab4ce74a2efae897726d3e77e1f5aeb286c7847c2bcc8c209f5bec584b58a8105ec5c8b52dc480fa911fde2147133a248c0e68411248cb47cb9a1619063f2ebd7127c1a64b8882e2902288533f17528dcfa109183279e7c767df5142b7e1fe3078830a31672c75b23f609a922cfdd66c9ed733e140b0e00535c26c35be311eba4310e5a26f86cdee0c7878bb1e5944058b31bef053228ec8cfcae6e6280664b1f3186df8b6f269c225df467da93831dec0cebe8fda814abe59a15f6072a3c7ed10f0566514724a7b26b430dc0ad8f1426924a37a68cccaf5b19c9e40910c744bba79c6d0d679800b5b28e5b0a16503c11faeec61501764623daf8b3856f7562ee11ed85387dfcc898fa0f324b44df7ae768872690e65f35d3ff4d08e1a7e7df9953b99bc7b8a05af70efaf3786c808a08b2775ab82cd3123c25fc29abee81a96645a1e1039d5275a7c4b523ec0a464f1ab9b85b4fb827e530f53974bf82687f2c0ce1d04a6c8ef0e82e218602588704209163e04117e0f72b817f8a3f1d5db7e8380f5830d9be770b32c4b189a200cf57d9f0d8f4dda08672f835f3637eb17b075fa4a2bf11c26c9446121b77affdbb26133e53faa2d32ffee44d7aff65edb80ee0d85cfabfd5d8a9daebce041192be36db0686fd864e06a33a1234c8d6fddeb7e9d36f6e31173c218188f0a7f23d537b25162715cee31bc429a895d66583974ae60325ace0586aa551bcc021d4bf92299a19b0a783eea6d45caf8c829968e9ec7b2676b40254a1a187fbd66f8fce95cfc18ebf1e4f510daae6f88883a12bb73b4bed8ca92238411b9ed212bc6baeb5e49dc39535f35f8c1356ee52a85cfa600eb99f2ce530dc430273d0d174a36dae8b548815200e874c95c46854e9ef231d669205002cacdc73f77861c47dd2cb87e84434481680ee3f88a45302c313fd78bc4a378a546f276a8046f32ab44bd83e6587dd46aba54073bb470bb0cbe64fe3740bf7b28489513f721455a85d73166ab9c1bc808657dfee191eb98cda8b44af8ffe263b46c9b0f6059d2e2eef5787b9df379604dbe6a32206bde3b5f971b6c6e416859a234acf0393d053c51cfc5fa4fc0f5469d6dd1bb8f6d1c2b492078193fa940f8009590d07bc8d4a4ee78137f98b8f5f051a6ddaeaa950c0424c9a8e4949f92ca135ae152454fff405854848b138a9f30363255905227979d1d55eb899abcd412cf592f5c59ff86c0f8c5d72c9e8eedf30b4c00984ab619f17763673503453a1061760f729df00d5d873b70c6df9016f8064a6374bbbea36d075efefa82b682b0a670b46e9cc573939882a3aebaa07943501813fbb0cdfddfb6b7ba6552647e32b18a001e03dc56aa0c6f4f3bdcf77f8c84832e909b393d28037b7895b023da9449fa386d240d5bc89bd1ec4434ee26503453c616cf6ae68b7e28a1da5c0804cd682bc51176fe571752be9af374fe233f64343eeb03b4df914ee514211d313baabdd2a3ee1202d1330f23dcde94dad4d40d74f0bfd5bab6fbacf99ac43aa41cf92bd07f0b388758f207ae012650906d5f9c899e334570fa9d669f739a013c3ff448e18ea3c81ac15641047a2ca5bd167d707178a03699ab8f5d20e51443a3b04edf23a4adcaf73d731948821258d5ed155fd7c4f1f0e605670745de66d661652d6e682042e727d5c7beeff0d76f73192731117445a19f2300ed12087712f9ed34a954aa6eed33c5a4b3eb1454b5c1634724e927bed7d63d982edbe04255100369f555d85a4e64c004521de3d23aec0caa66387a3107368bdefb98ba0b7fb1bafbed63361ca1911f4e441dec34d1a5c7889e518c4d0a55fff919a0b64876957852e192513b20856e6c0a52d4e491bc114a643b29689ba26c69629b18e2a480eb765276b3b629e9f3bd785648d255be5a6dc5ba0469dadcca1144db8aab74afa9e27853f743bec9166334b0a7e73cadaa42c02de127cb19a4c440f42d6cd8523e14bc2363aa04a5c3973bd6df1c29c4bab7cb35a626d85fea066cad8d04c61cbaa43e67ae55c70b33e15d87315f444bb2104e32b2c64bceb7e6f23ed56bb6dc18a550c7e0aaf5d1b304bc70da02777cf936268bf0f7d555bb3c8fe0dc4e262c0ee3f888723c1fcf5aee5124dd58f177d46424cb7ee70a0154cc15a8ea14162b0b5572cf18b6fb2ebae8675f6150a748b8588c8aea62e2cc9c53e9b740ef8d4c8f55acaee73be45102c91ea166c0cef88be79729527c3a11ebc285bdd26112331686c90db403583d2efde910c31362b647b9f15af27a51798181f7bfbc64339d342a9f59df22799c1dbc10b5a896a307a4373b7eea4a4b8c79db603e55d70162b68b3907f7e58d50bef12ff92a92930fcb3a8aca416eb2c52573779ef47e927dcf8055e6d18a7e91a7e3b5847eb779e23dcce64877e8f03d6ed604ebcb347e2db27de64315e15b9ec8f46661ed1960ea5f6ccc9e26bdd5e2e92665e26437631d1cb393dc4b28d5fd0c61da71b91b985b5aa3ada88b611740b6190c55a5cd62a3ac9b369ff87dc34e1844fc37673f34b9494a6de4eeefcdd74f72fed36f8a9f0c16025f3f4f519ea87bc08beb2ac48b69e7e5e4ab4403f40aed8155af3b89c4f97bfacf977c9d068c06a79ce07151b85a21a0603f89e0eb911d23c0443eed7ecb9081fddca81e3b631590163ec2ba1ab751ea21a0a3faf684b01ffe9dffc5ff1e4474949f1d51a492fff234046b12d4a90896dc3ad9ecbef422ed675f743d7b5a145bf39e39cd4c19076a0763bed64e339a3e39621cbfce1219326306f79cab495514ac6b1c7e60c16bfbf260d002b20de2315599bfa618eaa395f077ba474dcafdc005da3344b2e083f22cd1f33070a1efa241ae577d6fa755ef20df8f42a188ce777a4415d039a9fc8d419a6fcaba098cd581a4d6d60b72660d98db1942edcbe32a399e4ce58f09a48a924c26750929cc1d8d6f29f708d46bec1300c781fb5204dca4550bc0711d5e5cd0082a8d96cd2003537de2e6e6b2646adda02dd796403fb4d306e10b432e28b985b6767de2e59692b310bb8d0ad81b3559f2a28e1018abd30ffee7b48a434bf48b8a4c382ec539f0026cf5083c0923f27c77279fbfc957b6588a0fe06f43208a92dde4e40478c98388cbe96a28b1e8644fc002ad7ccfc5dbe01f6e8243a307279f527b15ac2837b1023048b0dd17aaa57078a823225974f68e4ac562f5a77666874de57db79399e920f9d8fe502b445291c5c068158958497c70a1cb9e363c8178b93130798c8b050850c630dad72400f8f56dcbb8c04f017a64bfd7f6345f24711f0bda86912646d161b846b6f0de1cfe7e295cf6fcde4888c8a10ddb7938b4c50cd65a3c710b0e096a4e9a84a822d9904bd24aba42d18c604080f2d14daa519dc9c54f39fe2a60308dbe91c9991cb0b15ac6c1761b87e8e973fa577d7b9711725f61e0e1d340763b83a6246d5f7d9bf6ad2176a02109f9c5e1386b29f0fbf897ef322bd92a324d111257b93bbdab0a08887c4e319d2f66c48165f744ec487aad599b2f8fbdc2d9115824c16458e34126fbc292243071496f37a6e0dfc3e6622c3204f7afbc51b48be68401fcff41dca0c7ff10fd922a3a7e140398302ffe61e108ea0cafd3906e42ac09a77c8505ca5d8a34843f61712f626be22b132beb936aff780a443d6772f1af78163f257c4a563b9953f5f5c397bcb104a48469fbc7bcafb517ebe7723dc29a94ef057f690f19fcdc712a5828ffe4322a8b26525a889e626cc961aad908d8125bdc721ee2ed473642bd71378c5c6392cce3bf924c462b9660b02b885c71c7e5c3969093df3e160ab0c25df877a7791e9648b93f1d6f54d476430f06df7f16034b06491b0f58cf0c132f0ad166a798f280a4a3845e985cd9d28b998ed486c446cf75eed79c535157a78b93f55506e6e9c7c1aee45f645a0a241f98e645a5c01390a9b399fdb38ce4051b99d45c90bf6113ea310bfc39ba3b9a819eb1606470be530c3037f94e07400fcec363603231cc8bf5262c206dbcc73cfe8306c3d02bc70042e17577515b840d51387c33ba7f12bc71aaf4aefff4f49d61e213f5a4bfe4332da782d11bb6a9a7222647b6929fc76dfc810ba1b4c8ac24c89ad4c296ecde9cdc46faa92c5924807f3fbac6b6bc2ae9898f2185c9f58166de4ed4679de6382ce4e5818d4e1ad398ae372414deb4f942116092c14c0b074c87e603bf12d7ab5b5d90efddc2e33dd6e347c927779a13a204a19439b1ec74f9d80c9ee8e55a711b29b902ca3231243c7fad8f345a35eba60ea4fa37f251a1b5c6b01b18b2015e2e15b3ac53c5965bbde82abc7f4080c95ec9e082c1544545499b85387c70d1cbfe3536e9a6d737feb8a982be74f428e06c866fb8b095a893909c810773a8c154361dde26437b4ee97eae000eb6de112c53cb8f633d1ead1f39ce61177c753f5da294ac3fb90ba98b75cbef15bd0cd22705d8228ad1aa5c13e6ce22a79a76aa6e2f2c830100e42904bbba2232db0d344e2e692d004eaa11b6343889ea19bfd8610539ac46a17944905bc86dbbad0bb66cd6ea5c88207efb425bbf9c5ee52c92590edb539e41ea844a774c292457530cc64d7b7d84cdad9e0f5c91272ee71c148d723d078544560639f1e3b4240f1d9c048b710136cf125102070b39a61e528ee2d28d3e2d8f39fcab8111eff7dbbffffc64957e8eecfbc15843faa4ba6008a5594c15ced7ab512bec4592bea786a7864052db2f9069496cbe5b0b809af497f2458e7576bccf866e39273a8b691c53b37f9840723eb38d27b346e2a8547ec382598d4b538285bb3119b39dc40a8f5abbbc1558491e6950994125a53b8ce9c5547f9d606b48fe5cf6f43e28596d46fa0b4e0d33da46e6abbd05726a1c3d467ade85fc21263e4b0cd408717e3f14b5ebc0b0a294ef01ac9f64839a127390c9838999174ecfcb01bff3a18836efbf41622d2becc977fc4f9cb1ccfc912bc16430308f15a6174b0c4411adff00e71a18dc593fc7130307d0ff9b7883957110a4eca5fe6ff9934fa92f00955f552876b2167eedd84a4a666d736ee959b93bcac7832c2da510397fdbcf25716398b6952edf8cc66349a5b3715e4ae3723ebf594913fec74cd3e7facbf0fb83c1791c8dbfa6a3ceddb1934d7feba372db48f76d3e8dc86e1985dc56e1e68e0936f9e5abcebb9c2f70dc37584f70bf221436242e4ce1290e911c3d238d8a0156a91b247a9dc881bbca10d54dff613b84ea87487e1c68370a86859104129008448bdaedc47ac0113ff5b4581f506287038d35aa5387f9fb389b18465cc4eb6ef3bd40d9badcb43da408c5cfe4785c650fd3f15619e229f0ac5cb02a8187c191d7a9f98618f223aee4f9baec1d6f1a6e8ff31a9b242787884fb78bf28789b4e11555d015657c323ccce0088482c6358daa23958b39e752a74bd99cb69b7f4a653498bdaa3e05eb788b646b11638ac981ddd7c67e1f37222648c5fa73e6e47b3518ca2df0b6c00c231a38a53399b2d46061a14a8f450b01517222eb8809fe7801547d1792bfae2cb569d0a72a0816cf0fe2e1af1d77b3326a5813613926a32188c62856c3a6cdebd767bec85eadfc8cca84cfd04ca0686d24941ddd307612ac2289d57cb7a36fcde9d05666d840b32fa23e83d982a21e7b28456c51f6734d7d372e2cfe287305c97dd29149c665c28bfa7a0b19ea380c7e935bf70c7e93c243c64f7b18d203f00ada7d4c26fb6aab1f0a12bd1af4ec9ab376b259b79b8a45fbdf6d916df6322b787a0a56d66c9b4c705c8f3c53d31ab17455fa5f168529989f291d848fdb98e3708ce8f24eb41e4f6252c8f9e1fd05885ea9739aad59c305f95d85be14486549fe72a3bfdb916940f29ec21785affd7f035e797b479626d31cc82ff1f60c4c321044941ddd643e397fc7eafae26791a43f55dbcc1ea02585f1cd25bd1977815caf4c649323b53d34b6ab9af7b968d5d250e30886ae40af3f45615f34d7dec66411885fcb04ad06760ef8efda5b39b7b31e510b4d252277bfca8e8189591a87c14b2c9719729be7a8b1b1f247182662fcb25a502c19552eb09ff0ec885a090355fb1dd4b3ab701e8e9caf0b23debeb3a6beaf44475fcf4b61d53b1cb59a0c8e6896765905a742a966bf1dcb856953c0132d76be97dae7641bd3b352200388da22470777c8e1cca675e7fbfef4c87d674a32c737feabd915984fb29a5dee6556cb1cd1ebcf26ed463e459d19d3e197f8c3f52ed92e6d467a5e0d2514238692976c1a8b18aabc254b5f7529e848e141e778f72c544507b135067c1093777c7fd8a729200288911b172e0c1c991e34dc8f1f91b68d0945020ca3de068e85a8a184cb402f24123b195173c6c6123fb5110ccf81b4182f78070adb9db15c6d38f1d47e1b8a13150c8e12ebabc999df3f4a17784683801968ee3d69fa50c6b03a6d7c94bdb7f02f158ba86aa29ec785b73803e61d7463ef47cdff0e6b34e0113b9664d69772aaeea047e4cd2a10d165e54bbc39fb4b5d06a11bca358f423cff8ff179dc8e2910fa9e9bfb0d43be1cd33f3dde1056c0c1f1f534a2c3c57467e7be868840547fb4477d87f923b39d29bdcd88d8cabb0127be61d810e42558b80d0f6428c17ac99f7321abbe985c75c572ddeb229e412ec0239eacaac7cd8f6dbafb1bba256e94d2ad0fcf91e47e5f932533e9816f7bdc87b9aab7289c394f1ce0b74073f5ff1d98e54f5b4000454de2e7a8495b9d67933a73c6e7103f9be26e905eed7a538ad7a81c4ddfcec95a82d31ef7971a31d45021f48b8aee6cf92533dabc3e1692c2e91c0cbe8db91ebb16d1a281a2a5281e04ac80c8d51a6f330ab9eadad5e44e35ad2e85a1bac4cd6173540893fce0c9cf12dc981461158ae416818c8b562d2a0e33cd377c615dc20edbfcca647d44328fd57e854ca86cae88e33a5d52ec396b3dbbfec8509f890bd2dff554b0c17f9402183862c68c362e329f1355e2c2c423463f13633e3869a0aae9ad1c4aa52ed6cb9c5c6e8ad11879968c54794f8b74a4ffe7a6294d1146041cb3dc5275cd9d7f81959caee19ee120367e51d4cffb68545d92743bb3b4342c41ced9da33219f2f972d7e294600b0a8ea60a70c914cd73de054f82bc8cf44488d1d4387d34c6e057c00a8481f68ace032afca35b9bab6e1adf2e11ef0b541521d57f3d45e3ca5b2d19ffb28bc2851d02cfde5b322077d24686857ef02db02843788489e64a54027830d80e3acf942714a6f084dc4f8ea65ca63070d175c4c175890ba80bf7eb409ae451c19f377b9bc7440a4c1ffd5e815c0c80bf922763ff1f4a2c1eea282106dc6b2214b81a377cd7e3c5d8642ed7258212b9c8c9b0b5b1bf4c70c2839af347718b4a6ca27615418814627797d10968a8259907310210c2cab5908928acb15e9cf22b1485df852fdf2d2b4abf9137cba8bceb284cb62ce002088b42a3cda11bbf9c3305148954b331f6b359df95088641d3fe3e1a29ddc6b2dd8cb0ff0a9543f88d4c93a0ec723d0dd2e647d672384072012090a814371b720a13954b8474c7c3e856f55b30497ef544de6528a6edb613ce1d546a1eb96205f8ee09d19862db18b7cce61c7238f3adf8ef91e5d16e969e2fb572022ce88aba41fb145907e8836cc50879bb0e10cd11dca7258ac835353f5e35cc05a4ba3f3772710f4670bed2c190f39e0a65ca058a96e0c588f2479332bcfa36f5364e4d8804e8df3fa4a9fabccb85bbd579a7131ccfd04902ce9ec16af2ae096dc40fc2c8a8fe9b31b205c6124c2ea6ebb03d9132e8b0da9f8c7029adc128ced02a7edc118bb6d3780b32dd9f894df18dae387985f4488dec9386c56fcdb8ac00a893e7cacf5906484ee4fd5530a7cc51fea406dc2e316342ab8e4b7c08a9b7aaf77b84af235ef757d022572ccf764f264ab2038613b795e4c49ee09e35a9bd81f28b92568c58f0226e03903f49d8cb60020766be8b2bb79e8f09dd0a83b987312f6127836c77b585fbf5adcca36edca10242b8c17c1b5e60e3578be494eb38a595e7d934caca423a29cb778286f1300a3f8bcacb075d8e9b0a75dd9266ad3369084951220be1a9bfdb518b23ee878ea92bfc41e22f14ce5375687a0839a2772d6ac6e4ac99793ae710d23fae3b5aecfbd01109de4ea624805579345fa9d799d14c2b22d99a840eec5ecacc71ce0571ea62c4970ce294f8e1e44a14a3465506dead35243afb3f526bc5c22aa70e4f3fabe0e30506a0d3ae699b97e178998e2684092c79288529feca2adf9b4f33379ab5f5c6203b7069e5e99d8e3570412d9646ed4e1a7fa6e098dd9f1373591b2bfebcd2fb4ff921bf6adc3c6bf34fe61e1a30c142b1090c8f524cc8105e31038bb74e582188d7d65bcf2322c95badf481087d0f0d5f3c57382d14b76520184128640a2b5192d4592b804c3b6e6fee53c14f848aa3eac3c57e037ec308b1d44823b553a0de248b1639e318297b3d6b6ea37f7e9a8fb7987b059dd3cd35c61713ba2208331d467332efc7ec804043062af34f799f47dbce98ea8094ac589a8eae7a6ae15afb370325c79cb22b48560fb1ed5f0debc414dfa539d2dce32cecc0da1a64404755db790ec6469d06738fb4d7e6758dcd38ee0d7381508e288d4d92a7b892bfb5810d9a3ef930c0b5c2aff514a005f8e2ec98d53c74003c7fb391b79faacb2559045a9f30a9025b2d94471ebaf8c29126951e138623a849c02a33689959eb5073a19b1bbf715043ca628b33111b5e75540f519a5bbec266ed8dfc3a339a62e3bd542035904e45a00921a29f3868ff43e16f073315a49200efc201540c820a028c59d668492d8fe08edff006a59226b2f717ba38e1bcf409cb13b21c395aa5e00d44119779760fb960c6b2c1f89b6d8d77befe011ad60516f7ffec102e91906b2ac2baede1d365673c9b7dfc847faf66c8373289e146bc453b095b89d8249a3fe5e75c7d2c06e1cf40138288a7cf397ac7a3e8f46275cf5b7d4ff29150fb35c89de1eafd534da925714706b5940c22d987767d0001269706b3d9285b8b5d9a38984661047bdb0a3b86763e17b6433986e334673c1d6a4fc0b848d07e18d8439598cb15396b691e368e5f05bf4e922223a8e632d65c466621802c249ae23c98e5797045ff00bd2c7ed7a3fed7b87aed83a08fa61dd060128e658449471f9e54c83280cbd1abe1ae837973a3f1b2738a6904806e12f16c7be64e831e03902ed0dba28b56cac655765ecb11eaec36938dbcb99e747fc4564daad5985be7e273e90c56355dbdaaaf4cfc1c21578571fe10e766890a5633682ecdf16fb94bcc5d1ee3c0964cb9fca6e2703c7e39368d08f8138c846446160bffea4d005e3f02d53a8c72dc229131730cd4440a3009636106b00eef4badd8ff56415f8d28049230b7f7f4993137276bf8438b291a0d7c0aede64b409a80763d0015b465f0a06609a7b05b9a58e72e23edb5628ecf038111b2f9d1ae9b7c7cfb31797359be87e10849c5d3b6bb631e73990c5aedb307b7f1afc89c1ecd8779bf2df11849faa815588fccb75815f4027c4d97bd571c9d7950a54ae6ef4be5fe35462a8b6f1a0b6c2ce79bd6fca7310d9e79d2633b508a4913393798d8618db97ee46c293d8ec8fe0e0273d1515894b4564fbb7dfbd81e7e7eaeaedab801778f9459cd6a5c07556ac741697b655746949bcbbdef9e1a30ebc85ba82fb572fcf21cacbabbcb0764517073790886edb33d79488db20f25ac36b1b241dd3e92a4f7c99360c6bce954985466b0a218807c27463fe4afa85ca9d0418871c2cdcfbba4998349e88f2b7e44f4d0603fff979debb6cf684b05876cfc89ecac3258ee5a01bd76febaf4d02ec41ac4a9902c305b6d812a2226fb56923aebca1bb7f98e5a45a49d668c01cbc511c45af31ed916f260e745116e8f32faee26a330522de7b12c1cb9b7d5d6d02f115f5f4d9e85dd6003259ff80d004b0504061d077982c34cce17a061d6df27f5d540f57c9018f661a2d74fc1a610e3dc1ddb8a0cf8d6c93599d59ff839ce89a0d76a76037a6b619147dee5396d7186e267f5e736655624a86ac47a110d80acbd10cac30cb2cd88458b447c3daf4ca8e6f205519eb66fecfbf1b4840f7d59609701ad136944c41f7ecf8835f5c7d36bb7e6fc8f6ced93dc520c817a3cd8a618d66994ab3d4a0f26b5982aa4117bbb4f8e84eefb3aced9ac4e3e8136982b2fb444df55683e52a980f191f94d4ebfbd2dd8855be379e840d7fc98170d65b4de6c0098f990422ce115dbbb2099e300e4f9b604e6a5653a59b48d8f1be80061cd77f0d3c45191ad38b121cb0329d4fca237850b7baddbdf87976bd65c49591280dcf22c416973f672781dea253982e87a512f7432ce9524dbc300363a0b6733bb5b0e0641fabe31833f9a6a6dcf3f2a2aef8a859e3a7341596993f4143a158655e7aba4c5ffc9f5bf35463efc7ec6afddb68cd936efe12e72a6c5cab342285797e1dc42230973608fbe351414850077dd94e164159ae34fdee741821aa08148d064cb07e7feb8adc13395c76c70ada8bbeccf9e48dede5b49785ee76ed162c7e07eb416cd96795f5b69cc9fb34287f947ae38bb388977c109cc69f5213299469668566ed427b1a5a15264353a41f2f89510c57195a623d354588b9b1010cb833a1b07375e4077d71b0bca1933b847a6068d51a3479c1b0ef4ef59063c6be4503a3958d6e84903b87828404e47175589161d367586660612b1849598a3d1808c2a80f11b4d1aa864cb7763c059e684299097ae88a1d5f917c636b2b1e080b89a23513f8187e80e654f428b0c7d425117a13aec60659ba2e97a713106cd2015394174f93de286914973105798f17123039e52106baa38932d7efd55f93ea0d5ed07b39a03e19e704f12174a1d7d220855f74cfdbc83bfda1ba4cbb160627e2525113fbb6e6c8909da32bbc19acac061d7932210308c5459b61d4470b0d1dd4d25647ea47763eb1b9d0f0962f31a3874498099cd183cbcf331433105343a75de1e37d88e40238a9ef0168bf121c7bfa38d2681c52f9157d70bccb408119bcd8e03e6e335c94255b00a284fa36347e5b03bf60f8005cc8c54b6868b8d552946f517285fc48a8f32bf9826f5c32d9898862fccf98da5356eb90c87a98e585217ba6cf5a5805afa562d8574fcc0c7c6d80e2ac9f5b4eb845a74eb5ec6cc09fd4c913683498a78cc0f0f238a108d42299bf5ff20fd14f1ac1ffc6a7804d175eb48829939b5f8c1a85ed712121aeade51b143f7814a6303cfa2b0d3dc29a75d6c3c8726d01882da3fa4dfd7242038f73f28293ebc0cd25945cb2f5b986382df0b8da0f5473247f6ac9a31070388a388da518457cc5bb5acd8f2a9881fd6732e602fb726b5f97d023e5ec85c38e9b118938bf0c60fbc13d454a4d65a050a3c5c92008fb1969ec3b7607be99f7518b06d16773c25101e34090f33bbf42d51c74fb473d258a4de8017ffa510bfbc71a9a6237730c0ce8aba2f3162a153bccc3a9b55a2b742cd880cd51b34702a4b838f14ed13ff7d1eee6f2eaa5d2be81b2f0c9822e6af734e7aebd6e3c19132b4652be3ab48c5fe64f39efa20a6337ae211b86c222bc941780cc4050709512ab55b697e1317660141dfe037f0ac6e27f403bf429079ed011f4f040e483e667a487cd728f3d9929282cf0359878783b57c3b8b83d527dd9a114fddc0a61ef337bc028b4a68ae6b97313757936b85122bd7c0972dba70e060b7849ee16d109f4f740a8821cfb4afd4b369b84590fea0cd97d6176b2af7eccd5cd28e752776f3dc64e3dd2fb5d87b488e11cf933c988fafbc616dec63b77754a43b02a7a9bd7cdf66c08e9e17dd15f0878fa5baa28c46a14889ac7efbe3273faf777da3ec413757c24d6f77e7f181e2baf7db0da2a3d0c3cf129bc571318523882453bc321b54ab05c3cbd1890061457dbb7b49119217d2a93981e009198ad9b83d23d23c8043acf43da1009e41977754ccdadd2ed5a42bf4fd6973c744a717d4d216de8b9d82d78c77ca240056fca72ba9f6a53829e0d7bda1287339479f37f95118095c20f0d309d292fbc711634bfc1a9a3fe95b78506c9a0956069a823033a27101d21be47deda52ec158a0f13d26f1d27739ec607a933fab01464bb1cb016b5836a0255ec4f1fb5eff3cf4227a3df9750856a5543d2e588d7f292c8e7f2e8dd0dd7cb4e1bb34aa2e8947415ae30e6fb23b9946d26941e14577199c52178441dc3b6352a371ec7ba954a3770cff9ce4503cac51a4a24e1f5555ef68d83b4974a25cc35be5d08615700fb93539884286a5f27c702f35dd579c2579afb005e404836d0b9308fceaf25ada0415389daa091f47365fdc43db8070e50ce0b51f2afd014ef311a662e0eb99fc600937008f2431a0523d67b665cfb6b126a4a362da013634beb9f6bcf22124510ba1ee0a0de537066baa1569e3a4ca6b75afdcb9457153b773a8fe1b7f85e17b2ecb712353e9eb9d57051664f9266fc3079ca71edfd0141fd75c353d3a77513f86c727ae219ee05c6386e9b081ad8eabfb0d1acc300e777d8c06061728dcbe6bd72ba85c4d897645ea42542fa3fb22c7d75757ae68784d9faa4adf5ca14eb12f4f79d607b7c9d39f9a712977bfac3c1d58a4b1ee9f92856c77d9597720faa2123677399aded6df7a52c6c4286280d47071af069a390c8cd98b21d8ee69da26be39937998358916fa47283d23a820d79a618b5daaefdc2c73ac1575707a8b0e061273dd12b99d424ee43bcc15719d1491aff67aeeb1e092b6c189a5698e575b8e002075954b8c509acf6682fc469dc8e1fcfa73dfcc294fc65c209aec27626ef356a6878b57793debe3f34360bfa59ed456a857fc97d64364bdfbeee910c20f94a24444386eea1f137d502b6db8b2b556e386897fc8c6d1dbcef0daa74a10d269b0f85fd0436665edbe0767a86dcc00d521e71b385cc351949efbff5c1c199c2bc002a77f2255b06a8427fe280f5d511fec3d54e16bb07f4f63e43a42e64f125d1f07ef1132121684aa39c90e60c7b39502bee20a78ef296bc5f1f495952997e147cdd67b1f11a29a556c636f4f5077c7d96ee9ef35808c9087d35214221586052b08e4719b6199e4f0e19448e21dd658cbe87fbfa3b8c3539c7979dccb936cb6e2ba8bb1ddde9df125cb915b82e14b8d959e7fc7dae0201259e019f3f71d9a1dd89feb29032d3006f04944f1e0b820a93f2d2d685d05581948b3a67c9b1b34b292587afcdfb5dc81621e62c8da3ac323d0dd773ddcf7f41c40deee4f832d88d6f22c33f3124eff27013b7eb78249624319fc43d935c6d55e431c24541c2677be0d00dbc9d790d531547aef174ca7ffc18d5f6827b2d0a2799feb6a97d9be0bc4358e882f5f8615a56c4eeebbc4206e373291b0bac94a9517bae186a6751d05371b79c78a32ac580b492e2f6ad9b398716cb754b01243dd599ba8ea51f46aa6ed812d2ff148febb766eb1fce4bc592fca753dcef99610be7f1b411aaa9b3c4d6f952ecf93a1b839ad83bd21fbba1e8e423cee01a578cd8e9f9f2d1646f44be00eb12519f08c5d34d26f85062c7a5c6f05a0de7d46270b3a319085937bb3bcecdd5deeb28f91f351da53dcef55d09c7ea6e521e9fa64bd89b2e1070703e2ec19a77c28b558a09ebae5b4547ef1088d2b45843990452c25ba709ca942e8c4aa44fa227f4cebc653b5282a620a596b167d86331fe5058e38f0fee8107a19d3161c1bb04bb5393ebce4e096918f948ca4b8dafaf8949f13c556916ccbb734e5fccdc3d5b4e549901af4d13e87766a53b4764af795de21601ca282d0fb6b1de22e8c03406bb625baa72ec7584af0ecf94cf15aba9556f083a0939d4de9f9072275e24c2d80e6eeb806c4b3c27c6590c1bc14e3f3fdebcc80eec6320269d67918c71209d0c70435cc5ebef2594464346ea89b7daf582d29f17ff211922ac8aa88eebc18f0044384a79e2a3248c94178ca568dc406b33de98c21d7f3c2c1ae387bfc48ef546ff314f8c8214debfd3388fb7a00f099675ec01c8651cfa54d7e4e1ce8c85410479798f18a4d12e92ca7d2523c6d8208c009feeae74fc37ceb2efd0e2d2f9789ddbe82daf06f8a4aa6ae0cae3820644997908b66121b3cb25ca4250801017b5b68c0cd3b4bf400158e97c1ade77028aa54d3174d223c60450b567c003d9c1a481a6c02fce6768bcc2915558601b99d9ebe08e87af5c882382396264a94f05085cdcad705cd9e080efbaeaeb32b7016c008fe31228af7ddadc8c00e525263935bee4c9cd099288ab918b79db38e894542735937dc09e61d4e6745fcfcea5929679c2a8fe6509b3d1ad759f58a0414af4fa4075f236233f2dd7c914115385e1284787fdc1928e25380c943334a3f5c783627d92e17764a4f6af287a81038dcffda5692256007fcc597b536264d7a5898dd8170a7ca250e1b328de4056ab4b18bb0948e1043ae8d465e2117b820fbd657301988e3b1d3e6cdc6854cfacf85dd41a59f3fe92c2e686be239d6a4ecbf482e691c3885aba0bfc5f928de703750a226abcda3cce0054b579cfbb902938e8a17085a8f356ec70a31c1c75bdc00d342b0abcf29d0a3c95d7e3a5297a98c1d678eba44772740a703d68fc11f3ab2ce5c6f2aef1bc9aa89deaf9e9b10bc731f8c1075d99f43aa51cc78ba3e3e77060f5ba2a793beb57847506ee8351d603620487c9e4e67ef4ea27d742c6f3671e958bf0519a16a11f43d82da9f38a9f3f7bd02606b4b14b301ce53f3df8660eabdc7ec4178c048152cbbb3d441356a490c6238cec49a38d4210b80ca216969e6dc7d7fac37057d157c2d20a8ea955c0b10d260f7a433f8cc1c91f788882fa2da24cf4c2ad115fd815e95bab8acc3c6e808b098503c251b3081bcf2b7d2b7738e94210a8c52dc7f87a88d0a3ee138e897f6cd213b0215472b65a8518016ac89fc76834400b1801c5f8ed432da59b1ed554df5d7afaff41762e28997709aa242624fdcde914a6d28103f885144ed6068d49b50a9f339298dd184067147de5572bcbe770ee8c6e64b994b25ac1ba2febb5cc92a561fe4b7a83de6017d0b9eee352ed7186efae576991a90359cd717fc66ef0af7adfd3d53064303f696e108bef409e8eea97ce753990c26742322e4ba045eb28af927d53876a818a7b1e445d0ecb57b3b92bd039fa5d66bee57f5fd9d87296de43f89bfab6aa8701bfde10a02bd17340d1b5adef2dd89c26dfb9a613f5e9f0e663b222f9df2cbf9687cf9eb02172b279c440f58c447f72ebc6755a3914629b8de06ed9ab48eee6a4edc31169c876b80e48bf48cf07df7a80a192076279d35ee4fc33db24ddad8133f1e4fd4a849d8f335606dcaafc13488ea26ae9cfd26aa1e3ea171902a96d564b1107ea79a0e721498d81fa0814f9f50e95b07512299ef41a9cd1d6934550f15ac966d485fd28bf158b1881d644da9f7a7a42d1f89fe288f1c7032953769e20aba6dcae05dfe19d8d46126262e9c1e703331b5754737db476499d7ea86d4e77e3e73dda01e2d9532baaa1220cfa9771e44035168bd90d71c2362c1502745bcd9ed88ab22e660a5dd8f02c5b427f5367b9f8a051fab5b8dfe5538b823aeb99bd0f5bc003f7f3259c65874f3bd1d46ff7efb048fe284552be6bc3e32b4fe87826b5519e8d566971d493740c038198b0d041c2dd47ca2c7df6681697cd66dd4fac1edb788484f7b22cac89a589045e00a9416b310d9099790f1d1e48f8270e6c35c19136126e3bb2b1a8dd11b63782dfbbf7e8cc60cd544ab92221e38e5fc80e6cdb41f2d200fb23d2ec541c09aeb769418e56f408e9fbe84630563848f79af2416ac752574551e3e58d37c913912672ff0d2660ff9fc37d2b68c675313c05bb662d0690a3495309a12b18c0f9ce3997b82a44e1f51771f28339f98f297558e7d4bb2d48b0db77cba2588d1df5987378b71f39d4cbc172c38cd00b7363c9c57e44a8bbbceb134c08e9927f3ed531e369479cd8bf180c43a5271716a8e776d2b0b7b5edcab3af3e1159cc0dcd7b0c161a3bc052bca7e57e6f5c45fff41159ac12ef1b96f7df3b8c8f3b834a955b3a9ea06728b847f70f5ac2d401245f10bf18f7f3db8332350f9659bb6a536e002a8854240b43fdba415b065d1270e36892f79b8f24f189f4142ead68a385d287b5599bd064f73c56198ab96a9ccd2870cd948dd1c6a9e24b67a18ab883d0972281749a4c346c3167df787890f50b6cd54142ad8ebadbeddfb98bdb82f0689796a9f5557cc7cfc93b19d4d40b04a0e615aff3bddbb2c3014994bda782455ffabdc26c51cb17792e54eb61d6372fe0fb0529d73694c2a72e965cbfd9d8cdd94e05260438a43698eeea4b9bf616e09d8a4cce2361d810d2e5c531bbfb89ccf57ff090f46afac9d7a567054982d1913edbe5ce98738a9d7dcc4c96d7e4fd14914892135e96c1980121e988ce8f2f72b6c4a1c26673d215e7e094ea4c867f337ebf164fe0957227f818c3900c241f9206cda34f7d1f9ab8f3eb78b7bfe6c025e52939048c038b649773fc9205b334a6203ce2207710124b2056cac8a9e155a3d256cdf107c50d654e9ace03aee424575252aa2c9e842d1daadacf17dc6f34f63765380303ccfc3084de2868926949229a151b4bc204221e2440be8563153b63ddfbf2ce4d08e39f19142d383db620ca8773a91cdc2beb00ea3144665a90a92209a90b65353b4d459a82b39186afcc568ac79a40b660c970239202da700ba3e8dc55a26df9f44d6dee0dc5ba37ad93a6cd2f8bdb163ebe5549200ef935e6b0fdab776f977786f728a0ff2274acbb608e097dec6d23bb11295f1c6ecd6b9c3ee7cbef3a56661ae46a052abb1b5cb0408ae4f37ffaf8e2ce5952a3a01df0741ba0a15861f1b87ab233e136389f7478e5fdcb88dd7cb20f43c131dd15070468fc60edd456e2064082ce4dd3743ef103260564b9f1af572a85272fbab168fc1bf9f896541f83104a791d0213fd1dd7527dd4f0a87a246e66e9d79ce74d1da5ee0fb67ca8d50cbb693ff643d595a9817587a95baf8da7264d8b4559da3506e1c09ddd06ad25e61d16838e192eb3c628c4884bb51c12f960b1cabf5a48abe550a1e7b48149d2417bdf74f88bd603d70a2de41b588d42d2ff52ca364d2f62090b8297198000310d3e1449af0c6fe28ae43ba7b60bcd2f3c478df00f749203c5addc37aac853ab2e57b5bb3350efe383a068fe22ccf13c363f696a0a426eabbc5687b77e8f4c3e14946886b82ff74cc7684ebc931609a1ac54d2a53359fc6bff4804ed70fe0efd91c890ecde63d4990084a7d6b7dd6ce8beb278d5ebe446c7f14b4510f54d7e8b10e61431832af9f893a84de79a879773560e2270fa7f3638080d333d82f09ca40b5aa820992950cc80af09ad159718f40cdec6b3b218baaa510e48f38a928295e04d98841821522b14d1b08acee21df26238df1c288564fed1eb359605f177bfeca13ab0f93d02606bd5e804ca7bdf4b46d2afee72b9df89ac01dbef84c80618fcbd1cf84120fe69c6e2ef458ba51eace8d73cb004d2e1941ebdc5aae05e92a90791f93620e69e79aad3ef9bc481d21be699c5ae0c64e4da492222d66d695b11445a4f4619cec078d43eb8f997510f6ebb485176ecc5ba0dcf503569c751a8008015278a4442277efb9a3575676e2fd18c38e25dbc41485038117aba261b29bdd2a44066985e9b5b63289fc8355c47443c675af8df6303be33b508054c6a94a19a1160c54644d3133f8a20d19697f81bd03ef9a3aad7a7e29dd433ebf671e99acb7ee99229cce85ae339b7123001c97c29ce9ffd1384743d5cb12aefeec5b8a98da652403f6aa61cc93d1731c9083fe40af2b56a7cda97c4074c01f99b5654642bfd3143375439f85fcead79ecf924a36e28a0908189f85f6773b5d72e17cd7550d3dc950e3de8bfb0d24356170c805b2d0833dae2055d6bba58a7b75910331898119732bc3746facf5e55dd7a18fa85f49676f8759b1039d37283af98e1a2a16b67509ced92f962209eee414c17b7ec8cf7585d2bf8888d01baca360af349f43007cc57d3bf966665d3c15fa5c6d12f712aa5c022633b1fcd86fda8d7d9b00bc949d96efd953fa8a5583533f51c0543b4daaf9ccf11c51d809decc52db149745ed3f4ec4786af80ddc85b6100695aef3771b1af2d4dc6a88130db32c56976d083134364d78e1d7881bd23ad71410e4f33e8b17e0559545280e3ebf6fa51f956a23a566a2502e5ced0c98f83faa54052091940d0404fb2a66c19caff7858084db8fe9a181d7336f5f79d727003f46e3c6f7eeb2f4f6115a04317a09b6ecb099567c9988a47582a14037cfea6ec902825e3a49c6a9a319531a9c531ff5896571f54296b6208386f85a068727cde6be97772c375160b370dbd521b63c45b404470d2a0fb44ea6f7557b3c152ec29b98773d036da089d5ca09c417a8864292d0323e62b8c5bfbe86acdb524e61439892cadbcd898dc034fc49ccd899ec17be20364901714d3394b91ddebce22b60f1d618cb96dfa725f2a367d7f57ee9f1c54c6756e04ca5d108279b265e1d3b75b1a55cb12a3a26b007afe74bbc8b1257e782c2ca82ef7aee2a3772bd23281a3150124fb3357c7b03cbcb6a93ba70361e3a7d3732e55d9ffa98e325921998f28b1848aff4e263fec53dc96b85ad2c5c118880ec749dae98fa1cb8a05f7d42296ddaa2e8e52754d0d64546d342e5f2f9abc86aeba78f7591c038c3427ed888566d6d157b43075dfc76de9882071b163a41724ea729e1ef56ed3d04870a6e63eb04300b65175d822116933c7743ed7ca7b8590532781e2e3cdbe0fc2c1b0fb88b3aa20a2982c35fca569636f9e9bb1e2206d5e01a89800fb8505e750b237c88d516e0a1392194de884d3f93cfed2b3e777c94ac1d72c665b6978b2350c8789ac905f7a5d993cb640709f5e02689e4303c7415e6b28c44a294d8513d44f5de20b1ade5cb7dda99db171af8d97a62104a53dd2e83127e2b9098b53be852d0ecbe8bd880b61e2af9eeb5133495df4b4954b68041a19ef1526e77ff01dca0d757365ec7d6049004ee841201d8479827f9448beb73797b358df360b8ca15089b780163d2cc1fbe2fa2af2b0a5f615f6e1dffa167ab5bc38bc869a14c5c483f0d75030e9b282a908bb084919e10e56e7b074912d70ae295969d9410306252bc5172e0150d2492a458701b33763af6d633f64a821ce838482ee6ce244f135733e2a9c58726c48e14103160f27f46d3e6a39b2f8f8dfa11b36de3674cadd6629c536299776957a8ff7839a45ce544425704e23e31aa2adda8abdf9228963cb437bab6c652734453d3866f43a5213e52edfab46d5d3e99cc1a742f10dfbf798dbb0df1d7c7232189027b53bb39e6f26bd92474467d31d7b9a4be357809c9a0b9a6c866c7ae93df612bc20fba0e24f35c45cfa3998ad7edcc049b39f466e918337eaf0048343180433414488a61145822a7f6a31be5bff416c049378a57635ee1417216a3bd97781f3d73cae46629f9a8235c6f895b8adef14f8adfd403dcfcb2717ac8fd42b78cda6f28fa45ed0925f4a2b4e685e05abf7383249a4f34f836469c16cddf6599a584cd9618c089a6b4d1701b0de1165b47bfdd3e1c52dbf4efeb91e0607c7ab55a68d27fcedf2c3d2c49bcb30fe9f603b67e6909bdff7a21326b1ee3561bc0a38c0b64da4f08e9322883d93a7f480640e242535ffe01a8fb55d2422f79eac8496bc700dc2463414cbde09d447d81a3f910bcd1d0924432646c14563742044cccd3a770056087f96c61030013ce338a6859692bc87e82b8641751c8aca3d1db712f7fb12c0f5b82b1e2ccffc0f9843b2638d9816ca6c02d4daefc0269b37ccc0d2d7ed01d6dc142b7e46d09feb18005e42bb34459efd38bb586f37f96eb78084a416e3e1491ae0b2daacba379d3b5c0a50e583f69f97f3e7812ffb7be8095147c866f89a6071931d24b3eb03742434fbda081becd6f3108919304d56c67cde6e383b9168f8b3d9910b1660b9ea710dd8e921227ca51994d66b9e21e8b58ac5034a8e875bdff43774d720490222437b2f64fcaa1007d0db88ea3317c4f5be3735ddcc03d5793e00f24709fcf92aa33deecad124a41e88a65bd91b3c31c87a30d41dc7e70eb9079d798b3366b1212336ea80b5634c159b5a910a3284a9b506f1d9d3c8719361e1103d435015ac17adfa5ba311d1ac48f56b6fabacbc5c9f99d3dc433b7fc3a115bbb597860e64ced9265e72b6007ec98801be208379aba13f109c9f72a051f8330f1c5d1c24c541538b72493e14102a212e4af323f1adc6cac17b068901c56ac3f2f1474e35b1020e02fb27c80cc39c722966cc4bfbad883754ff156906fb0b95e177314ac87b6163fdc3c94a698d627ef767820f70a9242f89530a6a5aa2d270ce366c7dba6c6856df744f02367889a6d524356bf4d5bc916ac36e1f1ebdbf16f37828a95742a68dc51f77baff21fb0a967d7ae8d9164b32d6445e3ed02655ce67cde86e04acc8cbbdb864f7f33adb09cbd19a2d1788632ec9e953ba88065cfe67870647cdcf06f14597fa3ed50836fd4586dd8787f9c68b346f8b7b1f5ffb869e3f461397c45bcf91f8c10451e5fe8ff584e5c28f6bc505f0b2f150e2821ed8b14e7cbf6217915b507c63fab2bf5e44a1ba1ddffe9670345336a5ff8f992bfe8c5541a2114a190591a6afc6e79752055998357981639e77189f0692681e0a1f90b5b68f5c50ba3ef95f37653f049addb094adf44672bb4f7039594845627921e8185bd03b120882b17374b065adf52d0aa573bb87956d0e9771f3ccd407b778cae6ee6dd2edae57501282d895d2b5fc3ccb8f2474530609d259ff8d4a86754e37d168880b328e2e914f65c823c49838ed85e0d3b366c5b67428cb5e0aab2285314afaa47ca5a20bd4a4e5bb1469f908b959e7a4053deb6471b8a9c21b147e4c11092429ccffc05c3a6df587a6b94d853650a572c07b9b6a040763837ab57f42b389da73cf6d2078b468aa575343f56d6876874eed768f063cb63475ac568e5acb89ba46d00eb5927a3bc6df15a957c0ea5e63f873b898507a8d628a5e130605908c4d71ed785a373beece4a701f5340999d83a95a82f3e891f556982ddb0e51475a5ef05633ce88c06db75ffef0fa50db4bc4846a44694fc97c537029f3c202fd7d5df3393c7ad8ec0718a529f9a5b61185bf3da226430ce455b12d1370a5d7cdc5d5798d840f5cb6f2f682ec2c4d1cdced09c9c0830c154cd5b1dbd1c06873c1c4f07a946ff00b39c420a56f0f3a760d287f27da68251799f7ded41a2205af21530bc9d6566bf40cfb1020b33a059c2f5f9e9e581a2592fed9fb7dd5291f449b0b5a238605c583ce776a1a63dde4b367bff6ef048d0cec9c3d853e543dbe0e39cd9d27c13d139f2dfbfffc6d374b4966f9a361b0ed48e94c09c2d81619535f783be815ea33e787e7af6c6bf020d528cfc2daa9e319863240c763836c4e0e174a3c7098530fc8dcc6a13c879180885b2e54850f862351d2bc26b766f6bf8688d17c7601da6f7d3e243fa53f78d562db20ecef51c8d18ad44c77645fbd23b05617e939b19be1296469decdaf98b237dbd04a8529bf1faa71f630c028488721ccb337fc95e76c35a8c737a8cd363adfbb46349b7301bbce6f746dab50e1c9ab64a95d16f8a4080858aae458eb21a9b40859f34e5b4e88a3a3f16a0497061e408345161d04ec2194506ce72fcd5acc641537d37111b81c11bb7d0aa79acd8c268ee26115c54218b1b17f2431568c830c17a3a885e50f41c0bb4c53fdf5e37184ebd302b980f6c245b285e32c0dfd46a7d2f144c9d158776cdfd5f01f2db3fa329899ffcaa755e742dfcb4f68fc659dcd3acf0255ccd57106b811ae860f1bade552b6237ccc22c46d4f43f9d5ad69a7c396983e476fe194a6c56aff486f27e0a095287edf507589fa954d55de453c94d70cd6da00ca1c7b4fe86e7aee4f203f80d793e26ebf60dcf67daa3bb8fe6b2b10aafebb716dab758057f358cfee554acdd09c689fff275584d8686851869f452b413b4a75052305980928f9044714d465bbd8e985d87500e3e7fbfbc6008e50ecba4df43e118d6106b50a5deaf23b8572c9f4868b7310de4b317ef86db16accbab11baad68c59f44fd5c4ac2c8d18596d4c682132d1c1649bbfb9c86b8c7cd190729c62a59d69dddf5dc3af6bf32e20784619e1e861bbfacb20c5b7de32a08839bd13941194696eaa04c2546a0ce8fc0260d8b2f0348f7d40a8e375961b01aedfdd93bd2bd489ad9f4e972dcff32ee5c27b7ec3506ff5bddebfde965cea6c14ecf9bae06a7746d6edc749f174ad5328f862c306dbfcf800fa513d12cd37422134aa800079b955d61312dafa4ac2c5e404449ab340add27d0d9695c5be4b224e783cb2a60f8205d2c3b138c6c568848c53fdfdc5362faee246cab4f03b8c3ba932366d61e49d489e95c61177df1a74688d6e9c7baa9bd0cfe0a37fb60aa33853b5f28042a578744789167453a8bb2313214abdd905831c594d803da0c59b9e029976215c056a388a5917dc4467aeedfe1821162484aebb8830a1771a847caa807bd2be4ad9e7a75a9edfd6584a81cbec41a3fa6a9607cbd82afdb4f3121b8d290eaf25a9432f495ebf294442a70da574a14d7df8575741bc736f22e1bc303a7174dc4699f136fa70b6d239d7cb2f5e833c01a2e212d0ed164450a6fba2eedd5cf2fe244067fc251fa27eae935e7868d72d3cb97ed49257c110c0f53da5947204ebb270475c7e342e284d5c124f7928840cef57e911a37b08952f0107dbccdd172fc24c5b28f65713a8c7bf9756901073a22825805cfae22badee749e9bcd73579b448dad3f22da793d73b2880851eddef211e46826ae4ee100cc19aa1684bc1807c68573bfb3d4be92b0e3ef72f081d690f4b508975944d17d0f99b77c49ddc582cb23650a31d07583d35dfb4e543dbd45458d8a6f3d1918a25986b4b7e4175be651a44075df1ce68e9c587dc1a782cc07ed2f5dffc7b6c696319976b3da65ebaf1fac460386d9891a7ed56cab97ba8e404af4b5287737601a4caf36364f551d3204ecbc321b848f8457e5618a98246363a4990a7546a7603bd5870f49f8f2a8125ec36bd9afa0590690021be9d729fed30b3a95391ba7b0e246278130e6badb1883842011297bfe66bb9dfd5fbbc5e81ce058d6cbe06e332e967af1f42140174ce3172d349b48ca7d32d68b250f72fb75767e11c646ae3d9bc11f267b6479536201bce5e957f5b984495bf12e46e7e79a9aa6cb5b531815010a31ce2c6867977e9a89234301bd003b0e9e7ce06debb0f1e30452be3bfa7a5b608ff7309717813f9734c4e6334285d7b7ce1e4eb75c2e0119473391341a1b65d26f963418dcc31da03b284e9a3b62cb329cee0bb89e8b371b8bc26e2c3f1e0200df03c37bf0a21a6f59b5117bf46654724fa68ec493c90015b3957ca22e9a7ae3847b2e8ca879be3894544514cf93df71a7231c06563a2de49f09100ba21c8576329d0802b53467c6b8f2e455afa13b10cee94b2ef606a8fa1ebc6274b12621fea9ee2a1f00d6e61b680151cdb47a9b2ed99d17ea9fa87d2f0611575d75ab4107a2554f7222f371c21ff4dad3f2bc68bd8e897b76a671323a0daf4cf02d95475e3a9f38457636de12cec66152e63ae6a08d1ba7f8250469d27102051412404c8bf9fc6a775edd30199574feab407ba2979065bd7b18d8d8ad58b9970405fc3b9df08074c76b387deb577ec0a13f3aba8c3b9f25b6f7ba94d62a467766d58ec04dd2a3c5cca92d138e8f6a7a4132e4eaffc6a16bf84648a113e461850555affbc7a1c2192483b7fdbead419e31fb1d756de86ab0e7aa76db5d648537b28c8c119c1c9b6f905a62d14205102aec583bcfdf899caae5860d2f9b5b72cbee8b4483a92ad591122dd1ca8cc869a4ba7cc927153efc364872271d1a54a98b5ed86d590a6e762f394f393ddc30df28850106b8e2e1f778c239b59287b5f323294d901e49846b842b9ee35029498e84c6a364e6bf86188ba18bf90e5957db05c44ea8a96ba90392304bad117b29906237340939a39593bfbedf0c1ae55e2fddd393a7b89c3474e7b4be8acd80a88888021f51189b14be7049a6590ba4b283141868f25a7ed555fa10eefec288aa1c2394894a61cc1c9261ca4a20205341ad69923ae02b6f90220f92e5a21f2e3e7322567936c8c0d92eae0223f4e1f3b84a925c7c3e5d4c37bcb18736a64e58a8bba6ebad0d0b2fde544c88f33ddde10e38f0dd51ce917a83e6bd576f4a97655aa57cbdd9f8f3a635eb1b424eb7a778671a56535cc3873f05c9f7dac1640b9b8ee7a9e9abd72bcb5d7b8725b1f1360481d8dbdaed4693b6d371348482ddc533f0b909e8aebcdc023db4b4d7f5d086f5c21cc3754ef5e8e9474d1214393479a94559f975576e24cd3fe744145509909577d56bb2545eb8da14b341db91f94d108d87149bbfe75620018ba5fbfaaa8eb083b858f078891682bd595dcd9c5311a05b0902d971d6107f45baf0aaeb9198f324ba4370b66bb814c1dd52e66d7bb3c49a2850d8581e1aad9bb15dbfc1f383f6dc9742a201747d13d53f5ba4603f1650ee3261246055182ea95341a7a2d71d2cb44ffb63d702caf22b628398f6802ba728f667c8924611f803a79ee29e99b0326da3e8206debb162168ec44f87b46304334b74df8c6fbd037e5647959667da3e169cae0656f8f28144f1ac16be646c29b9898f69e584aaa4f22e8c0ca5c7b39aa3057fbf0564bf858b19985930af3e1ab90de9f7cdea2dd3f8a17d1a080f4673e3ec8d64756158a05b7b77a32dc67dbbd0ba1202863d27d25846bc6872ec516e2825969a5393783001f02a7d486e256ec4734df667ca92dac657d924badc65c27dc76d0a95938e83633369affd81880243a2e5564616ecb62f3247a8dfb444cb14acaaa7ee6dc7b4326438a94d6d9f9c5cb49f7687556078d7d9d9fd791a9c54e1f55677bf22e9725d87f60c12b3a6637b9b34a6eef3d0afa571d766b10b0b150785b5c83327fdb63606f2a557690d17ee4bdee4490fa6d43dc6984112577813d0811612f8e4cab5ea2b92480bb9b3e23d3df4654bfb70c3edc6b9f3ac20b9f060d6bbe3a5372296c1a70f5fd660790c9aee977daebe5e7e0f02dab9295f874f45b3cda231eef5b5350ebd241e2f1c559d6171c667927a993630ed7eb15fc8d43b0a5ffe035046881259dfd1b68e662692997b920866c0fc64ab1b1dcf40237a5e1482501695db55a862163a02f4ade82320e1f8ad5cffe58df132aab675e72ce1ca6c5d5d69d06aa06b559fdf70a1191d84e812cc4c8c54e124a6b1b24643f3256cad220a6dec7a24986bbb082368fe6ad4f039e640c448b58a1e7ed14fca11d3fe6ea8acb6ce46db701c4236c40f0b21277e58cdf65e81650e9cd44eff4bebecc6cd06114ce8cbb360c85dac1a4b599f1556a04d6191a02dbc25c3a01afa94b2d03b11c3ac46899738a5e89d8680c8503cac374d2800e3d4480a658038df58216af55981472ea3e3ab86642b7628a1aebc5eaf3d2682d2fd88b0bec144a5a090a8857f8c0185eff7d336f3a0f37bd558bb42546ae8b29e0ff5f71250fdab02cde15af6abb30c3542ac5ed1fc1473391be380ad1d325505fa2f29542e786f6b76306bc0f5a96d27eb95fe15b688134ca9df01bad07a85f487767692ed9c3e9d2af4e8c4e22411eab1d276b831110f6cb3e1eabe966cbc5090310c8aa5a44180325007bafdd8cb23920c201f5203b291b91f2ab9c064a188176bb1b21da1d0fcfdacced2867058ed87cf73b813c921e8a85113c443f7ace05f51f8727ff6195ab73201a3ba06a224240ac5603761ebdfe2350c07c66a9ba0f9b5fe589cda185b9c510feb06abdeeeaa93889691dbe194c50c49fc5539af8c1f8ebe7a8dd9db93983e1c3e5ce5797325a12dbfb3dd4ca12e1a9922b892c3540f76ee5f33531bb1d16e12b4f72bcf19275086fe4a2267b8ba29436ce7d72ee3982b7217e8fc1fac731ec819746bb4d30520c062bef62ca2b507f2fcdc2075b5d20e5b5a875af92410cb3351ae85d43eec76ec664cbc7a72c703b108f4b7caa8bca778deca4b045d51bab0d30cced9a67b1a190e26687b156d74f8e5469a66e49d1f793adb7cc18b96715388068e6832abb8f818cc06678b3332caa432fca4cb15c499fddff2fb874c3c4e605982e9c4305f1b184131916d7e1bdebcc6c4f9212b88e7dc4b853fff7aa9656b3d5a245f0705b75c799cc7965b8fdea4460cb3ca1f830f189c609fb50b0c7556053caa089600000ad08d000987f5b4640ba38bce726dbf774483cc87c6fc0286093f108c918e11c92e5d596a4b34e674038c0d91452596878b417c29dad4e53e6ff1f98ff24e66e103f964b35c3aff7cc77dc45f4c2ca4ed2cc21eda0e33f88e59d2f3d640c5e0b71864cae8237911c3ba6140c58d2a7c01b282e5896d7cdde755d71db003db63f6d83333fa33653f5ef6d6e7497768d30493d019293b57d69d62e8a5e25e5bfbdda00f8a20c9f7587eceb09a8c3955b29f5d50c31ba52ea5bcc162129ec276948537378955452d885a997424ddf00235824f7a9e0b61b182e4e6955c391c404607b0fb98be95880d99ffde7ec2daa5db4d0e196392cf5222213346dbca24322ade8a42fe244e8c0c42865be416bc1a6b2868cc5a76e6cac6ecdd2ffde241a7c4f6160559d51043688f328fb2a3a65d5d9a52884773449266d15e7bd3f3eb5ffd33c0d172808abbaf2b20d11c678011e29aaf149ac9556be059e74a6c2fb5fea76a2f495df7e2831ab2a2aaaeed09fc6eebba8bf80d5ed73f450133fde6da8e9c15dea2e0dc04329581135a9e69a488e02464de14f3909d112e31bdddb4c2f95f4406263aa687e0dd34bd53ef71c3442a184450d3072149c81792e451fbe3905c86a5b22a8558abb0516ebc6493c96297644ef8c0a350a93094ba9b24826587aad49f375515783404011def431d0a369b65da42da7b1e51f672e044123d267668bced3cede6431f56e2adfbe6d3cdbd31da206ea1991c18382d1c390385ffd9a34b275ab540cc97074c60221f738313d0e7d51085ae5d992c7fe147164ece91d182abbf4db3a7c7c98a9a5c672ab8fce45a198e96fd29b3d6e13e2e7b243862d64c1aacd2578f621af2d0d3e67c0f5ce94691a590555a0acad639221dfa2f739f3fd5f7cad3cf1bd0cf0a65432f35bf006825e52dd5cc83ff37aa4d1d0ff6d14aa7c18a667c442eb7941826442dafd229e87b30cf7364ccb9fd4a4b1d7fadebdf9d94e20c3323d63dc4e6db439188e7fe690a3f728869d1792c53ac593da917983188903bd558d4307d797f73370a76067b481df8c8ee4803ddcd759489410cb7db76bcb45997ef87ad04c2ec86189693cf52aabb7b13277190149fcf275a66f3c9009dfdf47f6e42434585945a598836ea181978d598285a424397d7e4c0266fbc8c022ddf84a053ecb75fcd33f82dbd1001727fbd454c05eb39bc9d31332f407aaccc099537549f378a1bfa20ba8ac3b0c81141720155ae63c3e562df28ca881eab7179aeb96106292b38d001164bd495492520c8624adadec5d8b146792320c0d17b3ea4ef20839230677a13313e1993b6329afcaef7bb66f5d429638fc9ca353d07071fe6ec55c4de118beef2d49b82d48fbd57e86aa8135d0819ae283203f9f547cba993a9416600cfbe60dfa315bcaee9ef159a779bec3719e7c18935526959007ce1c13bd88be4f08ecc07e6506548be04548435cca3fea04f10ab8e37f2c90d017c720ad75549f693f830fb183ed6e3a3f7277f51a143aa6c351f5da2d509da87c0858493b2b119b8783de4bac91effffcd7775d60df33691b15ba7f99c9ae9214bc19b770f25c2391b08c715f8b80ddd5aa1f85ecfd0bbf3139cd4b472f951b33597438e0da0117e031d6e3bb003dee611f5311caa9a9f28e0ce2f84e6963a310037f61f8b8d58b35c2bc8a0deda9557f4720a51f708c16cb8da6909def66230d282305f94779e43ce9c66dee84029eecc65b346a404f0bf96111720b47b9bc3ef7cf2240dc01e171c7bff00a96de63484c41f516bcd71cf1ed72fb6c8fd4aaa7ba243bd2599c5a69cafec6952f6ec1747c684e378777db052568f340377eacee071aac3eca5b7de6eb3ac3fe20bafcb7933dfb4ee1bc06a34a2767709b887157d0cb60f90a3cad099fa626efd1f7503b1826ef4f653dc521ec409572d53447a677c76b11e3c093780bf52385e9adb772bbe348e591ae434dc09cfbf52a7d57f6ba801feddab32c65157675633ab51bd8583787fc5fbe14ecf5c365b503b05ee9369f0ab8332395064e3b0a812b1996aa22c463b4092501ba78a0cc88c6832e0de86dfffe661c3b69e06df76611b4c8f0056ec1e6da4a2eb782f0663daa9cc23f594ef08fb3bacad5f41c940542c557afa837ef69ee5bb5240603c9fd3c1d9e6fed847ed23ba814d8ab9e81ee87f73d0a69d5491e53f745f124cabee395274b6ef568e4639c80776a27fdcb277c7bbf25551876689c51aa78dc9b23a196c4acde324738ec7f5b20023d5e5a7cdbb14b5e53b7749a7e3ec1f512d95ecb1161bb268f6269b62202ec377936bc3d6e1ba6465d9472ede33a0cb83d5fe856bc93f26cb93dc2b1b22da61459677855560bc1ec4da5ffe4057de31b78d77c9515b1e64c49a4e85691f48f2e8f6375939cf7e768379ea7cfe98c4aaae4df072def8b7ad20773a1e1f68bf6d59fa7e557b9b386ca8c7563b71b36079d5dc3f8011471a2b39fbe15b31ffb9f3d4cd418da25474b364c7d69d89f639b66d939b091af9ee185b61885e6fc565dc03921bf051fb9449324be3efc33ac709c2c921aa11c4ec4676c8f7dd74f3269b7bbfcaba8132495c5b3cb7881c80f636e48450788cca61e6ac6ffd156c071271cba2af7746071d8028ebd0ffb3d4a875c2978c8eff4fb0a89e0de9c2708b73ef7c89528f47ddeb362f7973711523309d9dbe9e2340c2ce2d89a1122ea6214a4ab23cdc6847f061c67a4327d350d4d71e2a3232339dedc4c29705514a5213c77a4c919c1a5fc5855e896f36585bc65ad0e5ce57b6557e3170fc5a82e08f38d231c32883aacfd82d9057632fe667f36a866c34036c6dc8c658ac2107cc696e869a18f9a7de8acf8513f4fd6799294b2b7fd52f8d31222dc867d96e444771f9b0ec92d5c49dd5758dde55db735932abb59c635b31eb835b914f833e2448212354cf25fdb6dfa5de998b6ecbaf7ddf97966f121d90fcf0170ed8b94d93eca018298bb5a6002e9e09cf0fb3e9ba8186534f11ca142d51792c427d6a4401f7b691180d9e9e48b4c88facc1946ff8c9d335d2ce439af1f469a2c25dd336e55a000fc0b8274cd8e073f980afa4710f788db10ba3e921a73c836b3879f5af38d9bb8ef50ad4e01f95d289ed455b346a4f2c4b54813a3f817e9b2fe13ea087f6acbc6f3d0cac062ee6417f6c46beaab9a9fcdefe6f7d82915ab0090e6cf0bfa14756da5293fa1d3c6d5601c80d5ffe6f7bb9f9cf72cd9e5193a89da73a5c87bd1d536fa2571cfe23281338a02a6bb54368d3b27f45c81c1c764816a3dfa6455583482dd2d34f38e0e9eb51e943c928afe7d56b3ae6695db2d31a499159554b7598d97437f8ae0e165b75906a4f7c8174799c1421f0ded7b8885074128f7e54ad45bbd45badbcac8c8ae4cd3f18c76114ea4918c8c5574c3596d8c85cbd109195488bce7e2630b2aa0dc04f5715cab357a733e3ba8b261d17ca3239b936e64d4350cc151204e8cb00b16425f68f95d4c7243306f0f2b11eacf7bed3067143abd62999e23e462feac0b83fc3331aea7733500f585ed306f629c08a4534649806effce0d15d0fbe9e3fb1e0057b55212e3016f0196796128d27ebdf00cb389fde33983e757c36205078cf444a0c09f96a93f60d106371d03a204f15e962463202cacdbc24dfa96b3b88e1fdd27b883a49f8a80fb42070f3fcf6496453286273cbe3df19dd7ca170b033cf16442f6a780c3ed1c7225e4fd7876dc9c4a25e7d1e022efd5bf57ea1cd8f5eaad799e38d5c8f09c4442b5227c200ace311c3db9321c160cdc83ec34c91d5934fcba0287340623f5b64f124e6c0edc1fff84d87ead364b30d92b25f0754e68096a89fe0df095b8cda5dea719aa8451a8e7532e09d6618633dc9281769f36778120196c5d685bcdee3f395c98d06920c51db4d78806812325a6cb4e77f59aaf062d72ce0ddbbe8e97b403b322f09c44f35b183744977f1e4ac9d287de2f90703147f381295f3c6a31e4d1ca3cdabb47fbade870f1378f3c4a558fbbb92ee30ef0e68758e9c66a3c9e3e8078a22af479debedbc1529f1f21ec9353239b0de06969ab7fb5dcbd85359b36eed47ac28d08de7633e68275f2fc3ab0f46c0fa188bfeebd0bfd9b590d9168a6d1aaed8b2a496f9716d7f689fe688ff9af2d0248c63d1462f5832846a8e08ce641689ff87c8e541811ce2c3c9aec6af43b71bf686a6dead332bec18ff27f9b206a5142d122e09ee5ac4af9c1351dc0de166083e6af09623c8195f871981f8aeb2537fbcb95e7801ef031840969fa80d13843196b641907afab536daf426dafe191b70c2fc4209aefb09e393126112925a50e5f41fc705b6f016f029fc1cb43a400a7e47195d793c176ee9140cf782c93e64a60f917854fad8656203072762fa49a4b752d407e684de6c9725d5d3659a115fac8d486f7f0db751341e4c2f048acb961bc4273257f7497170ac662fab1f25bd610a7aeead6731b25f63840a7bae89b21effe09f31346b5388021dab4c35842be3e71c5da6d3bfc3b543f8f1f079de9c2a657aeec33415bb0a32b4bdbe1480c086f1d2da5a9fdd91595e71884e3034841ba0b97cbc0331a3b4841dd28101a5584fce10ad5a5c215642774a24c33b483ec1bcdcdaaf05c110566208fd9305e8f77943f27c5d305ccdbdbd17664331289b6115609896d1d4ae577f380efcbd1f77f4466441bdb522c6e862a1d805e51a64350ca71b4c99ef26305b038c44371af6e1e18ebcf76b617638802a74048fe771e79511af1222a3bbee0e8fd8f8d9e18217e3a8a62fb8de77f44d3e647fff2c821a9448160b4d61e1b46bac5929670327a2619ffbaa75cca5b7a97d5c79e3baf7e1ffcacdbf2fc64f376966fdec04e7133e0f3b645116cb77f2ec261e3a191f7e46e4058e9cbcdc4b40e4a0dedb5a8ef7e05aad5e345300e16713528dcc14d99cc52881ca70fb1fba2ead601e92be499b2e63af9d056d232caf311bda7e54e5a2ce2bebacf0d13add71e6963f337036ce02b99c1099fca0c7b63b35cf65e592f7462d0d7b355cc88410b38974e3e69745ebf1332a6b533877391b9f4cd6f7d4c90689f26d3ad596d5243534564a2d5e8dab0ae746196cb4ea4f167552ee83824b5bc1ccbf5f0f2fc35bdc376f174e6b5bba99e586e56aa24bdd8518c50afe352284595e1de97ca2c8760439dd7ad06998c1d14a0b35ae27ee20dc27414f1bbf7b3880f6b67fc8e16ccbe3c00ca844463920541949136f59f328fb00eb23a658401d8a47e4affd1da5c3c9fe3c5be09d946ad3ee0fadf1b75557dbc722b2621431523ca2ad6741f6355dad020a4f2bb437e48599f5d645af8ae5812b6b6a032f5de6e65f947c1a74caa90b4bb5c6bbc15e0968f91283973f9e5f47639e7f2d3e3d5e4f85a3264a18964e16c0450503ae1c084fd761500f2f70246e1ec2688d6aef336be82882347afd063276e1d52986ee44cb9b96f9426784e708606860fb278451ea62760fd93bef1cc5d1f403fd42efd3da0f04e13560aa30318190e6af1d079a91dbb1315b544df555e6d3e0e9af20006c91f28ee9ba99db8dd3683d8830f46165300ab21af30484f92a84c10a8546caaf65d72a7e6d8497299e26ec5ae114f0a34b9e3c5c7d39d311de45804306a1623c130404e3228967996e67405c71560290ed02e0816835815cb245f96db96cb1ffedb80d17b0e22573f833c5927e05490bf59a92d3f2a93f519f988e96056dd0013f015cf9c0abfa13fdc9d15b0a00e90031e5aad7fc2452d0ee44a448cdef8df7b162aaa91e10e8b6f5c7f9ff1bdbfb9f047a643b858a1421e2a424eb55a8eda4f234d77831cf40db79329a5771ad0b6668b2614b89cd734088e0fb2c0e78a40fa4696369a0052cfb9b10517fef2c9bf911d924fc4b1a717f9f959a8105bc9f811ba8f63f304e2692b609839cc5109ed4cc5596824522a2719c0576ad26fe7b5b89e3d823446c7f736384d6b17336fdbe4d7a837c3d40b12b304b896287f90bb779fd79cfa1dcd9709743b68327dc16190b771ba951b9dbd1e729cfec5eb9d5d9d699eb505bdbad501c64d18c215f38f6e3857231eb757276bf03caee753a9577d704635a00a8c614e3e99b0214512807a087ff80e42b5b48c261ff2d215fbac482eebb227913c490e8c1cbe9ad639e54922821ae611991111246776f1936e458b4caf3d52cad9b50a63adc3ac49dc9c9e81f95721d1ea005f02e3f64c28c6ce1c9d7ac95553711cf497a8d16cb89c78e724401b42c372ae0a04be8a5b285dca36429ab09a46443fc2b67620a91912e33e7006d9c7a1f665346e507c38e7472d0e8975c809443201443cc20656e8786a8f828c5427f1d9cc60767b65b8990a2a4a4204d9f1726edc48840559b5a71cd0cba4f58e3f2d4aa6424c2114e1a27915dc915aea37046bfa58e7dcdbb9906f14425875ca87f49827ac823baf2bed89bfa9f78d8326be404fdba5e1ef4f3f45967c9df6702b41da925c0135669d511cdcfa3fbc3ba4a18fbd6e7b71e25a9ee5df6eb16f790b77fba52611c0ef5e4616be86c758e621523376c5e8f678792b6c9eaa6a0ec9a8423e10b470d0f69e58b6c399400fa8a1c9620d8680cda5f24dc5e8ecda20917a8206117b8c30aa5572af9a3fb9fefdee445711ff49819c4419d5126a2847644ca870ee6d224b521418af2c72302f7f3cf9ebb86fc490b6f1470cb6da66e41d3810ad4d1d89e478d5cf068a679e9be7a299ce138be9f5ba55a0b67e01075d89c331e2eac3218007811d42414c6d298ff79549a3de413fa29dd5fb6d4b02e90ec0099af9c08ac9f1005bc1ffc28aefb3a41c8f3bf2fb99942ffd9be664b29225806e67d4f87f5eaf0555008724ae200f27962caa4516530cc9a6e7732bb0328286d94d0732cf42afad4e6c194ab5ce66689645471f07a7a5f9a6a4dde5cf5a5501133788ab4c4ac41cd45e24041258f597c499781146f768503b9a17592317af9bd2055048483410d2d9a3593c2c60298b306f18e7492e8fb6257489367e5f2964237dcb0a5aebedf21a6383b55e0e71e62f28f865c7cd509cdfc0a5ca0843792e2fd997cb8467d9579fd9e9d81600eca7e02f7af28b20be4e38b2449bdbada81e15da77f66bfdb905abdcec6983ce8940b097f238fcb972196c4c7c2bd9b4ff22daa3a5c3f89b8f6248d632df6f20c6bd48cd39031614c4087ce968aab9f6ebe6c2915ed0fa0fafa8edeaa44c7c9b48807e073553188433954a12cf6388bf4f7e637144411120a3d4e5327df42873d6b492032e3bf7a5525260494f9fba600601c85a4f888e55534e0e610617a53ab5126deb876e552a3f144dacf01108e966ffce646c6b41bccac43fbd0c22e8f224d0344011ef8690f249cb6529f286c1ff007bb107b1bd16453b5af866353ae6ee1f0b6dc0e7d04c0af633c85005fbae723ddd32b369da49752c1f883ab28907c3501166e4d6e6fa2d95ea7453c44699e7ef07f56f6dc9fe2fe3bc9d07e1862dfd303efaca21e99e32c63f2626c02eed8ed734c6178ddd44593f33ba57fbed54cfbc839e4e2357d787f1977229896d78db1aec5b365ebc5c6fd2c16f320c6551e7f4e6d35847f7096062b8ebc6e6d6899c4479cc5d22e76eebb3d2bdc8888f0d337780c11858576397b4cabded1ec2f3dd54445a50d7eb24ac7d421354394e28c03361ac2a23150a04ed9ac2830b4c69ccdbf6074bc4bbbeb91c4db0bc488069156393fe3dfeedfceeef09aafaf9d9f3fec94f1e84bcaf9e61260491bbaeff2cb5b9ccde35a05d50b1b48de7b28ca56926c8bb6cbad9ed3d5e2bcc15831fe3949a51c3986742a4d6248200b09ef410764bfbac37a67bc0155686c1168856712bb6ccb0c63467f40211617b9c96ef4f66b532ed166f0440886147e512824acabfa6355e1666977195bd2bf84a1942058beed09929760848e8eafdc4024208e937e154459500c62cf690e3944680970f65992b0cd246f655f16b5584a1f9395dde6d4dab2325a8f37654330ac6c3c4f659cfcafe2bcc0f6cbcbc0f19991d0ce30743f7111f6304c06ad86051fed3d4caccca772d12086db02de0a31a424682c7b8e4c6d3ad6272e26664e01249f69867d9167faeaa66ac1b085f4a64a37f836cd3fee3e24e3e569a1f745ce102ce178b9779025c6b8d235e778a4dd585bd0f4cbb341ff5f7fc82d568932a9c3b0d6a9626db0402c536a74d874b3526dd863333abe4f9518e3d93a148ee7f8e83ba7082b48d7e64389030970bce84982298c9c55565d821c9a17368f374b402b52c50474416f725fffc45e3193068b327b9b412715ccb72904096cc6fc6a9cd86f73a7f173a5807a6fa8960ec3a65c86333f8672b88a187dd4a5002018a040fd7c4e3a205e6249a027493f1b19274e8d653295a0a2158879938cc57a0bc4a2ae5ca8446367d2cd2a8902c817e6a5a2495dc0c8e15a07a9141b8930e71675494763b2e9f3557e1f09895ec9d3800e6f7a1a17b1c10839ad991c6b2c8dc26a6e9fd877f1bbd6f3410132d47c4ebf35bda1ce69a295fc72d93c9a2cbe815adae6e27aa806daf8fd683021d6fd4d8006463c60e3f48028ad9eec6c68b8f46ceb0381f832f3d2b06d95e208312edf25be9480e9686045a1df6ac7bf20f72b4b3c5dd778afe5625672d2382dba57d11439a5233a2c1eb92163939f92d5dd369076b7a4119a0979278295fbe84de5c9344f4f91550eba8788e1fa0038eb112295f37805505dfc26c2d20265291ecdfd734011bff0d1bdd556d6543ed960abb8f9705eb7b72ce934ac6118a5ef5086f742310b40f61da07a339f2826f0d6558d1fd561a639af559acc8c0160d2eea31117b73d890550a1c7681cf621cee51b1bae030551c079e952ef3a156d7a216f77ea745d65f01e803d63e11dac16c4d64a12c27266a859bae5459440ab25c21a8eb8c7cd6f3e0d302637f56e3b3f3d7c8ffaab271d416fe77ce24ae0d525386aa20383940ea9abfbc219237cfd15f8c418d837152550188d9a3e734ae360a7e59751bd58cba1abbca2e4da1afb390de55064cb70c48eaa5b4fb6ec8a9cce6ef51fa9acbe1bc7c113bf96a1ad8c9ced50f719e0a0a228fe5a56085a14ccfff67eb01993b2a17258ca32d1111c560899c8afb737fa003dc8a4a0dba9679792d0dc0a32bb74ac2d12c8b65b6c9a5c972423cabf203fa564c51fab352ff455bf224ca58a84784b371c46969eb8a22a3a91945cfaca128623b4856f034ab27dce6929b23980689401512a762975c16033f26787f3abd25d791f3ee1949f3a0c9e8596d15c9de1d5b42ab41e2dd817849be367de78f387a455f9d7ad1ee35b291f1864b39665b750a885e57100a0b555dd506f811d10e3151c052c68382e719ed83358108a49d3b9ee3251dc9e732c80398bdc62b84373d42712725c5ee9fa23625a2c6b62fd0037c85a39d923d3fef242b0e097e6478c6f4206d30a71443c75eacadcfa4be539c12baf27965298ccd56060e55385028c8d2fb841ad343fdf8fb32023f9f79141d7446e695d11075f974fc5825d384a72c0219aa43cd041e18392413651194da533f0341932eb6df63e9ee8bf60e6bafc1641349a2051454abe709c2954ecb570c942a1a0f38c567bd0315a977f3b9ab8502d6f6214268c70c7ccb785a9ad6d29a301c5db7149467546c026fae626ccbc3774aeb6d2a5eaca033610f42b1cd4e35f27684816950ee95e561ecbfb59d7fd861bbb9ac9849f9324d54618c3ffd382f2631beb298dad803ad26a8ce8cdf96df01b7e706e61bcbd9be29ff094a0bf74f7981ad44a6dbe9e976976b9f13b4d5b4ae08ad8c93c1eff942bd1571a4a3d7f6d5cff6d57197a3f45222207fca3fbbda7cb79297af8f5f0ab912082bbcf436cde7a4300f19547c943594c041dc9836823b11288badde088d58ad28f9a0527aa0c70930fba8ddc2eec3ca7d4e3b30d4c725b47eaf319916cb9a0e88e1b44b4300bf2f085a0ceed046051dfff442dcf42e504fe8efc3a06e205ef2f868aea5e3d3d65a5ccd6cb6f6d77ee118fb94580125fdeabb471b5555e1fab9f63f9d77cb81b6404429a1967fa5bc093f22ecfdf98e78dbf98866303f5ea9807e810f19255c63ffeb488f348ea0145076adae14754f1aadb093bffe92959900eea18a1b1c0567782d011b6eb5fbb51c3e8df5852ba1a41e1162ba9b3e7c401818aed55e16aa444b2a084660778338d89b001528d0e3153caa41066e29c306cef33f0fc9ec7a70fe39b5428465e39f38c1c3bb23fab7e5554f59e9d68c1e9e79c7f448d680df4f870d974c6e4c3e554c8563ea03deefab07a68979bd146219cbd59d28059cbce516160132ee359cfd2a4f14906cd3bc63fc102d610fab4954805ff9753d7e54420d5b791f3c4303566f09fb0149ac978d34e829b9058f581bd85adcf064ecc3ca14b2353cbe11f405b2485babcbb232fb2d70c8a14d60ea260dd224e9ffcf9edc2b434dc0f4696bed1fbd73c36c89deb1458c0a076365445abb54148a8d11356a6539eca3e2377083b5ad1617ef921d8a59eb001f810c713ac91785fac57353c23716e5f7ad0e30ae18121a0d161103006a71b75be58b16285ecc36dbdb13e1343de1eb1095b15d65f9a65ce3430201e4cc4de6b4f75b7b2eaa7650bf1a73594c746221ed5a6a76924875794ed0475a67f9b76ba18ebe437a6caab31e0c30b3a9f53d943ed1ef8e5f40db23808565ac91dee1365e993751a168f60af287d5be0739d199102f679bb674d03f0d3e3566794348f9c75675d75acc1bd5ce975eea61d6fb086ebf621eb6783e75d623d8a13cb08457bd7d3d0f56d6ffd1d4ac71ee42d588bdb21e54bedb62366ef0c5586ed9a60252808a64f6574e4e1ccf25d6560648f7005436dbb5e663e6b12eb2417d04d987fc56a82f4556dface128f66a74d3925711a94a59a255f3697136063aa9e254d55f840ed0a9f1ab74e1653ca146d57067f9a781e985adcc7de988e47493fd7073ee433e32d56d23a1ba071bba99e1dced073fa8286e4ee5eb544da5d56db0bb7252fcf017177fdfe0705a551a5a51d69fd7f49c91f5ff8bdbb0a716c8155def96fff0340567d996464b3a93687459d87f9ca37f3cd9731f42c24c267a37f603a4aeea53692bfb892b1bbc16266fb19e06030b95e9ba525a4835f0077747f17983697ecf8e08d00a5c50d30e9b27fbf41b53e69a6ac63349a11bca1084efd7ba219c5f47c6da2292ab66cc1d4b4ecf1767ddb0712e6df12b383dfe1f66a2c77e3543c3d948db583f2234d3f145adec7f76dd6f4969e2145128cf840bc5778ef39b8c82c74d4b26d2ec2eafc7625208a6c0575b5688286aba82c0f00b884a73404548589cebd4ddfdc32e34eb950863a004703761907d6f10a5c98a8822c8f911a764b007a737ae189928d0da1735f09837ca1844a13161c98fe5f84520009c2995bd5a0aa6f9d115d4d17172a4284de5c2adad2ecbb1d04f06d8122704cb7787d61685fbafd1a22781728cdc2336bd34f9f06751139ddd4b90834d6ba8648d60abec99381db4d57a52270939825ceb80aca2b284a74548cba84a6373d7957f70a0dd87a3f7dd20c56eb0c53f449608d8b3ec4c999d23b7f3bfd00a0d17ab966d53863b312b6822ef13622fba6f6ca0c5295dfdaf41b75d5c0f7eab0d66708a32a8ac3c301c773bd711d5efa8524a81c628e1a0152a2740a10d99fcaf34c7aa5dee83f929316c751553860c7f7b1795f16a236884a0af56d8684b196edf5a45353978aebf7122493da50255a90baaca5c2e982dd4a5772a2bf1f167e8ad29f0ac3f27affbf0040bf8f27aab1bd959077268adb13531d1b218c34033287e3121a5b02b2e326b8ee28312ff14d844ec98daf09c06047fa20b437ab65249ba1254926df060dea92d5b4d75478b24ba62d588c3ce49dd0b893fd1f577b0d8d10ed4ec73e6fdbb10c005c772ff5eabaf430798eb0aac9ccbe3cee9a0f30665770ec5703ab970c0ad843aead30df03a576f148ddc22a864fa9424f2d8f65f05c3e78841c308644ea69984be0b0e88c05dec34e78c9bf9b3238b73b11933a2dad784f69fc8b718dce3d4469abf0081c0d359dcb89faa17036eb93a30cf5bae458558b902e286ed15f540851a06922cc3d660c3189fbb495b2f213c997b91fa1d6df19f5aa68b161acae40e25308a2b897fb8ca62d428b3bd655ccc7534ceb32772e54887019f371f0c7460dbd29ad81d5e6dc8630b198b978705d06d164bb2bea21e5aeef04e499ee4f4157e37595cdb864a7dfdf1ab2087a2a1bc7a54d9f63d2e021c144fdc755d8a7f25addf152c6664d47f480934248b38a939ed4871e756a81298b42e20086c6e305431fe3b597d4639bbb82bd123977876df18204e14039939dcf4c6ebff0c152d056195edbf34376e75f807919c3a236b47dff6e1767d67a89c002d41dba5bcf7b66269a308ecf6185be13a4906e3dbf44d2184157d2bed11a88b9769ff1d55a145dca64faa1074bb9eb77cb73eaa9aebfd2c39b9b2b393c21c3c6477004dcc401ba404ee76a9e9d54adfb06a28966931036121e81624a67e8019b92c549cb610c0e5b69acaa24a5f3ff435a17c05dff58097934a02aba20e347ac4171778b112a7eafb80d25d6d0898e5e4e15aac653ce2a679a1e7927bbca8e30e60f2af718f74417a3fc2576f4a03ea4aa9d8595f8e3cf2b082e0c2d304a43a80c3963d48fec2e7d09a8f83b1828a66b7755327ce1edfd2c4733e195b208c638a8c0055d651c1dfcd7726dcfa9ee8f8bb4c1b97a8241116e26bebaa1a2ebebd69e6759573d79884d2cc75b5520f178ad6dd2c3ff95bd3a660950a15916b47c41c0508bbea776f6876ba51b8c656c157016dc1e99568bfc260b2c5ff467b0b4cbb12e2afb741c872de19f09f89545cc5958723d340edeaed2ef10034b7c16c5f79866acab287e6c8ac1ac0620eab3369bbc12b6ee1c70fd625eca615db3bf53e18e52e8fccce56b63a9d9f9d9da42a4a1db30c276d97e67bf1cfc90e6b46bf87726669a92a8e75cf4f47449a7d2329879fc00c0fbec10c12348676b018b63be898f36e2ff9802ae3e88b26521fef647014e51b2ae0c65b2d6c32e11ddbc52a89d204fc5cf1367e3a7e22a3c760d0f1eeb23ddbed232813530584d66815b70299e514aab1d10fd0fe897d4c5f4f8c33a90e1c8ae5db9fc1bf03b8590a3329eaa5371dedfbd5ac75d48884179a91a5fee927c607028f84987e4018e7a8a8ff809af9f6c12f616518a3172fad40a2f4b4151c1c598af48f670e0242b5403506d15e4d627d9c56de8680ab7ccd7b50e2f6d27c3f6d96391912202ac2211662b5513540d192abfad075d666dbf893a337a61822278908aba30bd49a1c0e7a967b63c48ba1ce36e5d74e1831a2e71f8580b40c212fa39aeaec8b47f54b1d3896ccc3c7c47754355024a031bad381e0bd091e0ec4982f79ec8f1506ba48112b9e6abb57839b56f6e9e5ce57b9351a0464e0a4c3044001003d1fd2b59c801a9276d5fbabd1893ef4a261d3cfd06340c445440433585b46f77edacb5f1531cdf56daa454bd65b02eb87170d1bae6f5fb7d778db641c17e21402338b35f6f70bdbdcb8701a6217cfdf868b702a4bb390b7d23284e2b18fad8884fdabac67d23953df97130fdb520108e709d41dabfbe5d14a433e319cc496b35b77ab809aceb78a656eec60945dc7f8e5520945f03ae15ae8a6b061a1e9c5442909e6ce1214abe4b31489b0ead3947903cab1415080ddea7c0b4787f3ab430b52651e942cf4882a8bb63fe54c85fc720550c6e096faf63bfd816b1175f3f431d7f44171610d52414b9a92d07b41343cd5383651791dad78e71a05a2ca554f2a39806323ca0fdc5863ba4deda271a65bbf4244dd41c8f6e19bb81d355194246adb0c949e699efce6da5f6b98b285bb7d1085692ce1316e2677138ae1d0bb570082ae8c03b788b699061afdc259f8a2970cf2e2f1cc1ffef34de003271be5e3fb0f9fecf5058d00604038e56209f1f91b89e2d3739833cd6a9b3dfd9e3e871ab1bbb33006df228d830bfbcac47651326671cbe50368358de2ad280d2c1a4b84e1ecefdb5a105db56e673a856785938053b89c03337b32cce334bd77ff2c38de65820949fb02f77ba7fab1643299df8adeee06a2e41ae88754433e3f8aaacb7d53984f348426b2c8518515551c6f0f282c0d093a259537223dd144287f029fea18db74b8b6cae4ff9d00664d6daf93b4d8bc6b584f435992d8b16372aebd957749f7b831689633d9e2250431a0c3401ba6e9abee0b23d856c0526a8150824b79ce9606eef5bb6f7bd44022adae316ead807950e52710fd9be58ba252dece0d10c562841ec89ef89e8a0ac3dc76a3adb9d39d4df8ba619ba8424ff728d361ebada0edccb395f58467057eb8e059384ae608550add2115e1123e77a2c0513a8c1bfde0103d6e56069edd3d586ac72a90a5df3041780b952e1835a821b969c42ed9a21896b174c028abb71fba64b4e88c3358bfb2e8339745e93e00a7df70fbd2fed44560f229b97847730a07960732f146b5c4011dc6163d1c3ffad0213ce48c05ed88dba74bda63a85782bd4e1e29092e816fd262125a88a88efef9c892ebe22382e01645dd553978deffa19b6cb31f477333f7bfbd67359db341b2e2b22b9e3e88b5ecd7fe378c5fa5c0b023cd55e9a4aac2a16a9bafc07ba4e431d7fa5b79d80ea069a53bd9b697a9e297ec339af6373459b697ce14c59a67240614103a4973747ab55a3533cff81ac9dc611b1e06f6c65767859599ee52459b603dfe67430e68313261149352ec5ae8139de2a565a907d2f883aab9efb3c1a784cfc3ecd568cd7b02d8fd8280ac32037b15659c3ee9f0241daada9335efd19dbbdedefc57eccceddf6e50b34b33906ac319a0bcf823ef59b21538f9b4ff704cbca3f564be36adced992f4262bcaa2f62503f58b6ee74fa4f26626d02dcbfdcb0b72f99b863d2796eb25c069b11dd8e6a87cd973a962f04ee4f24eae6be6e147f6730cd9af991d73ba2b03504d45e9bc6b7412f6412bd89679d764fe88f23bd74ee69e6c595d91d4b13427612c9fcc6e17b8b2ae4d353506ce74ae9d71c7cc269a4347b252f023d4c237ffdc1854902158f11a71e858ac3882825b427f82137b618a156f3f0034e8526a5a6b1741e51667b2a0e280adcdd86eca15b104c3ab106baf0da3dc1fd82897949591a33146ff6f2a41f3fbc2ba1d3f6ec8808f4f3b6af3c1b34e8cbb1a7ad6bd2bfdc850b84ceb63fcc8de53b1feba5488167b7ff3a52ca4a5a8e5904325ce6fb4d1f55a7e8ffb030e03a9133120d9889144d411229e3cd5ce4f6fc82e2aa17559736b2cefa47f49f7d167b2105bc9b9adb281b660268bf65aafc0cd9f4d310c2f5d1e82b02aaa729676b49730cc904f8c67a80378afaaa2a381a7459e074fbfb7fa601ae24b1805e1abf427e0eee1435fda65b4782e54ea70f011a7ba8b4420c5012ea0a6b4c5344ba8a60fa9b38457709f71c5b77ec9489bbe68e57bdb3cfd31972a936d5766ad2c936f78358c7e3f6592266bb9d10dbb5b1712b2c6ce742841123aaed7e1dd176b6e32a1af39ba7810ef8b5b6bd64f8bf05708bdcdf42e627226b212ed4f56e9032c90fa3ae8392fa1604591c30c22a74ebc1246db14fee1974c10ebcd535e1f55f36a4657a58b7d5a149e5c2ea7022500922bf89089882874331209e6d60436cca7cc98d9a1c419dda6c70029d1431089bb5bcfe4b04f0f606d106a0d397b034325000abe0b26570f8e58038dc703902648e7640620e5abee5876bd10128227c2665f4327435c57e9c17744660d65dd871886be281ec398a7e9bc104393b74ea6f58d2fc2a2d158d8062b410504b93d9ae2da3219215fdd9fdbfa94f95eac5154b29fac5712bdd787590a3019ec02f88448971591bc11e8084a4758eb21c1e0b73d0dfabc48878abb1308f8574e6411bce14bc8dc7442a26de35a1e160e43accfbbdd2b5b74987a96fbf5d8db27101fd5a29fbe372712ec78711c4296951b5ccfc47f4488f6c7687dcb2e40e78e4ee4c419e7626cc514c3206e3928f61e52f750b54dcb36821ce6e56c70082c22b4a5cc07fa74385ec358b8ca505fe911e1aa385c54aa5f2ae821cfb249b6519a45f3273073ccc13eca3c9f2039da4e8caaf26ac7acbb1b5059b638638bfb94a57cb099340f678014e0339fc500e75fe143f438de261d1ec431ea98059c68337853ad1090281a1cc7bdef9edd152a82b7d6c34bf7528e0edab5f67bd97674bb0c963ab542862c4586fcf7e75553438ac3020a3ea9b4f1f22cf6eab294a2ba06171e66e36396404efc41a0fdf50f664d470043325c80eb18eefa795ea385b92d0cf81253adb2ca8cedc6d479cfda72f55fc00f71bd6e91eedd91eddcb7c093667a89d8f05cb38994d6e9c971a3101e090acebf90df223d453bae44cfe6d312691fc6c9e7b3821e5882ea4c1e9e86883ffb8bab5969558a16eadf73e263c47fee7d00cf8aeb5e13fa56caf5e32c5e967a352e19b4863fa3a47100127ba09c4e06fbbe079a421d17c9fc549597ceb659f8d89239017ebe6b89dd0e50ab1a45df1e34c17afe5382317ad517c72c50b518172ae1a6d93a15516d13f92038136a2da12a7b05de3b513ae2ced10"]}} \ No newline at end of file diff --git a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsELECTRA.json b/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsELECTRA.json deleted file mode 100644 index 3b2efa0572c..00000000000 --- a/data/beaconrestapi/src/integration-test/resources/tech/pegasys/teku/beaconrestapi/v3/newBlockContentsELECTRA.json +++ /dev/null @@ -1,419 +0,0 @@ -{ - "version": "electra", - "execution_payload_blinded": false, - "execution_payload_value": "18191970007912226669631394668547651071148255645822697200640823429642410377933", - "consensus_block_value": "61453013339935582189619461221462653003808078281923085412032520595023747176323", - "data": { - "block": { - "slot": "1", - "proposer_index": "4666673844721362956", - "parent_root": "0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef", - "state_root": "0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e", - "body": { - "randao_reveal": "0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71", - "eth1_data": { - "deposit_root": "0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f", - "deposit_count": "4658411424342975020", - "block_hash": "0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379" - }, - "graffiti": "0x0000000000000000000000000000000000000000000000000000000000000000", - "proposer_slashings": [ - { - "signed_header_1": { - "message": { - "slot": "4661716390776343276", - "proposer_index": "4600574485989226763", - "parent_root": "0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b", - "state_root": "0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb", - "body_root": "0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486" - }, - "signature": "0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483" - }, - "signed_header_2": { - "message": { - "slot": "4661716390776343276", - "proposer_index": "4600574485989226763", - "parent_root": "0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6", - "state_root": "0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26", - "body_root": "0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1" - }, - "signature": "0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1" - } - } - ], - "attester_slashings": [ - { - "attestation_1": { - "attesting_indices": [ - "4585702132744102314", - "4590659586689121994", - "4589007099177470570" - ], - "data": { - "slot": "4580744678799082634", - "index": "4579092195582398506", - "beacon_block_root": "0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c", - "source": { - "epoch": "533461240", - "root": "0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565" - }, - "target": { - "epoch": "538462976", - "root": "0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650" - } - }, - "signature": "0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc" - }, - "attestation_2": { - "attesting_indices": [ - "4585702132744102314", - "4590659586689121994", - "4589007099177470570" - ], - "data": { - "slot": "4620404293179370891", - "index": "4618751809962686763", - "beacon_block_root": "0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b", - "source": { - "epoch": "538078227", - "root": "0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb" - }, - "target": { - "epoch": "536923980", - "root": "0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5" - } - }, - "signature": "0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65" - } - } - ], - "attestations": [ - { - "aggregation_bits": "0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001", - "data": { - "slot": "4605531939934246443", - "index": "4610489389584298827", - "beacon_block_root": "0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b", - "source": { - "epoch": "529421377", - "root": "0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2" - }, - "target": { - "epoch": "529806126", - "root": "0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd" - } - }, - "signature": "0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc" - }, - { - "aggregation_bits": "0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101", - "data": { - "slot": "4544390030852162633", - "index": "4542737547635478505", - "beacon_block_root": "0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd", - "source": { - "epoch": "527690007", - "root": "0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8" - }, - "target": { - "epoch": "528074756", - "root": "0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8" - } - }, - "signature": "0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120" - }, - { - "aggregation_bits": "0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301", - "data": { - "slot": "4529517677607038185", - "index": "4574134745932346122", - "beacon_block_root": "0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947", - "source": { - "epoch": "532884117", - "root": "0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31" - }, - "target": { - "epoch": "531729870", - "root": "0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672" - } - }, - "signature": "0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683" - } - ], - "deposits": [ - { - "proof": [ - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c", - "0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c" - ], - "data": { - "pubkey": "0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d", - "withdrawal_credentials": "0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c", - "amount": "32000000000", - "signature": "0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb" - } - } - ], - "voluntary_exits": [ - { - "message": { - "epoch": "4562567354825622634", - "validator_index": "4564219838042306762" - }, - "signature": "0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e" - } - ], - "sync_aggregate": { - "sync_committee_bits": "0x01000000", - "sync_committee_signature": "0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206" - }, - "execution_payload": { - "parent_hash": "0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be", - "fee_recipient": "0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343", - "state_root": "0xbf886c3ec849316e3b187793c3a4398b6097768d06bd968a54e8d2652d2a75a9", - "receipts_root": "0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34", - "logs_bloom": "0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9f770a36a743c8a3abab61dc439ddc0604dd5015b1ed3835787d9565dee0f3e64b25de4c097defe3001f483a4b6feac22b992cada114bfc709d483b4d94f07bb0a1c4fb9e93ca3c31f4b9683753ba33ffd971777e301367f1edfe6809da491535c711a7877b4c97fd1a756136c412b4f3c4471ba439607333623558a63030f2cb6bc2ba885822672de14ea697d44fbcde134b6909208466be0b4c981658ba30f999c991aca746c3331766af1ee10cbe69624066708ae086999a0a3853eb777b3f9f0455cfd98a98c7719710515b97c596d2b662d353a90206e470c523d4374853", - "prev_randao": "0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d07874", - "block_number": "4491510546443434056", - "gas_limit": "4489858063226749928", - "gas_used": "4481595642848361992", - "timestamp": "4479943159631677864", - "extra_data": "0x58913d3ec8a62b95e52fb1ee60ebddf392af6e1db902dd5bc3f1eea7003130ff", - "base_fee_per_gas": "48712354854557871613352262057776104244427151172201944877932608112921551169417", - "block_hash": "0xcb571a3e876c6732a4c11cf3562059c2b8c16889ffb6d1b8d5f883591e767c3f", - "transactions": [ - "0xb736203ee72088", - "0xc7dab83ea972da", - "0xa198c43e69db1b", - "0x135fa13e28a157", - "0xed1cad3ee80999", - "0x60e3893ea8cfd4", - "0x39a1953e683816", - "0xac67723e28fe51" - ], - "withdrawals": [ - { - "index": "4864971916622804241", - "validator_index": "2164897", - "address": "0x09988f43d11dcf2aa7811c9997eb4119e8f153ce", - "amount": "4866624404134455665" - } - ], - "blob_gas_used": "4858361979461100433", - "excess_blob_gas": "4856709496244416305", - "deposit_receipts": [ - { - "pubkey": "0x98d6103215e3916a0cff3af6b6f29f22374a32d87d440a302e18a9e2daa80b32a2824798030f6a2e06ab612b07c41f74", - "withdrawal_credentials": "0x010000000000000000000000db034f43b15d672031628c76afcc23e92d134914", - "amount": "4855057017322699473", - "signature": "0xa8b4b8e92e67565ec430f2fdda94ed0f6f06d8cb302770191d614b795d194e4728c11e72162f25e04d0f7dda1dcd54da0d8a7c39e71e945873168ffa294da70dd1acbc1902a2fb1598267df5d277a0f95592967ea222ab0706571001c315eb2b", - "index": "4845142109432660113" - }, - { - "pubkey": "0xac60d2ec631e27b37f4f5541319b94c8cf82299d71ae4139039cbc1d0c30c71a075cb62166801ccd0a56f0bc29edcdae", - "withdrawal_credentials": "0x01000000000000000000000001464343f1f425aa1be85acd56de4c833a194738", - "amount": "4891411660974652178", - "signature": "0xb66c9d2c80f5a12930f0899b9ff3d1a6a37e0f9edb279ced767eca8ef0380227681b15bd3850a00a383491ed1d8e869310f10edea2b912278e1e2ec1cfaaba8c0981af2e40fd233a9fd2f67ec56540c66e062212ee2781593a4714914e15cb52", - "index": "4894716627408020434" - } - ], - "exits": [ - { - "source_address": "0x5d6ec4433175f5be08277b12261c89e3b0d65cac", - "validator_pubkey": "0xa83cbdf40e5c4bdb4e9802d94d765c70150d9926521b0ec4d273e788b83a9f304694e75d2e381ce631b24121ffecc9d4" - }, - { - "source_address": "0x96d1b2431158938de8efb094a1b6c64ac3df5962", - "validator_pubkey": "0xb679b4b686530827b2a201eb2b18454e9a5758d7257737b29bb215b9f354c2ff57e912b19d4a051556187aa24c97371b" - }, - { - "source_address": "0x83b0b843710cb448f2ac4969cd2db27dbddc5ad0", - "validator_pubkey": "0xa7f8aff7b912b6363efb810f2b661643624ab914b034e78d72397ef84fa04862dee94c9b2f46b872fe852f197f558fce" - }, - { - "source_address": "0xbc13a74351ef5117d2757feb48c8efe4cfe55786", - "validator_pubkey": "0x83b8c61b63de768821cbd82ee3c67c81bb848163d6af0186ffe1ca3936d283bb4cab886f3fbc7f6336fec3da8d542c76" - }, - { - "source_address": "0xa51dc242b07456952ea93a8886a01023c35b31c4", - "validator_pubkey": "0xa6c59055cc0bed5baf1a815f59d9d1cbd7aba1a4fb8d83de7310ad85640524318110a6a16f5bc141a81c34e103d9b7be" - } - ] - }, - "bls_to_execution_changes": [ - { - "message": { - "validator_index": "1816540", - "from_bls_pubkey": "0x990cf4f3bf6ede0aaef3010026465f98f381860535ce007b87879afbf2c955c13d07d7c2d91e22fddd8ef5531f8bd22c", - "to_execution_address": "0x2b0599420f867177e37d0db84f5ea0beef702ac2" - }, - "signature": "0xac8ebc3beb6cfc97c27f286e0d2e582707cbcb972d0898a41831b2d1393a684ce54ce54dc9128dc3988930ae4d92b4ed0a51b2bf639d8fd8e62e40ceac222362d9bb67f9d1b8419f3123dac1bb2e4e0cccb5c7c0985c83bd0501ed610935aa96" - }, - { - "message": { - "validator_index": "2892795", - "from_bls_pubkey": "0xa0695f8f6f65e3d8401e144eb382eaed73f9ec56be6de71dadb917af79a08ff7b74967dd4f4766ed77f7bc2fc01cfa38", - "to_execution_address": "0x51478d424f1d3001cd03dc0ef66fc958fb7628e6" - }, - "signature": "0xa18c2c70d886e11a592393a7bef6fb3a515100e1436763854eb96fca9c031a959e4c105be367a10ea87c3d1a8bce821303470a1d6053cd89139bbd86fd7bbdd3e377b331884bedb0f9b10eafcb3272561fc5d71b96b219d7fe3aacd6e1558c97" - }, - { - "message": { - "validator_index": "2664029", - "from_bls_pubkey": "0x97e268878248299c9e4d2c86957935d6cddb83900dbb6d4e52a935bcda58978f6fd33e0dc891cea14da0feafd5173762", - "to_execution_address": "0xad6f0e43909dff15ba42fc53c6ad05b972343e5a" - }, - "signature": "0xa2010187045aa6d63130c7ff23464438af57c3e42eaa90823205936a94c47713b68bd93d3b6837947e277ece630a6d200d428979548f340f6f71ca33e8731e059a8c20f75d71d36caebbbf6fde28f37a919353dedb7b7c7e4dbcda553e5bbee5" - }, - { - "message": { - "validator_index": "740284", - "from_bls_pubkey": "0x8aec1b1f595063af33939f3c3322ad38d2e1de1b11fbc8a9d04235dc7fc9792e1c88e51452d337855d254a71f42816e8", - "to_execution_address": "0xd3b10243d034be9fa5c8caaa6ebf2e537e3a3c7e" - }, - "signature": "0xa0ba14bb9ce5877d9f9d607da9b2fd2d629a1de42d6d3beb5a8f4c1661aa1d6863e01de14c548be8a9df222efc6373be1290581da81c76d71bfada1d07481d7b7de94290efd640aadca41d6b4d4f81091f4c459b454bd6e333eaa35c60faacf5" - }, - { - "message": { - "validator_index": "355536", - "from_bls_pubkey": "0xa912f4ad989d87e777e45af7c265b430daf0b39345987506d4158cdee406847f294fc7745154eb52abf0934a5e1866ee", - "to_execution_address": "0xf51e0c420e9d60ece0c4bbc926328df885b91272" - }, - "signature": "0xa7f77c7fc98b1c3a364dcac68b5cff112f7745e6dd41918ba56a6fa6945507e0ce245334e22d4581f49bda913baa2a6b1176b44d52168151b3aff9a625dcdebad1899747c42c4a43cf31f49124fc0d4543e4485592c243c5300b79214398b770" - }, - { - "message": { - "validator_index": "1275809", - "from_bls_pubkey": "0xa77e90361be2a534a386cb689d6d763a98bea5f23f325b553a762648668e4adb4991fb5f41ad7ece1578f082a5c01b5e", - "to_execution_address": "0x1c6100424e341f76cb4a8a20cd43b69291bf1096" - }, - "signature": "0xad188010cb0db88e067c2699030353a1c215ae9adf083916ee2069a805e0f2cd00c76db9250a859106dbbff4430b4dd114d6293c4b3c2e9cfd31f07949f04e53f63423a08b56d7247772d07959d5d92b17bd8c7c0b294b71d3db903d56509177" - }, - { - "message": { - "validator_index": "891060", - "from_bls_pubkey": "0xb4582d56f8ad9dcc77eb5413558e63a6b562e42534c579a85384e7d7d6ff8974ff933d05a444c1d2784945f4cd1c952e", - "to_execution_address": "0xdbabc5418e28d8265fe892d2129c8395d0dd064a" - }, - "signature": "0xa7f07c5a20159b029b2dac119315a0d439c541e63b0d1f6d377fd2867e5559d6b6302eb609d5796fab97cbca121ddf400c840b9ffa60dbcd89c6d441f84aff2cca1f68fd9e258a969b0d511ad1d90c0c783dde3c093ee8cd56cf6f70a61fd77a" - }, - { - "message": { - "validator_index": "2123298", - "from_bls_pubkey": "0xa5849044acc283563bd9b40fe9b01a8c079093829fc3837cddf20a8f9c13e59629251481406f415c8e2df65285ddb41f", - "to_execution_address": "0x9ecb7542cf4bad14a20f79bc45931b8d1483242e" - }, - "signature": "0x81df97c3071aac41af79494001a1c4404b5121776a71d6cbe3b8eef000e803f59edd2fff33331d2ea037faa919ddd6a115e09bead88d7c8f23368628f306e3a244f2ce0a54e4472d29e4b79eced6da3e5ab40177e96fa0d94d97f5e07d2e6e95" - }, - { - "message": { - "validator_index": "1738549", - "from_bls_pubkey": "0x9815cccaf23783a4b1bfa265d2d620e70c76b50b32e1975b909ddc3749fcca44d97e3e7e717a1f2979c3d1e4a70c1ccc", - "to_execution_address": "0x5d163b420f4066c536ad816e89ebe88f53a11ae2" - }, - "signature": "0xa4fb80ffdea501d608a5e79ed05fd3ff67d39963afeff1b2e0be94811c3497f8b615af4a16e438e23e8cc6c34376a514169ba117403d86a2ebeb85ca0bd638e63ca982ee45c8350d726f228ac03eb8fb584fcc56e8d3877a3756cbb06a7aad43" - }, - { - "message": { - "validator_index": "2814804", - "from_bls_pubkey": "0xa6e3b4975aa42a8e0f86af69da109dfc42eae539bc7bce0be20f733b1fb15107bc42eda74c8788c1feb0aae542a6fd17", - "to_execution_address": "0x83582f424fd7244f213350c530fd112a5fa71806" - }, - "signature": "0x8d5a3a8aded5a58f952ac7bae812991f1b285e1704e87ef9fd8a743aeca8dd30ed7710a1b6c31a1860768704e6ac709316d5e7002605470c7fcf4b2c691f8a897c900cc60e9618daf83af929b7e8474e7f71bd996427c256691c9b90581b1264" - }, - { - "message": { - "validator_index": "2274073", - "from_bls_pubkey": "0x8d2a58c4d8939845fbbdb04c560d5eb57cca82d7dfed86580867df9faffd4bf8139bffdd1dc92555e6325e18d57afaa3", - "to_execution_address": "0xa5c538418d3fc79b5d2f41e4e96f70cf6626eff9" - }, - "signature": "0xaead124a78a24d0bf0a4a7d20c8c4f34e92899d925eb47750d683c474093f4d5a5af0ab36598838b149c0c348bab313e0079198921f7df6009c7e02db76b077b2541c12b71c70cc93b80ee4e150b2ad10ec6ecf6086bb8f70e9b49e4f708946c" - }, - { - "message": { - "validator_index": "350328", - "from_bls_pubkey": "0xa5062aabefffe7dc8d1d8d47651f86ade1d5ae1e656cc53bdea561703a90e7d961763550042f9134880b9b7e8270e3cc", - "to_execution_address": "0xcb072d41cdd6852547b50f3b90819969722ced1d" - }, - "signature": "0xb5f57267f4332f951dd6ddc29fb6ff407e92bc8c68d439d69b7c16be009e4ad7bc565d8dc5c48cbce29809340c2888140981fe9e575a3eb126255ad5dd4ec229eacdc544257ccb5633b06b1ac3cf845bf91030b5eaf67fb915afeccc7f79bcd6" - }, - { - "message": { - "validator_index": "2965580", - "from_bls_pubkey": "0x97b55e1024410802342165e019ac207ab51084599c14747f2234a1cbda282926c4897fd5923adf264dd2d8309b960da9", - "to_execution_address": "0x2730ae410e57553a34f42f8060bfd5c9e9e90292" - }, - "signature": "0xad1c182d5c0751fe063f7288a2217dde6a86066bb31d539fad2564c13fb8b4935299b02fda5567b1188554c98d6818511876e25d09ca0278b541e759542a5c0b7cf393f9a3d986a4af2687077a115d312c2bc78a7bf7c95308feddddd0e4a998" - }, - { - "message": { - "validator_index": "1197817", - "from_bls_pubkey": "0x8238eb67219c0c314c0b387a1300ebe7ee0b3bfde764c14e90d42e82197100fedb6950f6db432cee0e766cfd35ff22c7", - "to_execution_address": "0x4d72a2414eee13c41e7afed607d1fe63f5ef00b6" - }, - "signature": "0xb3e53a1e347fb5b508927c1a938354500ef34bd89843132609e5801d8831886b94d9c5100ccf9be9dd1ab272298319f306a7e2d95a304df4b42d08006e2273e3d30e054eb0cfb4cb143c70a9b24ac213ca467f0cb0b089fc3e500339d288bc3d" - }, - { - "message": { - "validator_index": "813069", - "from_bls_pubkey": "0xaafd198509805b36458bfa1c0202ea15976ab05f75100f8ed811fb700b4d657531e364c12a87d345f4799c43e2bb5ae6", - "to_execution_address": "0x0cbd67418de2cc74b31707894c29cc66340ef769" - }, - "signature": "0xa6c82d3cefe88ef29dd3396af000eaeaedd8317fd147a1ba13051e010fba5f612665b21f362d4c4f0d91536cd84400d517af6af033a6896d89038c61a787e2d790c173697c8628bc71c91a0f5e7c883d898836a4f6069bd0022fa89642b8deff" - }, - { - "message": { - "validator_index": "2981201", - "from_bls_pubkey": "0x99b20a6a3e75af8e62e7f3f5143a149ab8e4ff041b0bf44e70174c19184e0ad2d612a3cd648ac30b428469bde0d1cea2", - "to_execution_address": "0x7c0e7f46d64d29bb09077be5c681fd8ec96ed2ce" - }, - "signature": "0xa0fd1938a1fd006793fb69482996de28fa8269f16aa5fb33fe4ac6047653d5123500b8606f67abb026b57f8a46e40982054d914727f04e16bbb647a214842433dfda136d4e2987131e03549764c8e23b93359e9e6ea736b3e8eb8c4836435f19" - } - ], - "blob_kzg_commitments": [ - "0x02f65546365f449dbedb4d15903f8d2af483cbcc493fa0a9a6d1d982e0a353b81d20fe781245e513126d1f941ef3f17d", - "0xc8926746567ca6cede12189315a54fc3e17ace1626e5257b1d4e0f2a51812d1856c1ab5c2322444a3c24568fc99bc6ca" - ] - } - }, - "kzg_proofs": [ - "0x4e7a3e46b68dc1b093e7eac2de62df5e0d90c714236243925dd6e7a34a2731e37b9e16f4fcc866cb2fce2b453b128117", - "0x15175046d6aa23e2b31eb54063c8a1f7fa86ca5eff07c963d4521d4bbb040b43b43fc4d70da6c50159856240e5ba5564" - ], - "blobs": [ - "0xdbb36146f6c78513d4557fbee82d6490e87dcda8dcad4e354acf52f22ce2e4a2ede071bb1d832438843c993b90632ab1a10bb523f89d9469bd50be3de50f45bd2773bfbe661215c62910151d0c483c50ec40fbb55aed6247e023e620dd668556b349bc91496072dc17f97aa14ac2b854ede1878eeba037d7901f5f80c31a33014d2315794381b753539bb48edc1c54019c779325dd033b359e1d0f5a2dd9f030c97c4ef1567071321959155779569b724396a3af21ac002c4de5663912a2ceed8bbfae8d87dbbbda5765381d7b0221d704745950ef8eb42fd8e5fe8f4f30944c0dc0cf5f5f00db782fa69e4d60b9357f51cf0c3742252635eac020ee22f2bfc15d8c71776b988433796736b84a190e3472256d5459a0631c24a5073eb2913c9b9c8a68a3bcd5a95e3edd366f7f5678a00339a345382ed7ea9a78e1f3840d0fab80e7c01d686fd4793e76f933e95b83f772e8ac66254e18dd56d3da9b07617f73d254713607be68136bfebae5967bb96f8554edba2b35e36cd4c9bf4e0abd7df3f117376136e85ec26c916e3939afd09bd3562b809a42e8c38487fb5c405046884e687544181f45481b5d0cfda768f223464878cd858465b84cb900efc39f8537ee1d3288d0ea9c950734064c5cf2fce49f17b994424cbc5c03c864718e6f7f1bebaf892d0887e5daf3efcb65f45f6005f2afcbbaeed2745ff7e23762023bfa98f38234286d4fe50855a4894ab20e792125af82bfe7ed61f568da637c623df17cc787f7cc303c0423d8a089ddfcb5894b766cf19b50d3d6cb0bca1b4d570a4f1ebe291a69866ec98eda43e40fdc07b73df24ec21290ee208d8793a6f66cb632263f8d3b0029cdcd1bef9c678181e1a6c6ff7391abd3c4b7d5fb280e43928f81af4a1e21bfd6b0b5d75cdfe018bc0ba52bd3a585f787e9d41476a77d309e65c704f06e582bcda003e29ca43150848b6ff5faa1a18ae10266b77e4158d0c662a761d765c678541fd65bdffeda5372820c5fd4af874a56de8a288af952a928745fc3b7bc7db73486e23f865be8a0459f4b681487a950229511a7882c1bcc44422002dfce76bd3af33e67a935647f601ed55f42861ea3c4c0a2ab58ec53717cb93b23b0b702c9b844cd3f9198c092475ae8923a399a7480bf8f9f4e2af4469b226c1221c113da012659c13e72ddbc26ec2bb3ab2f3d71dc0875f0b4b14f8a4bcbb5b63a22b5a9f02bbbfee1b596bb4a5c316d99233adb2392222146f254ab9d3ea5a5990d4cf862019348634e043ca5628e9cdc6f89dfe5427c54b49ac97c860dec8df00f7ca4b7a36a37dde0d5994eb7ad849fd62498757944f3230390e45c279ed084bec89d55af541b1e5f7424ff72b977e19785a66a9f027aac673f965dc291a6b0b54e4f25bc573f296ad38e95ff405ff7d75e2e1ece7730dcf298a128d4a39d60e72d9014ca3827b3841c2294987cac0856ce3d328353883398a385411ae32f97397f8093bd766ea9159e66a15b505090727d5d08651a25a5be780892b2e3b4e974effa18ec8837a83aa83906e9707dde856c184817711b1cbb0b4be543bdf30183596c9bb8b22a836a41b231944b21958662c85b1f3dcaf31a766f6bb6752a1cfdf9f31a40fedbc1e7dcc9342494322f003bd9f3d84dcc7cbe92af387cda758d0a16a8151630101d037d7947249a2e75598c02817a7fe57cc3f1f0c4690a23378f2fbc1211c6c0042eebde42c4c760086b60acbe298a7aaa3f6fba6b1e329a16aa200f1a2753ef1addef082449ecd9dc65f9d02320ef1633cbf6eeeefc3c40b683b8583a5aca50b53ab43c8f0d344fb709dd3270dfb5c039db3dd8f0b37ce3ba6cb3c704b58a9961a9b7e14812f072b72ba0ab2976eb5d4fec7edb7a568905574c28620b573ebb5a1603c5bc1e8fbbc98c2cad14d4153cff7c7fa97804dc9ad65ec5e36a91deb5151c840f76716af732007bccbc2e0dde44d86ada3a23e6a6154476263e136fb5b7f4b91eaa00487e0ae2fb51f175ed9010b04badf425a771075e4d5b272642169c3c1e6aae4ab44bbcdd275946d811edc7872e123ee602f22f7fca7acbfad791d284f3c27bb695cf8804f3fd92574c54c2ee363d948c5a6e29e9d85c5405ab469bb5638730849bcc02341c746f6dcbb2ce8a31c0d6082a43229b1edaed697b85ef4018d58f4d6a2e90cc92a9bebf5f44aaf26c8fca1aac355bf076314879eb4ef724b6627f16a85befb76c4221e513e48c58e36595eac6b6a98eb2ad1b3d7f3493bee76a88ed492aaebf2652803948789d04ffb34631c300f8a4f6309e1451e0d56bdefe5a1cc546b133293385c2c461789f83a34cf18f08c47955dc0e610ddf84fe752946e80591afe0ca4220247370af4c66c2fabe94d19e497a832e4207689afe6387c3e24dacb5564c1503a5b2c446959e950a9954d1ad7582fdc16278c7c7bc1c5cebc95bc1a27f85ea2643783036223aac175163b62a025ead11d53dbfeb3458b6fbf87e3ca9853fc944d8ac1feb463dca21846f14e36451cd6bffa3aa9fc060ead6687823d20975ac5d4e7bcbc75b1a6a0d23632db9a37222a1981372ad99958383404b7978aaf29a33e480809de8c922238bdc4d22c33bbe86b4b163942ccbd1abf55e08ccd91157cd81110c369fcd778e2e82f9a7848c5d76e044d6053b73621cfd94d551048891f61c02f9d025a837dfccf2716775b5c5cb4ff11368ceb608480b34715e07842fbe2dab2099b193d48b3b54f9924c663969189e6a58faeb92c6e4ca549ff79341f83215393871811268bec28ff09999c0337f1568236f11efd0be6c7f94370dbebb46817545c7abfc34f022d158c4d787935432384bdc394bc42fe98b11def0eee2c2bf02cc5445540e3b180739586b0cf660acd3072311b2a4afe18ebc6c86c88cf50625cfd30e91fbe0217914a660244ee1552ec258e5be95a8de2493d6260b9ce53b01c1c1dd1b62942083ffb06a328e13dcdb9b99dd476317c63a63d6f1273927dfa70bda5da291a0c600246d07ff31d35cb2e4aa4848fcbb55455295675d4fc00fcc8a60974236a2b42a404e7dc1b44563caa46d2de8f1d982ce55a120660d1d56ad0f3c04d4e6cf441824268cdc18971ddc15218739f888e179ba331e616614291fdee03dc399d246b73853ebb361c6dd612675d7266c9575e3a0c76c5090fc5e45300b8ecc2c2e2e31f33eefc819823718f96cf819cbf08e067a0013a887cffc90b149ec60de397563642cf0fdcd144d6c6611581537b2932c91e086ec8b5f15c5025832a8d3a83647ac564c798f611d2977f30755f6a6d9bd7f4f234d9d9baa4f384991e294a23fe2815f1f2273f0e43cea7e4a10f372f1b0b4f0577c06e4c20e5b8291628d6b1ca2ad0d091a120be4bfb21c889f3a4105bff4498dc4d16e24aee88427788e9de1cf8d5b9de809e91b7e7725d61d7f042df6d639f77d4cbc68265089a76c4bf0b32193d557467becd42511a479989847dabc47becf968b1b5a8a77e9a11edc91504dc5482c968beeffe410e80f02fe8dbddb050bb4d4e53ecd3b355bf2e33d0f18e53bc10e26e39bc4af37e8466a5144c7ad25f01dd375e068d5d8fa8b17cfcc5db7a4d80c4031e1cea2fd744f6ed14026288e9033889a760d93a025cc1fd50efe5dc254d2eea4672df40b3b7371e5d365067c68674677f8cec642245ec9f8917caae915b3197321d3adc09578f098680d09fda3a2c674b2b2f466a5f989c4b32d218455944556e60dfdb031be2d17b563df77c5f3c3bb2aa3832908869d292b2cc491344fd90b20843a207f3887347b3707209cfb16bc154c8e9c766cf7fa501d05209a663dd2868bee8cf4ba02e6d8cfa7858a920ca95ad04243d022b171f805d3d7612a90f2edeafd22213700c728662f041d60e8fb278c6c966439f6a9d7b15f71daa1f0322059db458fdb8c9572cb4451f05e21eb175537837d756d23806f663a94d79360cc4a120a773c85b16b1b71f5e786ebf964eaf7eab9c2c542e72274957f60f1ce81e1bfbe40f08fa3079fbfcab4170f692e87c92038055dcf7ef0ee765b3855d0bc0d975ed749fd9498407480ab3de3c1ccc81c6d9016ee75dd0262e9734d0e3f0336dd1dcb1ac4d27f33e31ceb0a7f4d365be88e9f77d246f72ce089b05542f910f564e243a7264f6eb16674d3fed89b731d08332181ab34e66a2609f3c6285e14c1ecc7c335062335ef69b4cb959aff23b07b821a1d3699428385afc50c10d5fa8d6f9bb9e48e0626ab82db589be21012a0a1921453a3b00b4b80d459551945cdbce561e3afb0d703f79c3f84380e29d89e40f126310f19204e7a65d68f089c1ceaaccf80cfcd17831f4f0592f8e5e9947b2c1fd2db965946dec865f675df4c7e6d4dd055db216e5ad385aa073e39b4afcd9811aace1d57c5caf669f7ba3bc9189c9c027d5ba825e3ebda7d02e2072fad83b7aed09709e18c42111ad8b9990c40204b390ab1b4ab49791322def7d6c713c8155540981a25eea262b7e63cae1322f8c6fa7c8000114ef473102befb22b60ee4556e6920f38e43416909f38035e83edf8010308b18d94b7d314f58daece095ac9748d14f192ec1ade8cdb93e4748b98cdbb8a0bb7fb85229bcc597d63669791d96998feb8906ff1daa44e57735ca13faacda540f857c007222d6f621670c3efa3628cbe89f1b8e3b26556de66245826e68b3b64e444174936fe79bda5a9b0a4c77f6bfccf5a8037fc7218e39c5e6b1d21810b64738824c3ec5b740958962ef96955dfc978d20191dd00f17f1a9ecb65d8eeff450d8e68ea4032e4d5f2fd0d06016b339cdd46a6710c59318089fdba38eb4450b787e2fabb71c901d5416a855109c01f9b26063cb320592e9a8e99f986d8aa736d38b9cae37c362e185aaba5043743630eab46bf018af49e6c8e1302c15f7ae3c216666f3a93e5b9f47254deaa575a2ef4469eb29552ae4a59980f56bad6c3ad390ae688fce317fb4a233c92ad055c5d0ab48b07b14021089737b7daf9f6c8d7cdce55b05da65db4292ffebbfd821783d4b3ec96a0d67b0b15dc3945091d7e90414b77f858db21cb07d77aa29d8c4ac3683f85294427fd840966449795f41233cdd030fee0db1d4e0ec7519dfcbc2c7d0d017f105f737d8f45208fab72546119dd7e0c89d169e1cb2c5a6ce9561fe4c5f112e64d330ae56ea81e53d2d39d7f3ec7f0b8bb44a451f9f1e1758d8bea0eb38a5d2a4d29f6b21e4c87cc7d4b4a31b1a26e86d42df225a862095c4e1af63c755ff79f2b8694753045c6bbfe637472d6aa15ee7d5bb0680d4936d3b33b6073424e97fd90725555fa0f0de750e30ef27bd8cf07af4332fa57b601d31c4508aa109481e017d4878c1505c897f70b2f92ba9a7d121c3b6f0e97b07d15e4caf44f2021574946bb42e62c6fe519ea6c46e9a8d967f036fa87acf046039a943279c15604d61b166834451b1ce33f901269c2ddaceba7bf912a302886b1242ea7f2d668f7e89f0d275d1f5a8398ddce2ac59cc26f54e1e430bdc974079bba79a14057d5555b00e88882febfdb9c4c246e4129b61d046cf45e22fba713dd286e76b180af0826d0cdb675ff8ebaf9ead4dfcfcffe1f5dbb9b8adb1ea494807ca49df781393191811de51d54d315949827b7622a8df75b63b81049dc224f128bdca1e0ff4e9c55edc32696c211c4563ab28399888e6c729fb81cc9b7cb1e556a91edf2939f4880cf2423942446d3b23bb6de2c6bcf2d01cd8b0481809a2e4ef59de5cb4ab98919489370bbe631517b068d3bd70bee09c0ef2202afe19567304bf1031423e391785b5a95778afa5e7b8e24a9f9d15c2c802b816c7cd5d58f594f53e42294c76cbb7055f1742618aa66c78b9683dd3408f74b5cd26a2adad3a8293975f906efb412d51812d7e493a4ed2a8fecb482d8daab3e4d98a00631b4f3c3a9be0c540a8e6f38aa33d9829616d035650e2ac465f0b49590fc8b5f6d91434d32f177500daea92ec9ef9bd046e3c1e96a9341e1f813ad0757a28d816d50d3baeaae903e197f62b0c75d8134f81ad74af0cb53c5bed52ceda5069e8cf1d079418ac0845737a4c199f2249f29a9b4f5e314636494d7901ec6c7802bf481304bdc8ba89c48e36aa6298a437cbbd1887c3fb4afee24095b77b54cb0c0ffa513c0cc62c6fab065f310854a8c860d8267e87162862d8041483afe9a7a5d4b6a6f9f13bffb32a5fe195534841c6ad3d063c18d789ec766e9ab2e8c0fface45378d3cf8e105e330e06a20a3c44cbbd899ae832d31dc19ca06425d6eae00bdb2f742588702fd87ccf8aa7ba20ede317a8c9a88edd41171512f6a3b566310962a8f99190ec2ab29eb8dbfb7e5a2650a298aa32deef60965d74ade6619d2eafc98a05b1fa2b0b01871a6fea485583901eb54939857c1ec2ee9258ff731b71addbca446a198e19be83745328b7c855ec0db8d1d81cf5152454cd490b83ed818d6a950d8f40c907a038973d8172c773d88a5038d59052694c074e3400c8084ee3e474cf28e5dd382b0a91b6adfd97a1b510c515c0928982f0a0e5d10db5fa5779ed238cec8af63d12b4ab3b21d2a769da165cbd32d706b4b2276999bafe45ea6165907fc43676e6b7915b68218ac0fd5dc19c1067e9368b25641e2f90d3d406aeb409f64c6b37af6f127f046e74f679ad82708ca80500ad59eb5e46e273d041d00bed48acc005ed82391984e69e75e34239fdc7d3ce839a4f04cf4e746d9e196e060af84dd8240b187d73bcae3ac72917d280fc5e99f1b57ca351defb5ac6c4cad840067e1afb161b37276cd17ba2193b57b8383fb40eb6dddd306fe647405180cdf491d58c4cc0e2b4e7fdd9f92e62d3ae5febdff96e8833f3ca65a5592da9011f8f3e7533f6dd6ab4c82d60b97bf5f79c94635f6f453c2c5bbc0e79d8e7811f1aad4fa87637325d0c1ca318192473f561c8fd6baae7a06384e3e93e791eacf23a605f690b718e239f5bdf69c2f5ea5cbe7385b0b9ed30cd9284c4c69ea6aa9a1ee0a33da8754dc02a6a5e0f37e60331ec9e0b180a02b3359304fabbe8aa383b85b49202862503610824a83d473f1b7d673bd0a8e16e2a3a981a6b56f877e650919cd24dd9aecb609b05e78de6552341c294ea1386435fc4de83d67a39d86a9342660c683c754382248b8df98934b04eb47599dc4759fbfba69f0eb41a8d7ebc8d6f00b0644a4a492d65bbc719aa4ea14ba3aa50434422a07bbbab2be2b9f951a5248563fb09a32a5db56f876002d04ec8382fb52146434b9c7bdc4ddcc2c28c4141129436f60d4724c6a19a9ad54e3f838e3417c89fcd8ba2d110a0bd592a9b16c2d9ec8275b0a011185f0fe6d54562283703b0d8d50f197be656645b5d6d4c3fe5d7da743128c42870ece106fe6993ba12d28861c9f37fc0378d7327db1ef494238323973237999ff0005e9c98efa03e3eb93efb8dd4209b552ffad64e067cb036d1abd2a4704de3dd4c6a6f17326413079b22de5848d844361c40044059adce15ee6941f65ca98f937a27745c7b1c3821ea338ae6cd90d4f52f10a1a176404cf08064bcdb84714619e2eeb84bd8fd3646e35111eb9828631cafec68962650a95345cdaf2051d259596aecb531782b87b6309e3c8d6939272e570fa581a0e604f01b29b14dd160d2af7b275ba39c1d0face24464087aca4528b7262ef77e0b8ecb9a492f7ef28a9a8d80820a4d65eee2c1758fba0fd0d5644909858de6b8550affb07912876cf2e0c607ca462252582ee450cd0040b5ee46da8f6be57de96cfb22cf6cc76a1881947593a2129abf4703167548f0e5bf38e282af0fdaf7f413a5e4209137322588dbca1da398f93e24e7d375c0763c0bc3584a06e01e12820d6f5d775b3f7bc865fc6625dd61299992169228e332b91150cc42d686e0accf27e089502a69dec0f2b301ca5fd917dbfb924910a8d9513196adebddfbceef4127401e4152645996f52bb7e37d0d1126129ef884a5c51fa46c7fd08c97d72d9c7429bf8533590dbc1949b164fb7f9d2a6dba19907a814f18ab100667ad720fadb136426870867d166e8fac5879316095458221cc3061739f923f86c962ea652164d4287cc5f75829ca1740fb0159889d2cbf0c086299955d2f8ad699704571a7929ebf13e30a8d4c0879a308973d3facc029d6eb86f58c89f521a9e6650a81468a56b409ad2b4902d606f0c80597fad99a64e701b351cde7bc0ed54a9f8b47118dea2e6901f9076e0a1eee5a8513905f82d3ef458d42d8bd69108c4ada2b93eeccdc4d8cd7ff661617982ea87d70071e22339daa989d757494603bf21bc99669b21d7c1dcfae77e98008b8c70f7b8f3761c21d27b7aed5c4c46a739fd805a424cbc388a72bd258e8692e0ad85a7aa3c38ee6dca2b284450de19749a4ac2a6b9fd62162ed064b839116f489aa4ff0250298ca5ae2f7d33a171221cddb65d4cf3e4f1f6e3399a6a79557700d2c41450bf852be775497519a5b075068513a9bd95a0702d93bc2e4b0732a738a496830872183665819d1ad7d3ead6cc5f349090976354699abbf42de4fb1496996aa502fe72e8e54ac63252143df5bd17d3c6fe9f794129e6000a28ec8eacb1aeab01fa8f87613f633f80f318c7045c96623b66f7c6a740574922187b2c8f975e76cd11646757e3c0c1ed26cc7f81df36fceaca0726a8567987a71a21dafd477d16324ace279b505e6b52decc7a6ab0c8d051516dc4ca67895632136732f838e275a20ed32f149e28ccbe0ffbb08cf061c7f6c396e13042cc57f229a3d945d1e2c5bea94f70e1f53d326c33149e84ef0134fc10f690160a35741b47dd0875ee9ced24cab248dd147ccf78c65f45f6b503d032c9c061abb95b3c529ec3e0746211871307451ad7cb5e9294ca6b6f1b7d605ee7a5400e8398c3e05e1eaf76d66f0e75f45701cc73812ecca344d9323034c2a586feb39421c9717c2301b8cd6b4fea5bbb96fdc0ccaf7efe25bd381a76fe65aa3e52be5b70bdd3abef41da2df0af0d87830463039362fc69e0c1f6787eb2ce51f7c268443cbef5a9d01d2a8ba12f4e0500c6f6a5b2bd8c38e56358c12c5947da5796b2cb3667096e6a9818ef36e318062064329e3075bb5c91b887ed69b235d011b6b19d56263c833bfac7156cd56cc1c049ec98435379808457b04dd2b13c6b5532433cdec1dd94ea4d70b16e80a5746547908b76b62a9de7e02b4dbffc2e7d26bc4b9bc1ced6e861e1d305d656153eb510e803fa0e06179072be00ef6dd15c442988d49d396f420ac8c3cd713c70e1edbe73782762c275ae32e38fb3936d767af4ed54f8c264e76b5ab49c290906bf914b521ba0cb9514c1cb28cb0430346c7abb9b25057c51c1d9b3612e5a0acd87561abaf15ef7836374e99d8106bc6ff0d3c05932c65062d58f3084569a813c78c16c01328ca7fc93f3bd4d6796e186c33969aaafef785d8cdbf694829521f723edec362ade30cacaed7ccc9e3cfdce7f06c06a02a004f984796ba2ffb594451cac244a31dafe4f313de0d3376aa32e1ddff302cba35881ea51bb3476adf560fb9a05c464560d1980aea5ef34491a905664d526f6b0accf892154424e86568d9ebb630f35336eb6dce00d3aea81dc235b1f5c26f5bd8b901e6b6a34edcc3c0499e277510262c8ed24975d28c7411873753921ce7f5c54da924174fbff8b1eaf29975bd24bf4d437011d5e67dd7f31429219e3d781c9bc132b19ad33434be9b13d0591f5331de08e5893dcc7b6f33e43d1b504eb15364a2ad8d294597bfc7fc549487dc25ac54d8dff102a3b4c73889889420a2db0a7da182aa26c0e8fc6336204a5e6fc508f12dcbc859ddf2f750e276ceb1f62daaf1bbe9c43bd6cf5ea4eba4a3f79cd25f136f1fc360d1d3c4404b9ad6e929d232611033936d16cea958e84ba7846fad055591ecf7e57d1590a4ff062c5548225019351c45038591b46a296ca6b41af7b0f2cef052fdd8c2f380b09787f7c13c165ce5af8174453bd9caf4f6ce6c2d6e941f4d7949b04dfcbf7056cee1cc314f505c02d7db08ee73fd3ced27fde060286d20b3485d8b5b29c4842114a7909fbeff2be9d9f246fb20dc88b11a599abe1d1d95f941c29dcba451b76b25851ce6c3f18eef8fdfa88140d9067a633a9384e3d08f25d0ef7ecee27448ecdbc06ba523fb0ce0ea5c83af7d2be587f636a93e0a5ee58b8d2e95a17ac632777e60715782a24ab86577e2fa1b9234458f0f1891a01213ccc7cdf1e6aa2f314fd63ae4fbb6ef42b86f9adcf9a4d8e8905c229491ff3b7db51aa9935374981db58b4f87ace2678c4a75aeacca2c248788e636ee9fb53cc876f66ea21a5ccbb39720540cb87db7c4241d8b097b2625bd5175329e07fe52525e8190e677d51679a3188ed1cc2ee1145fa9101e8122958599e74bc8aa5bb7caf99e2a81009dd74a162443e6dcd5115c0b22ae83a80fb6130813192a88a195bb44bec8e28fdab77a66696575f6f69697cb94fbc4c5f335f7cdc0e4dddf8c4e3d3cda4184377ca7033b7c049b7734116925280cea10169cbd0b3c4e0327e127f4366e544f309ff7187383b21e1cc71faf58dcf8a01fab268b5ec1fd692152cb74e906b5453d95ae9135caad26b44571ae66d9ad8f680cb242819391a4f5f545ad20a3bdb7fc7007fff12a8a085bb810e7e5d6ca350e9cde21a12ca809b692850bf36c37d19b1f54ea690c89588fb798c27f78b06a1247e7eef61ac901009226d9bc5e1b0c6b90bf115dd84d06c164187231bea89c2574c4a007fca4829c1dcf501b81cddafa83a319037f5a950572bef04566d3c4c7f867d19d60d4bc63cdf711dacad360155f7920e498533f5c2f8aa3297d33c20246a84fba41342ac56ebe3327c38756d225cbab8260c2343284dfe6937a46fe01f08e8dba4b26503b2167c142d4757a88d349e6e24f207c4c0e933384fecb573712c90e1849bb9d6e22d5eae2ce586ecc83df642774e8f24614a0c825dbff58521bc75aa349e00962624257ee0f30576682eb1faa726b0b7398823013eb6aa04373b78c7fe5d6698ac9162148412c2061f575e92d539d4f326a43bbe6fe681a4bb6ced3c737e90926e827797573351628aee5eb8c70175f1919a1a9aab0bba5cd03eae0222931f229917ca3f1e3821d30f5a634fd1d32ff0e0e275c9cadd9de7d174d885255ca74784bd89dcec2f96a6c80531ec824b07d3773c3752fdcc73448dc138267d19cde63bf8e94e4810acac7e9ded5c2d69639e48eb4090509eab37844355ba37ff89469808fa0a9403b2bab8b8341c340e65fcf3c5b9b485a2e7c641955c096e148794e6eb059cd0a69e2ad74b0ddf2fc14cbc79b7d3403881bcbcbee3d1500d0727602178bb1faad04d5840706c0ce1e3074f7996f18d54d2a729d6bcaabf71be191eb99624c7d6bbb1279d55533ef2c4724c02740b46deffcadfc987b7fcd0b644a7ecdfe557c5aea27a256ebec28a3781edf4c332eb062a6df7d6d99b9f746f62b5b11817ab9b7a81bafc278c1cb19a4a8ff200224f9407244bda1850b4c475fa6a2d557930856b52569ec539827d7ae936deadd51b97fcc8f9041e223c22d73acabcc1c46751844f3f5a27d55f1c353a06edd6464971e10ce6a0ca6eab9103cb3c8d544666621f3268e197cad2a04dc7cb467481a92c349238e5c0da6b356be70dc5e90b56eb4e9d4ce2ed452ca7569d7236507b5f21aef2dbdab2a4589860f4ec3b8c6bf67e962367b594b676c79e538eea524263ec77fafe50f9c743c8093e6fc3ecc816eed75bbc194ea6e9d70852d4c55553ffb575b093e1640a6f406aca8dff491a1d768d5a4e01bf7a73ffb93f9f3fe02c55c86578d2057bbc17a4cff923cf4ad4843fabada76d19ea399887916d54707bd775cbb8b63a91e7e6482b316f448d92592cc2aa545c75e30fe24e625f86308fcd4fe0f37e3578e17d9122f1982dec08bdf63d7866c3aa6b0285a01abd6d3d25d2ac05302e28a9c1f017e08b242dbd87669fd8bbf29dbe77cfe369f070d9dcf0577f70e10c16270049a8ef227f6289a31e2c9d419208351e693e86f2877f48bb1f7c09442338869a040bafb57ba2025f42b61340e36dda9077a46127b23dee62c190bb39c371cb77929f1dd0ca3f378c43c9701705c3d530d3b71741cae9477d2aaaaa4effd41568d6c614d9866346f82911e28a1ed415b70c3fb88c47b2ace018cb0c7a2d374409ab1d3316aaf5165b0b59527cd7a06bdbe5879b0dc80ebee5a0729f8f6ad5080e5b87043b9715d3f298d93e24ddc8e31cd499f0288f4052c2a852f31a14091de1290831c090222a2991d777986108314e8338efe00263fef96e56088b4e0c8762c96fbc4e3e549b9e4c92e63599c4395153b657622c0ca282fff8f3297fce2389e3d3a0422ce34a7e3382b22a3da81afdd4962a92397d96e5c1eb8b59d3d8e60693da4da74bc1230b43e75fef0a5030824e01c25cfd626358ea0c4ff3b84282aaecd4d61770e247ed8ea7c1f64207fabf3c03b7c26e261b8206ae067763b6681d70a9d99bd21411e5b97dd83d2a94e7e8cca23c0326cdc064752adae849855bc9998e68f18c69242f7e77f9ad8b14648060c1dacee26959aceacdcedca6ccf624549580b3212d5a132bc4d57433eaaf370fceb220051d8b77ed0aee32f9a9ed628103d773f26f7cce22d33d8f71b75b6e2448b83716a5eb875a018c74867fe883747e072eca1e1e4557c2b9c49b6b76239e11872c3d7f60aae0546898d248371777056d7153ab0cb4d51c513b8b5fc0f4af91c1cbca9732128353a3302bffc98a46bc2f3e9e441ee139e91b9c1e8275613ea720b0af8ab7531b6cc1aa1e0e21c7918551d9a09f5e253508777ab49480e6656b9f120174b2a5d3f43657ffd727f87d6dc3caaf4fdc070fe3f550b37729b2f3c36c6a7a50ce12aa413663644a2bd024e4cdcf09af98061f6a6bf00487909deac58cc1faedd34ef50abaccaa825e4610c855cb4d575c995463a4ae940689d202e85396026c7df5db23069e6f829fcec440c8a4ff297270b008adfbd59ea4042505263d374c0913d880186b16f46d4c3369b79509e98a6fc7f6bb453ae9816cf12e23f9e512eff83a8ec42046ff458f47ca719d355f4beb456b1c3dbe0cb0c43950ac6d7977578ba7d672b26b2dd1fa5ba51eb1b418473e65d3b19a5e6ab95f849fe99d06253a6695f9be18345e9f19c10e29c69aab69e07aaaee849a59270f8de1bc6ac10a4373d1f16d6013cc593d3ed803045f9010ca6aa85890f8f490fbc17bc962853ce1cbf9ab763ec020a2fb894b90415f8a57122d40f42181f606bec4567cc35063210d02ed16bbb7990f4bcfb4b1715ba05966536e31ab43a46e03ec903dbdc4bd3595b2882e0bb53cfc3f32bdd7c9f6a4682d7bd733d0cdcc9dc2c4003668a7b0cd7c26e85a9c3e1c14ed4595893c394f7decdd36900ab6d349968b87525a63e79e804ee08cad1aacd8b792ef3b5910e361c1c11cd9c316ce1b1cb2336f7a8704c2796cd8128bd4258a8514cbfbc1cb69599c06fd94340aaf7b526233d47f44e2c475994051aa3afd2881bd0db82e9e7a45359c8d778d3584e4cef4966e88f06294f94058a36ee06481dff1ee336521938c970a75f8113ac2d34e76e36e5387d19f7ca1441e569de272f20932eca3d0ff4286e948a1d443a1a5952d7537f925d2d7b34f7730aa0bf4e510d23079bd8c5458e866dcacb67d89f0c10db2c46c8de26ad6b36457950bc321550bb637891875cbe2c4ded125998f3145430cf9ada56a61a0878a65ac3fea73f7eba7425d9c2d2745d8c952d44d01dc70acd47657f85c7b795bc6128f8f495c17988207fd265bc9728b20ad50d1011e1c5bddf11d3267f1d013fcd040a5eaae19b0a2ffc625a4b6015cfd81f36336ec3d17ec22eaa8b2fb70660c1f4070f55d5ec45f58f3ecc0fcdddbf09113c3822677dafcc671cd344047611bd42fa5b4ddf4d8f693643554e5de2f30e6d0b7d5e77053545497be8f8ae1e3261d1b39aa5422e5446de29b537b6191138111880553a365624f63b8826fa81fed26fae8b30901585188961e0025aacfe42f7b84bc6e91a674691d9ceabdb31c23ae22b23fc23c90a8b949a36e5485cafd574f7a75e2c6e138d3e67152dfee38fbefbc589002f46386ee2f72a090ddf836d90e4188a6ec940f6d3ae2138a25a1fbb42ae311621299d0ff07b62b59c3e3a55fb53146f919732ebfe2bc07677bdb24708b1ebe650c70e1f9f30174a6b1a7abbe2ca7262b1fe392e34872c68cc33d68ac2d179192c11722a3b5c57208b078532f52860314e880c4cb7a9978f226736e42cba20daa6d33754beadb0fa6511a018c88b165433c97678cea6c373c8bfdad10e3d6d142fe5c712e78000a9f76676fe35b92e13d13ac9f98b349fd8235afd12f608d3b2fd6a16bf29e536f9223cdfefc2b9683596bf64117263126fa362f6fdd03e81c640400520e04d9a54bb9cd53a82caf4c0b12ded7bb734c80944b7b0d97b4ca805138ef5520a3f903d8547d45f24bd1beb7148471cd9c63ec0af5607b1fea587fdf49d6667fc101f98626071c99b7757958a13a484527648ee6c6037668eb7f2281ff927c91e39fc5961c281f5acc18e688f606312fbde28d700a5c9f9a786e5a0fd9f4b1b04968c48f54b274f232bdf4cba224dfb58a9366a91bb6c4b5c817025e0a402fa9a9544290640a4fedcf67e81cbb82f68417d0c8a92231758377b3f3bc7cafe419b140e70ab4aa6b99f4a0331de143ee6a62180a0f1740e8d51d3b2dd7a2ed14425c6baeb9c9a0e1bff0f66471e93dc364398d25554d2c344dd3ef04a2269baf0554f96ec7aa0e40f6bd78231fb37b7a7d23754c40288ed41cc6d36d4efdce7b325573f47125a8fca5fe3d3bfe48bb732bb5ba06b8ead9808b6a2d4c6e398e12501a45a7a627393223f4ddb98eb6f08b15d36be3b510ce52278109dabf0758a984603fc55b101eaf6c60f61ac611c10f6a3a39b92908053de857b51b519f9dac5867152aa840c9536f81a884a51b097e31a1fb76f59c6bd1ba374564ed91412a63badfa7db0f3fc1e6741914ab81551a47e42a8462edbe572de510ea06d61c0c292b99ef19ac9b57355df339703a694fe10365b1b24613343978316b8e177c22b9a0a4093a5f8984cd90e12607b0bbe1d322efb8aedb519d629407e26a59a2fecec9435eec25cb6106f71548e96af6f1f5572c4f54e1b4a3a837b54983b5409d86cd1f4e6985b8a37576c5d388b879cf55e427c305404a87ffeba3728c1501e1eacdc45a2a12b8d72802a965d758d69a244c0f2fddb1795e4f24fbe60c1301dde86e22df6f28c9f257647fab539653b883e3b63805d9770c31f32cdf48c951ef719b0c5895edfd2f6b5e8821704c6c129e9b11623993d187e3166b46084875abd671bc3c76cffa3e6e1708df64484f2cb73fbbe9cf91e570338b2dcf6882e594a4974d67655d78c24a1bc972be205e041add713bef0767f84e6faf172e098fa99034400d7984c71e645e99cf3b18b1c611c497f0d012e015c0126ede419c53bc387bf8472def46986ed2c748cde0914f566bb160345a2a0e076aaf4656a3869fc5f53e2d9f86edc19b1aaa9ec61bfe7d9682ebc16249f5498f00f82cdce9701fb8c3bd77e2d9c43b76d61a1b96442a560aac92d9662b467cf8ba958bbf8ececc6a9587bf337e69c106ccf5bd924bfcf336a8993c5a67f318ee6114ef9bf4509712798e4209518f881cf39c79b3868f4d4e3a1520ea13281b07fbc9fa5aa31c38487c2c3d44e379efd204737f41c6b0c5d4aec1b9751be41b686a4bfd6e78496c64bc9cd2043b837baebb649cd74b638e76b97b3068347db821b5f5941b3765ff38bf7e85e2879a27eb615ca5401a5edb3652c51cb0031849858166647e86f0a5f0d457408b5bbe02d6a4cdf306958ce46255459976d932e7e91f01d749afdfa71fd30eee0a77871e4ce52bf2c6ae8bb8f2ec45e3779a1ac49b7b6ef563f71a5b4fb071a23d439dcdb8666fb896f52fdf4135338b9f9274cfefd0174adb4dfd6d8e66ae522e993f322dcf97b814a5fdcb49222235ca1fb7adb5eaacd5e315db01d00ada1d6495bd1c6b64247bef85b020c86548e9ce5db0fe81f6a013ce43769523366d2560cffe39fd529a6bf8cab8bffaf97ef4313ffdf38ab4a90e2aab89bf0fb5c4f5cd74fb91b61503b5134cb6aedbffc35823c8933711e5438588513eb999f09e7e2876425633c56d2e977702c906d3b9cdaa7339228f5a2e1e8cd5b8b8c3aba09bd528740035b06757ea122542b390275123250a422805ecac3c7e8b1b8c7fd12cfa80c7c0f31e8864796b5ff86ad27547686af73013a14486ce523f61b7f61ebf884b843cbf8ce85b4de922976908332c563d43bbf76ec2e44fafd0f5ee49d7c44c9f8a9c88f4a541db0c9589cdb292d280e505567b82345dee2631ccfe83305ef411c9eb2f066054dcc112bd04b4e9354115a9e575c02e63c628c4c83b45c1b15a1cc8c5ae3bbd594199a8331f1a31da7cdf6fd4889b87dd6ec8e3fb5c7407d5947f21557acee7fa92ec9966f1078abcc8adeb7812fbdacab23f5bac51eae144836affa1b67e09315125dd7830dab4d3c9f083ccb77d06f62a3bec31de8d13eee995a63015f7d6c123639e39f23f9720f5def571fe75afbaa9c7ca9f0963c6c5508bebef785902cced83baf63ac1bb4e1138ce0bb194bad9aac21f3fa6c906f22ed1b877b929c6631621441f8319d0e41baef3e20732d55d390486d97e85438222c58a971f5d603f986d03b65edf280574461cddcf8bed8ffd1492d3f231d1b73683f33475ce9073aa00f293b02d38be6cc054c3091cb5fb642745170b7268de7a57187ac2b71d401481be7a13661beba8d07bd8bb602684163799ce5185a4b46bdd3354121c4eec7dd54bf762af0fa4e2cac4bfef0af98724a6fbedf6e88664f11e1242d83c72b350f630148e86f53e002637b316902f16af6bca8e0b54f10bf4ba9672d14f58c1f7bd36dc1d93d5df9820300a4e868a32e8ebdd19deac2ed5c85936337babd73e941cdcac19f458994aaede0f3e09112aacddfa41c18c689ff97b79ae7889486e76e4d65833654c1d84c0c5298da290f71f3660cec786313729fe7ffa814f07d8edfc8a1a65ef24fda3ef3c4e21d615b807ef3e353fadfb1e16837977b608f586422992566b53767a8f83f0caa97f742fc7a03906f3c0a0e7fd522d7e6c5af954e9b93c2ec4ff7be722dd5f5b2ba4c45c3e55ea24830cdb18e325735ed8c87a47456e0de21e2f92971d4c5e8f8837e01ef4ac5ed3562dcda977bcbaad2491e7fb8785390465bfbd5f6b6f9481409be8e8406c948cf6f27909ae2183e5d5b72f24837d2d7bc76c22a7ed57fddddd48bd866bca02afd60c47b732ede551932b49137d460654795f8b0726c86738d02f88f1e96811f7dc75856b50e50850af05ae595ac317650266e77d6415f8b2a9cb333438ad9cc92a6ce1cf00edaadc0803da71ae4567c053c9c72a5b61a6f4fadcee5ff753754ef64af0c72b419d722049ce7c855530f60763e1a2391dbd6f47fcac904678e15a7ccbab4957a647397a6c910ef97c6227798191cfb1095df8cbf3a30188f6172c5509edc5494e2083a776aa0ae2ff1de89630535f8831faaa11ead0310406c4f1b525f62061eb379c915ba89039603bf2a5b14ac692aeda2287ec71181d218928698ceb3aa09daee8dfe69af8fa4fc4a75347bcce44639a009a56896903d87cc487ac5308b523395393ab97df98397765c7fe94b92f35aac00ae75fea1ff6a32b3d6e9b3a7900a40b31e9384457d848a59bc9dfee14bfcce17412ffd5a2767c0020ce9378af134a9516ab19b31c72dcf0e80f4f3e6ee3995b4547b751eb2078bce9cdef26ff7a9e25d19f5c838c240fa4ccdcbbaf8d22fc6499649bf1b8497b1a9759c8c0706ea652924725237c5f6e7f15eb9de3b619b58abf9a034d59225e513bd718c0ec317a111c221f741517bd0a00239203d41ad715554908a343336d1e10d741167f23ca04be75f537387073c72c8dde42a85e4ac239340a8fd41bea9f8cc087346164561b3e22da8ceca939333a5ae6eea1ed4ac5d3952ccd1b7588ff8c6891a40c99737ec6b3027af8ce6f9aac61b30faefde6235e2ad60eeec1f0ecd433ec30d8c28dd03293268cea4aa7ba0d667698af2d82593b4d33dfacaa3fe876c8889de1439fbb56c204822bd628356e7ffe277cb9554e86a6b197765f865cea5739f045b4bccf170cdd08e94c6cd1b2484359807bec9d5bb48364b0df498ada3a3749ea4d8ab174f1f688ebeca28f57cfdfa36d53a52d95b022591c120142f8ee9e4b575ac298d3e51702a9262d8b92d090cc93533513f2f0c894ed0c9a6e5886ef15693cb21e329e0ff826a622ad0082b8e5c7e823cbe4e4f3768ca9f56f293f8ecad565bb5c10502d6f7d78ce2e46ffdddffa0a4abf8141e8e3fc836745c94941a8ccc6d25a31bec9f80c3b2fec4c7929aae1665b1d48c12e9918d2398886bab7b22f0c03ce8adab9cfb91176860a6feae2505b58fdbe1a0f920f1326369643ffea294fe926fe95dc04beb98b56f88a13182e971ad820761a7ce073e04b089b0d1824af38f0b42f93bbe46e882fa73bba97181fcd4d8fb7a43bbbe98a2fc55f381f5aad347d3af7427640813df118c198d02b846f3c896842807f6f3febdde7bfec920737e8acfdd472ae69abeac9338c427d1f54269de85069c9878a37b9a13fecbc3d2f619fa33517b9e3890c94fd18ff3738a4e891d26d3a3e21ec55e7983e1171e41dcc874ad56dab13c3ebd557e2b97714dcc17f30fd1f0b3f5abba63ea46f9e6497afd1b12bb2e707f59387a53569451749a8932ea603c853bc8a63cd3d64e0ce4673f0b030edd2f4f42927eb7e76573890f07679d37030116fdfbb7d384d41f769ee0a11e0a1e762455ea739cf7e77542944955a3489648bc4e7d24aabd26b2451461a42bd89c126a4afc99c5fa3af0fdfe7baf13c09a4f27fcffc320cbf8c3be31c96884c6134637f7d8e00076aa1445252c5a0a25cb5d2587a0db9b9676778190f080a95c4bf0579eaa48cc62da846751260dbe0c26abb7d0ed4e971a55e6f818320d0a71bf225e78e04283f1fa98e10060812b783f7710f4f8d50d95c744dbcc8267312978c7056fb1a3236d5b2c43ee0ce5ca933074fa0c65614f99681080084de9d6b3c7a4f030b3702176dce7af0ff98df7e08c939bbc1ecefbc292978976f3c06cb02eccf61061c696f3ba4576996cf9ec03d61c45c193dd473f0461e5e6984c750ff2b609795e643700cd6e1c11dc1a03e89724a74cd98cd5394ddbb47d1aa439ba1f95eff82ad2a72f43097621f2ac58d9fb196676f8e32f2e4c4add73713d35100811ea94d88686cb0187d8e4aa0b182c8b4808d5cb73acec09a72a85c06939a353f6dd68be5b57f96cee1d6dad98eb9837128b4c801dbc68db8a9e87c7994a5cb65ef7d1d5ba16c167698a44e0b18123d1af19c98412c9f412f51d9f1008622398f93c92e5a2e18caf893b36dd5aa2e114e217a0c12b080fa0b074f1f08b5c66dd635980a71f40f162c67919f7d9bf6a1cc1a763a527f7627679835b2a8ebcb6ea314febc04f5ff59dada24926a0e15fb1f702c59c6ccf1400037082fbb416d03bf12c085ac7038b1917c22301ffd7986eeba2fdaa72d451168445ad0fa706e717fe2da1a994033435ac397e72ddad610e7f981542ec92a8dfbbf50b5569d9249201efabab19b0824b6296340de7c22de78691d3f5be73d3c310b2560cacb24449701021051baf6c48c66b9c27e9a1acec93e6e7b92567d31fbe5a5845773702d78f289e1cb7edff4e3c0a22d9d18bc909c68d5afc9c6ce933f3bb326fbac47e2b9425b972b24c970885478fd9ef0cbea187e2672588879b6a993c308b8887e5c6ea3f58bc6f75e0408006b4274324f0aa84f2b42b313aac155cb606a10f76ecfbf8c0a2fa04d6c3a9f9effc65ef112eb562220549b5ef1ea18f5a816898776d4c47800f8ba32679dfc228c86078b23cb707a352b8d4ac23d682ccbc20ff675a7fd2e7328c6dbe287fb21bd735cc657af437e10047867f5f31f758fbc5e2250738c86b25ed1711a85b85e128ff0bcc3f74066480f08dd465eadaecfe8b79572ae7af53df8efa96b41a1f35e4dd85e4304ec23eb7b0d8cd65c5d0ef711912c47879dfb4d44f687d80a62c6494e3ca6f7e84865f951ca60a867d18a8a860bd3c060ebf74c86bac787e1d20ecf621c3dc032bd5ee61c315e2527dd209c075184d25e543f58c883ab431846dbbfe3fc6cc8bb94511c665e21999c4ee0590730e6f602705cc57d9059f984daa63c5878adf78f9c9976ffd67bb3da623d7c3791ebabd8913798f5de853b41b348406cef0b7ee18528c71d554a094ee939d867a0e1f03117870c9da1e0c0fa36bd1437d1f3500d03ba51432edd92955797d3b4c22ccaf3ff4f111209a5e2d3b88cdad13594a4afd0b74efa9b1e2aeb4f69e605b4c3c472246fc8054eaf1ed42cdd033ec53c75c81cb96b5f7122111c5c0e1edc65286a5f9a1b08ef24b498e8c98ed99b249102f0fdc4487c07f26c11dfca9e707937cdc21a13dc8c058f9474bb1df6b0635f4e9bec03f9716a36c3e87e001b236fa8020b7ba90339f01ffe714465cbbe243ba1707058887311da819c89425e38e581a3278b446f0a9bdce321f6ca1ba978bc05ac736b7090abda783c1656c6e87f164cb6eae5c7c7300cf64cdcca7d6ca966c993ee14234fcb4a5ab6e482c3be54581f3fa744e4a90ba00c14c157dde46cc802fddcad86177bf2412e3e785c45f3714181b5a057ee15bfa050ea04fbe3395c05e0afc4706d041527e7576faa06659d5acf97eae239ba645085f278ea97b90ceffcace12efb73a16fbbfa225bdabe22197cc311fbca8de75d5e9f3b93418f061d90ee84026ca47a5a2571aa30841d41ae56974c4c7c1ed72fc2d78a303bf0adb416f54b9f16da9293d7fd437fa227864e37f5f435978e63cf711b85d14ec6231a23fd38af77394ea4e55ca1b1ec6ec1b39bd0244976e86c6c391ea2da5c56c37b418b6a4d7ca2fe7e7c1157c5c14a5299534496ccf7536bd7b068248352a56645ecdf2d8c9cba52f52a82a5cc060608423d016b3cc0979c068c20e95770150dac90453beec52f4142ba25f16b557a2df3112867c54c9046947b5abdb5e0f5b427a36a17ed157e507f9642e55c7a72a27542e0dbcbd586693f9d972a529efed5f1c339097564c21a2ec7026bed436e1695e33df6640192f0697c507eb5aaf2ef89f3e1cf689e6394b377de547fa0a191a4aa4867db656a4999a2afaaba94e20d32d7004919f09cebd50f9fdc080d497af7f935622fdc5c50f7b7dd80104fc9f3740d2183d1bf32ce41dc7cd89253ad2d68fd27be91a244a8d33381afbb567de99992fe4c4e78dc81074719daba997015d3d603a1ea4eb7e91e9d53e2922930bb3035432e3f232d831ba0574031bffeda9ada30795291cc8fef5aee59c20cf6948e1aaddac9d3044ed57556a33e0c3bfc22dc52349ac62b2cff143155492310d44853d19c05e1d489727d0be83b30c58d548a5191c35a49372da4e3af7cd4f2c5bb7fece97ff04013900b44b714906f681ddc3e2485fbfdc4fc92752c818242928484734cb9d0e643b61c0c0acbe4c058ee8cee22c99ae180102efdf8d690403a084f5617a6235768caedbdfbf6299b283ca950592f411499b524d821c8ba62adeda926e02fd9888b99243c183fdc6184012be1b489b05ea958b98fcefdb90e709f219592b179519e528818ebe38739ce39a6beae5ed6d44b0b82e257eb3cb436ccf8f50b7d40ac880b4aa9aa34a73aeb66053a7c680e64af05b915805e2dbf24e82cc0915706dae13bdd4e7817d1b1f70d56f1551635f777201df058f6461c14aa8a3822f8fba3e33e606cd58737c79837a5c7550b8a1a375a6200ada6fd49678dd2ba928c344bacd594d49a10762753f1eb4b649d6249ff5432111436b42f6d50ed52ab3500ef09a368adf654b087e7cc0b616e57ac4fefd6bcec34c242354b773098f3861674d9b10d0cb38a001f375ddb38a4773f691649d8d1183acd54b7c7b2fcb00891367aeaa2cb4dfa356f9bbe42f4d2710109bd3bb23cdfe0e92089ceb002abb8b510f7a06c603882e3f6ed0b687a026ad771f1df0fe592cc17f94d59010aa68791804c059da9ec14bea6b5e7eb5861bd688b328a1e145b6b85e699fb99a729d962f6a2b3ac0362364b3411f435bf307ab19c3637d4b3c6fc26dbc38d23b91307d4956b25431dd480dfa3fe5ed1e6d2e264d61604e0b978121d4d19a89ea6addd05f40c3baf50bb21eda39151d97a56ec1fd79bb32ad00f156b685a2d1884751c6a9c34baef834c5ba293837e75d0d9c46b93022ae75cbdc1fd507dbe6d0591f96d66f1ca4bd1d403d84e499b6602e946904b54f3324ca2a4012f4c422276ec9cf4e571767b7acb02501984e0a2418a87367957bd463db2a489ff6e6542275ca2a180a12f60e882d64343348a02ed034ddbde32d936f58f788563d7155905b39cf3f1e54a4db9f71143618d5bfc418892112317bd4339902a08e751067448a58e2e48ee7de01f5bd96131ad1872b8da0595a1a685ba72d29bde7a352f38707a7116a4db666a149c67b787bf0a8d5bb3882c5c5e6d65be28ff751ac74b06fe05b179450b04e71a61882cdc4a7eaab11cb35c35f6588c495b5d12d8ec592f9f1aaf8bae703c869f9a1f9a537a4558054950eade894c851e2d1055c9381d39b9af400fe8bd55c42b14a1375e3b34612164dd2c265ff4d4c26a48138e299c328a3d2c51308f7c12f630975945eea5e9d1b60d3188491ddf48358a7c6bbba4184f68b0f29e6e410f8e4e2080423af59bbc0947ab37fe3927b7ae2c91350a424a22effc8387cb184fa71e6cb5f94e04e5619525bafe26df20e726dae880d1490d84f7b6eea4f0dab444a7423c717eb84059f5f778a6a548903dea14c9d60dd222bf3a76b0579b415ac67e7b8ee0a37a961a853e62ce44d47adeddad8be5deb66b9b4e48e580156f089c4784abf03cb7a5e64431825d0683a73e274e55e70f169d103f2d22c49a68409e65e0b647746082a0e53b79f75a7daf3437f11bb958d404ebf29a1f3a6193d34f65a4e6799d6a21aae27a4e1fd18b9eebb266fd5b7f175e9461f733ded43d0c8a93fac1b9cb4ff515a14212f2359b2359dfd1e5dafecbb57274209e4074156655d29eafe13d8c9abf639850a3b43f5119662a86cebd1e5f8f8ae89f4744b1d6f0cd64d4b05321941d84b853253d2feed184bb9af9ab05f877f99268090008a64953b04155a11418dec79036a75dc2557ffaa681b7d3b57d8b9256cf0801f7d665affc739d7feeeba0db42c7d2c579e84c505d2137892a712528e2dd5e5bbc1f83ad56216b283c5160f3a639f4dd750cdb6d281fbce6a30b594ad62a9bd07e72ecffdf5741080f025b51c5a97537fcbd14c3a26560b223832ef74704650bc315c89d4dec076f7d3d91fc5f6e581ff8aff6b33cfb0f1ff25e94c63d6a270df74c60e669385e331de42640663b964677fce95cb928b9971bdc762e34d951d6ec90a8289229172d8fa43ca5811494472c659438b0efeae8ec61afd79fe4f71855580184399fb8a4f23712f98a0f11cbec2340aa0ed15baa7806bea060a1d93449707cf5631918f76cae839910e73a32334b3df291e312bf18237fd752e01ed3c6e423cb2ab76eb5a6cb33b2d65cd2f69db6de9b4e3a2d20e6f84e67c0384f372c7905a53c857e7b4d73f54c526c89656d8bf4062b9b79eb40654d35eae250c2beb004a25a1ca38e922546182f4d05f20949e648668564dc4ba6b3a8876278332567b554da8fe719ab8beb8ecb452e277b69ce430c88eeea6e2f6e693e01f3eaecf8c0a226ac7b84501406bcba4db721f291397c82e8a6c9d38d6cb32b1916d005fc97bf3b8522f13ea02a2f418007963f536be80099e460e95c5939612f5d85428872092b55746eb83e55d4dc5eef9d597a4aae127fb732e44aac7562de2043dd0fe1ad9baa0dc101fac64bb8e3d3edd9f05687150a4cc83b2d4875a9905009f0c76fa146e705fae3b3200e3964399c7e2d4b4cc019bbe62a25179b200bb9d7fea01ea4780e473d2b124d217643d859ab344bfc9834804e2ed45698aac7a14369cfafc293e3a0342a7b0b63b874a5d3e428c36d7a0e10368ba086d99a1fcb10f9e30e038e58b1ad36222e76ffcd78fb556b57b9155f4985c9092af43a46176b5b7452b0714cfcc92724dd2f7d2a3b1f4b4c88596d08ba14330dd135461368fd9f531a09da99ccf9dab370fa185dc609b84bebeb5a8df2d8579a18516e9d7b69ba5a4ce97b75b8b3b256b403ecf2e0e3b3e91b7b07d861ef88179549d7a4d19302c17166991ce37b1231a534c7c5ff2ef606c63a735073f75ff6d82b9bcde3cdb504373c8fd35557e462fde0ac5eb7fe5ed56ba9f342e9ff365809d344aa3a287263d05449340a3915741ed5512807896f58c91dae96ad95acc2b2f95596291dbde244afc9cc7f4f16d12ce9a071e840ce75f7df20d4922e24e4cf17c1603b5480e8e163a60df75a5d576e883940cbf1c595b010977bbf3f4568341c72c699758563598d8888ab5de2f13daceff2eabdc2d358cbb9abbdcc7bdccde3c3bcf9e3fd834c6bad7ea708a24526a247b4a474d8beab21fc17692175a46c6d078cd6bafc03452967bf0a41206ef594fcbc39713caa31ca2d4f35b6a5fe062d00ba059efaff5e539cd6defadfc9db387e66a715c98dde9cea5186f3c318a88d68c4edbe535f2528458bd26fe9d6e72a6e0d7f007d767c43287915a64a347da56cabe9fac8cc62a5efad76694b1f718c23c4e74ffa86c3d5adac9c7bfadab25b2b4ca31dfc3ee9633d37d6ef9635442328d5339f0688417396e941cc542314c43b8b6e21f810921463b6832e60c1a1cfbb15b4ef0b09734fd3187d4a21059e40254fefbea8aaf8b6ad0f76cbc19b62795008b3589710b00ea63572f0ecbd780de44a94bc5cb9b170f68a4e3cd7fbd2ad0dcfc12ddd5ccec428c4f9a6834509386a98b03548f800423132e2df42b316084beec04533349b60c497d1af7ad20ca61c233e8004894fb3e47f20c3a87a28261bdf4e3092c58ed1bae9afd1a76ef0bd51e2926f0cdce4fca85db101324ec65cbe71867219195bbf9dbf32a2a79bd675d6e392714129a418ab72e4a30ddadc942c0037578a90383dd6a1e50e5807fd0a6d26296b6678d0a32dc2cbb4ebf643bd8f70007accf7117d1fa2d7735d50746749441148a4391975069a9b0c7f36de03c507fdf76725f22808b27b239eda5b4893ed3be9066e71511fd1a9f404806d99e10be31b0b2b55b37549cd40c5b3138be932587cfe5f7338f455a65bb7bcae1ba936c86e09163fa11d5df331c18716a4d99f4b4edeeb5257190de7c958948ee11af8f051d83f3a8b0da5dbbe47a56d266b25a3d96058a0cfb2ffe0a6ae39894de08855a9922e9f54243388244c12f16e94d446291fbf5d25f9d928aa004787fadfccc6a97405c8e2945909038cb0c5fac468faa61b052956bbb994839caac90fad05dd906185117067e15d110672cbac59a2741439637c51db4c59df53a3b64815551ae8609dcb70f890b3ef7e9ded49297117bdc17e47c8ce3f3cecfbcc9945950febf71a8b7be27602c245fa2287c0fdee92e5de1163101efc3573ec3ca98ae0de6d3a5d8b11c864ca399846004d63102afd1d2224a126e8b61c6184aee12a5eb42df19b1af4871cee81e570b5701c94bf2862ffd27bb05dcf325ca3bf8b7ca78b377c6bc502db47aa3d274cc9ecc12d4644e34eaaa43c447db2a02f3d7c5d0cfd35b40789f2fd6689544bf05d2d2074880cedcc8cf6e777cfb0e8927f617d0ea07c54cdc3a5e04dca761439d58604758f1b98982c8cfa61faf19437d7afdce43d2d75bfb56121a216589745852c8d317d1ee2b713fd0687f688a51009ff2efad3e030039103bc62891ab7f77c99481d37fc53903c0394fc6e5c5213dbea176d667ed4631a97775d05a41877589d3aa1e03695713a0b5feaffea3cb8b0c8498fa22963cdd9f046b5dbd781af257fb19b25122b08f270d410c5e8457d37c465695173cbd4da2d0363c2ee7ecf58e8f1d9544829eae16a703f38900a6223351d349a35e62dfd3a21b21a6775c9d08924a24a58c90ffbe559dc5fc0ee6dfb3f7cdf14c63a33d5c545c2874701d5ebac9c2e2bb6b45409d4ae624d426228f613768dac678f641c39e506d5ad96efab228329ed684df8abaea7ddebcfc01ee657c314522680e2b1a66fc72ce4e654eb422d34b4de671fde30ee6f1eb6c5613698e37e7e86b1f32ac1e29e9e767509a1edeb64fb01b8520f8941ce2e2c0d07f7c79a8b85b2b782f346fefef5021f5435c0eac18fd9aafdcb85c8c48bba86a8f926942eb80f769bfe80c1a8e0bc1041e6f0cbc85a3d85f0f57e40aed74a0de2fd1eec0c57658ef206b74a3465e0d41e3c8209eafc6967df8cfd8dfe46bb92d5ede9420055ff715d5fbbab8fa98ca5ff8dc0c80b387d44e2066412ba4e0f6ea82a13a29ae976ca5653f0c87709c12c3b963bb9042a25f4f5381990faa2976402e925ef9bf30a3b7a1769e2017d7635ed16aecc21570c4479cff7546d85abf7919d1ade7a2bb2ee0c4a08f9144f0f97738e8511a384cc55b15604d9d563cd5987795556e22551027203bedbeb2397d1f3fbfbbd3e6372a546d3abfd86c6542f18f747053226be9db776474b974f8d66d1b3f5859d09dccbc4ea0ca0eb8ef7e7fd75076afbc07c790822124d7b7c33f73a4415aada3e7c6f1b9c0d3f0186cc0ff4e76afd864e5ba2378f2478cea19a969d69b7ca06954cdd1e883f0cda3aa16f3d8d813a4c2d5c873ba42ec11d4ae88a2d13c96da753f87207cad4dc1c31bd351ec174337bd494c1ae83230dc6298eee6077a95234328a5af15f2716805f6ca961d6260f78b6ec904efdfe9b7d056e3f73388a239be226175d2395df53feea1cd103214457f3e5bb7c8e982fcbe823efe398ffcfbc6b9e09ae51b299f8a58ceac060a2a565dbd95bc0f2a5ac8aacf074f5bfb2e0ec53de62ada414d963bd384b84f1e15f9f41532243444fd66acdfe362f0e6860739b8ef105fb38e99f3896bdf5c3d5d0389e70cf53acf6ae7092fbdddd81cb8a0e8763d7c890452a4aa78086f11b11c3170178e4e3a315fb72833e4b8d2bb54d6d3aee79e8c7aa721493a08cd9af8bf056d4061908c4515d9c2ff97693b2d7cdb622f6f1bf80e4fb442e417211f563dbc46e87bc7d262b85ed4329f186e90a8c0031a5d38802de473252bd59837ceefa6476d6f82056e6f3baf1469dc172a47cd9e41069e373badacecd1d321d5fa4f3ec1242de72cf93ed2ba5e985e374905ec126ea5f55e3039b1804c3c24ebac21047f04c3c1b115746b90bdd524f5f1ce4adf25d7ac5df8228a5da33ee8d3cad7e1411df8d643c4fdeb76a6001adbea5cb85d2700a01a1408021f86bcdd2c165d52471ceaabc45c3c8c6458afb67219fdaa6285fde37c075d2c75243561b00ab5aff77e98334b5734d5e550a912d0c9dc9604acccb7c806785eb0ec938e9439003603bede902f7332d4ccdc32071fe5c94786aca3648a775fe71adab80c03ca0a5b5911143023c2dd52ef3f9f7ec86f84ca85305c9f8e4daf0538a0851abd9dcf7a0f4141e30ea6c76f90409091768b6851bf3990c4bcde19f1eab3cc2b5352efc7833f2c42f9da049746fe520b3cc2ba1f1b264c5e65f90b875dd90827094122ac9f5a94cc5b621a1ba54db2f428c9fdcc1a4d0b2d0200c258d25db5b726ea13bd00dca225d7fb2cdbc62488f8b7fe1b4127c71dbf6c03882b07c6f29d48d33ceff9a9cc483d68e44bdf949eb1ae5075ae058f2a2a85653aebadff91ab23a42207ffc281f6c73b2e7d4a2999cbdd5c4421e6223b21a268c3d9fad1b5aad774f16a99370126bb576cb6dd611cc08c945942e303028e4f9e07fe355e6496c4ee9588c5691b57d04ea56c81295aed229cfff6a0749a3830bba2920578da0162c1ace6b32b844bec1822679425185b6fa85eb9fe2486ed67b0b9f848de98cd94dd8e83c3e0d15f2c70fabe67299d3938a19388d4d2288a15bfd5650d1a7f2673d464929bd0386fd7b0819ccdacf680c393fb9680f1d490d445ccc5ae53c89745dd6f013f1b7aa6b6d9edebb301a72094b32835aa52e21267f54f58cb5f8523986a3dd0abd4451e98632819551dac434cfa983ff8cf7495cc0394b62e5d272f4b19038d6173b21cace0f75133725f87d92f678e75e2f4e036d5d0170aecae066b90162724ea5c583b78d3c403eb2b097243b3e17b989f7d3743528da0ef4dd53203f118b16407a2e00fcddfcbdfcb778ecef8f88f95d8b68ba5520db478c5205f2d66a0571d3de0b25fa897aed9114dd4c25e0a7c89b3dc30bfeee3eacaf71caad8c6a765cd5a6fb5f1c1ec21ac55ec4d8358ed373d668c7aa3ebce8be8a177243aa3da957c0960439000a303144faf097dad9e3177169bd65f5004ca12058d21fc4321db4c743d0a632fff38f46b2685bd3359d9b35f1d03f25b1f5d1009e434942b484b8d3352dbf89be8f305073f8219532407ae04792a96afd003b8689238c405b4493ef5e8ef7d3475b1dc382ae8cb015fb5d6b3475498e1c9f90797a52b79788f91c935688f5feaa0438b9a15e63e256738a203a535478ec97ff304fb7e8211af16a726a56ef17bc0850e0fd1f2900a84117c16be7750620c22b876db9e98bc2ae31bd61569cc7f53c8952b0ccc032e172fa47565249861d8e7e439bc79b454268f7f7c28ab52be84712a5cf8421312705e3136c9761d6a77dc5cd84d07463408916d987180630ac242ba7c72b1b6de571e381e81ee26feaa3204712cb0b83ca2f90d36dca0bc9f5a47b8bbee72a64c21cec425b35a7da862b4162d431b50493adae397d8f27b289eab9754ca35f8688e515f6e98afe4dd0d2f794e37e2f2cdd0a7821f4f85d910deda0a6af8f4e651a9bbcd1ddb2ef7f786a137d85b45ffaa680f0747dbca7a263f936c0f49e171ded83696542a614e58e598f92cdd71dd208fe2cd87f34d24c052e64dc3f067a2930d68dbe314102e3d91712813771113af3a7354f5bdfef411a00d96f80c4f818be400d7ae9c340b73fb4bbc2ea109e4d6b51b8cd9fe0580d5cb544335f150fd02d63988ceb51d4142b4f4456f0c4dfae26078b1aa27b3b3130eadb8c22fe744ee052d7cb3c7255ba95a06aaa24a2b70bf088feb0579cbc35c50f2f349bc67328b31664824a939982a96fb2181943e8878dc5008a0d4fa84b69e6500ae8774a26142c95981f84ebb1b61a73896945429efa660ff76049b264c3084d600560a2bb8e05181c59fad3819414361b44c9e04aa746faf8dc5e46e31160ee25a995134990855b30b90b722d5f6065e701f3759016f375bcc108e94314add5bae26db9590c85cab14eb46300ed1a4a3eb96ea7aaa62f7e26c7e72762cf05e05b5203d74ede52d615e7563e2a42ee5af4a2e57a7f8572889a1e3b1a3bee4ddd6691b78b2b422bd151e7359758c19fda9a058bd221d4658664936c714f1bc196ee91aa977282c698d2b7f89b59d582c6bd1a94f976c468b8706c831907517325595145304805cb10cfa62c3b49560015810bba40510c0d503b9dae69b8c9c3980237b2e44937586d7f83c1dc0afb174437b3b9aa91619ae221e7c0e434eaecec1ddd6192ce6e7b4d033c8baa11beda37cb4fd2a822d38cab80c7ae89fd3b77514ecf856980c6bba50077c268b93aed6817a94ce7ddba0f9f457213b4804ec6bfb2ff9501831a907a457982deb1ae09638481ee29afb4864ce3a3ad8c03218be15d50cb7d97c7e66af6a64cce3e797e9388538738867f0991cdc8de0919894d477494a8c1f0879f0a1497847e4ea8ac67e37867dbe9ece219ead32b80c8e3eaee6ad96eb5a9cd0e0d61fff1d1a86189bbadd9e3e73d0c0fb79e20c1c2c541101a209ea59a0f477c3ff3b53ff0b8bc1e98992ad45c0e8fc47d67e8ebd35c29c274c8da977032cda653f9a4807eb3ef0a42fb92188ff1fbe6ba1e279d25f3fad98bea61d3686dd93d7a7fd4a7671b89fd97754c1066d3a590b56044ec6940ff8093e22fad6bd9c265cd3c9d0c35e7cc3ec1636f21a8bb859241b57b94f3a028b87da8b3bdacdb70368bb3aeebf6c832262c5aa833576105ed2169c871a6239bd260d2f85c4f42e43e202ed2e4bc5a71a3dc5804351d1d81583b410d06783521f72d0de02a1ba1f4fe01633b7c9e3178b60a895a97c80cc8be96714bf97f0ccda3b785408b40e0e36859c9c69b531939f510a01bced87929be3382ff949834292608fce1242d2ff6687de8bc0d5dfa260aab4178d31ad5ae2aab0b26468fdf8422087bbc6370cd10583d2c9101bd2cdf3184fbe0a455d9f988a175fd20e5da1a1075eecff5af5b7e89fb079a8d3f3988be15b4dbc206a7de94092155591f4d0cbb2711334eaca65bc19772ef27534b0f95d5a60bb22eeb39bc36a81dcba79c6ac2aa6dae24551edcb82905742d71d593b0b447127c2d63b3a2bf1ef9733d22d61a216b55f4ed6a899a8b468c5f162aff9deda7a02faf4f159e307bbdf4f488898b7ca0147c42d2dd88b4ed2e5f4b0bfcfd2236aa5ba8a8a13a8e943615c9fd032e04e8193feb5e1d16e324c838bd724007cef32a95622456b4a75df83ebb90e6f52aed4f05a2fb1dce9717e1d465146202fd795cf79863607f3948bdff83690323b62ee458a6f4c2ac5f559c85c45d3cce6167869da4d41bc2dff99f8ad3b10fd033aff30732914ca16ed3de7da6f3445cb011d028b91fd3d7d8bc7a342257e366c1cd07a6fdb640110a976c557ff009314d949a3f312e27cd9756daf81006b4272201020dd5c99d54c352578a51657be84b96f30aeb1e6ee7262e3df4590143a4256f72d53b393d388280dcfb26df8fadbd61a2b5b4b9941b5aeccae950b687475e9a1ade216ec77d5fc2376d19f349c24f262af3c8330d94d1ff4bbff330fbca27a08acca70a5f584f94fd07eb1223fd3981dd785b571e31ccaeebff8ad14abefdc05c85f3ad35f19b9849d62e2c66487da4c87e35607603caa03a57dae5e206222d90b414ae90e52f096b4a01d4122172b7c841621f3dd3fe09fb1ae1077459934fe94af97960c4b017c1b750017817a74aa083eb37019994e27abd2a6450c353110bf771957194694628d5b597d3530ff93909a686c406bed3a35baf494024ffce31b546616a4c0504b141dc44ea0c8190931b0e071cfb98c4733244a537afb1fa153e5b213d5a191457fa7a72b912845bfd0834c7cf10e13044249c4a286d3ea48191e3f2711e808e18e4dc4e1b44706672a0b8bca70e72b32137d82c07b8b5619171a8a4b06c837f1a48f92d731adcd9cfb8d98c1a759a6e7815a8e8fcc03b526ee55ba33e0ed77280521cc53e1c5bc49aaa8e9963f73d3c858dfa41a4092f358cbafd6bb94063733b921f03b7688cedd4d2b4e51dfbc7cf4e103e772beb9ecf1bfc4f363232df495c7c32878130798c1a124ca2e11bee336535318c2711121a6f815bc9f38a3b0464e832f8d13a43fbe34fc3768aa6424e493543eeeafb9cf0815e04aca7dfdc0fa59295be405e23c24ff154d6bc1f9752296f84143b7c483fb76fae23443fa670069bdee20a0cb8934a6773f4f8bc2e5092e626fd523cc8e41fd4f1b228aad3de6106aa1265c3a31444a1452a0ae8c042d2be8eaa3a3681c54d6e5f0a1394a4d5443c440e0a9b7b88e09433dfe9c14b5109c2f2019e3be0c8ca66559d2267d6dc1389d20033bc05be40ba82463d9ab62454e18cad776e00bbc8a7e34cf5ffee966e9c0b85b6e5c7d48a8f02846d7a44800cac5ae416c7a14c845fc0da032c575e3a0b805821e6e5b55715c909fa9cd685d8da6d3941906fab22824c16c933457be8cd72ff3926505a8a51ff400dedfe4f24c5ccd473eb966c8647a8f5ea4d773c64be41db861d4238dfcbbdcf8b92e2cbacebe2b79149adc3b5a9d70c9127be7c941e5e751cdb10723312f90dcf5fecfcf56d81ef9ef4e63ff1e7f6145061575f8312d500308049a01c367e7bfb604f14e2936fd582869d8987047f75960f184d0e226854a8c11f51eb4cff486c54590622458bc32d7026dac7479a01523b677c77b935df2d6a2c979bee2f46fd2d9587e39479dae874f92732bc885b9b60088e51a9f15908d67b4188b8f1c61491bd9bf32fe7bd082c274129b214b08ff1b50805a4ad366f79ec96eecc90512f5c7f2088ee5868b6811f4ef83c17bc25d08bbd9ec43111d057ddae714f1028571c0cfe054b8c780334c876f2b00c4eb7d7415fad03e5a1f48a2cc1d5ea86fe61937ce764e4679a0958e3a3d02ab9b52c5336d6b3c445f5dfbf85071d498b70073903d544fb7bed66d5c596360addd33383481b1d48f2e9b0b9000a099fb007e3b8eea164f6128b03e75dd17572f9bbb01e83a3ad3a4137d6cd0f71c42113e53c6e7c78ecdfd18dae9e89083ac9241aa2ca96a625246a7e6665beb62286f45f04ee7af5e9e4e83ccb5cbd1ac83aefce07a624ca26f73d287d7e3f51748e682bc07fbc2d6fd8d33cf95afac4dd365773f6c1d8fb76876f111329cc30677811d5579b59ff00b1aae75c2215900deecdb6e6a05484690d6c47e80dd2aacc89ecae8272c3586408022fef164cb0a7da4bc97bbce99c8ea3c357bae3712c678c2e765865a65f659c5ba906e82dd1dd34754370012412c9cb21f430f94d4e134d3049f88b9ca1e6cf26ad1d20d007d57dce3e155124bd90e9d5eca0b0a823b91d6bbe7a2fb0b86127519bb39a5565c6f80090d3ffcadd77c4ebc303fed2de7351852e6c4fae0d06ce66bb0905c5e3232a2f556b55b29f0009d45a48edea2447c36e83dc1f49a2cd13434b66652fa7f460594a420ef9536fc89c7c6f1aeaaa8a4f8d1eaa840a0d33e750a47a9126cd4029d295eddcdc298df5b946bd3704f6ed57f9a70d9a1611af615b9883b5834472d8c2fe47e324703ee85db2a68955599740cad8942fbe7bc73bee67bdd27d670503c0bfd13c5ca966de61c0086ae18a6af37292aa45e131445246f4ce391052c1076e67cb73dcfcd376be09a5c8e069f45d2ed5829421971229d20ee214b12ef5813579790aa8ced23494b6d5a460d0d2ef7b67c105e5b87a67420839dd57f3e4d607c042f414889901c41de995308ab01eac407a3828e78c542ae133e5d348a5ac3385e71a47dbf3a9178f9b4321cb16e47c083bff20a5196030bee1cf7296236f491de4da7a0b745bfa09b4ec92be663dc63da5e1b01b199dce816d12f76bfccd0f7d2d821f04f82dc1e2dee1530a47ac36d6c098a7772240a3adee7adec30f3a88c8dadac1487f9683987f0acf5eb2b31f699793d5dd762451ed69a7f8d7776d030efb97c60726f2835d595e8a6e9d5e5237d875eede2e46f0ec168c4d82a4e1459fcae9f4d9980228149c6cf5e6dbb90ba23d943b954c4a0e5d53f15364e255bcbab7eed0f87b6981c219d687448555f1e22c7b1ed6cff43e4605f2774e824dc0762a3cc6f50f3066be26d02f9c9bc71d17916863216db03c0e917ff1a12a8eec20d05b223cd5421914eda510b41f293def42ce6338330c99d0f1daeaa699a581833e48dfe70ef182e9c93291050f98b99666d4f8863e38fdecf41bf7104561dac21ff43dbbcc6ff9dfa16aba8201b4f7a309d53dc8948dfaec13afd82bd055f0c4bbc3303d935699b6f7d0e5383e23a323f3df229166036b23ead5976a4757f054a00e964ad0212eaff600bc2a270e0b110834cfaa3fb961a7cd20e9526d04df745da142a409b0aa77902683fbcda18ecddecbd3662271ab8ca5f8e3fe29394d8fc73ddc431ac8319a4485bd455c9019691ccf2d572511df7bb11809af5b9b1c7bf5178481e291aeb525f117b7bb8db0cdfe1f1914ee23fe0b99119926dc1154833c5751548616fd003755a84b93d30d2007cdb2f94e55a57fc3c63acc8b4404e5c09b9b37b4c8a2da4a2f7c42409e4de70f9f6b0761faccc7aaeef1c898263c75cd71157f76fc1667210f77a5eab0aacd3a914a4bae8819ee9696659f05680d6109e0b623c0689b7fa41b817e36ce4a0c0a51ff826418bc48f19c76f5a702a139cfe077533daaa473c48e05145c411bd4a9c2c4dc12e9e2192935268a75af5bbe5cddda4defc1d25ea735c1c6a256bd45e67a130229deb97cf568cc91fa6e12174f384757c292791593f0e06a53de82ca8a4324d648fbfac19a909ef241024f70e3c633cd9b64b9e93737703638af7ae0ef9d42927feb0e9ebbdd851b4d73a42bc04f21a5556427cbc892179fa9218dab4c3bef29a67dbdca28d4456f768b13169f1065e0bb2206839b202bc1f891a8ddf982d2207c226efe13cbf27dbe5a7f560d3ac3bee7120e0fe211092ac1e1fdc7330d7f3c8b71f85a056eb88611b331069a70d9b9430dc65c6c2cc81f4df64ca7112b04bc85a404b6f43df0fb07ad6fa5384d59931acd6be7474a36c0c039417d585f50ff9b18fc5cf20b880382221ede50dc1200d3746cfa80d926913117de0888df63c839d18936dd80b82dc3337646f11ecfb9e0066d9a3f5a115c2981fbaf00175380b6f3aa63be980bdc60c2c915df88fa0103eae256f8e2ff6e546dd26b6923192ac74a4fc28423b3126bf657fdc8821fdf9f6d3c866f9dcd9c4453990a8d583aedebc389f304432faeb4bb3197b7e9309fea3e99aedb47b59643b03058bebb3662a6f150c754f04437053f2e1df28da4680bf3121d8ab12f792886dfff7288cc0a0189cf2fd37744cf4b76ef59f2b45de4ac8395ed064222a605da1f392dad0aa0d3e6441e70f79fee9f98f605af95674a2ccb4705f0735d464d357b91fc4c33203b8675eb11e3d1eff99ec80278de7f5b270984c148526b0cbc247765d63d6ec6ef8aab2d488393a012c82226dc2f71114b64605d5a00ae12f3b891dac6894b567e3a471ed6726353c6193f87949d3d101562c74feb33ceee2a074e6c83f01565ed7fdff000215c2badd928803d301922b16f2024deb57954d3a9505e3864f58a0268f2bdf8535a9c571221d5b96534483b5d888f16e371e1433b8ca5828ce1a52ea625e3249f7359c31413b478ac562a76df99c4d92eeaecdbdf7d1a68426498230f17fb96f2bc2b2af2187e7bea48ef740cc647bd9d367410ba7906c8152c2e391ebdce690a616b4103108de4f90fc8cee23cc69d4aca7ef075075e2ec86e10127157e25bfc06155e613d670c24f7b7b1c165b105e0859399bd4e3a2cd11bf4644aef7f87ae88b5b66d021397ac6a6391ece7a0e1245619efc4936358dbde6b690f6861b45df9a52949a17cc745a2ef3d0c43bd5e6922bda8bdc3fee244f2072bcb1f5c6119fc51835c11205b991b3de6acf7b4551a0b37756311b037a0c1adf0aa1fb04e446a823dd2e3688cc4e406e8be3ffeb234f8088073a1c481345c51f980dcdc03174ae6151a387a76d6b6b057145767e3c87710222d4735067bb77a0a43871d861d1554a766c050f954e0cdd8e853376a7eec05e6d750942fa5c140b08dedee36b64e7d662ed67069b8ffaab7548f616c0d0c43533a6438d81344a4991bea409c966a0e00ad9e92caec8d6433db9d8d2ac4ad6a22236b36d7dc95742f506535a64303751b633ffe10d338548fdfd7f51905e9cb98e38ffc144c1e285fb3e58ee09a60c6f291228ae9c10ddfd128725e8ac4839ac8d049a07460a752966cb030e04a4e8005c04b6dd4fd73ee0d6326a78d0cb1565dc27931af4a80a316cb79522757421e693abc57f346f79db3a06bb9237a1ffe6c9796a4d6f36f951dc2a549ad99d3bcdf8fe782c74be2e7abd43ed583c057e07fafe9259674773ad666180a313a8045ea1238ac4032a95af2557b7eba236b30a270dbe76d9c64cdae7196c07f3cc4dc805aa32bea122421f761c38cb9fef1addbc34fbe186e34f376c3b528e3f17f8cf9c24e98b77899e75297f81f663d5affda951219029f45d69edd5d80fd81ae64eae843adeee698673d7b447b26e38597fb57542f331c5b850a64d83a73676c8c81313560f04fa5a4c6f7b008f084b9400e7f366e66826e4030a471d5e4f44dfde244525c2f9daf230ad55007dccbdaa67505687c00a4529da30aa96fe5cb05e3988b2c53762c61a77246f8a5d7f6fe7a39e7d164860926aec2a9f108cd635bd908b5abd5bc4c363bac5e858c1a55490aaca553bac86c9593f5b602867f8971822d3712a72dc268971195b815adce19621fc58fc6633aee24d40cb8df83db5c2f89c611bcad47aa2739479dbb6d5c8b5bf050c8156517019890a9821d114d0a9563deef572708af3ec5358278b38b4228df53cbf1de59d452392f69fd08a875c7e93ebaf918248840c202821ee1fe8b3a465fef9df28b2651bf64986b320630f5b46a8b0b6c32cecdacb79be7fbeacaa2410a206662ecc0c731b2832b6858369d9128df8563b89b39b1405bc09d98373c426cfad20cee99ef4aa89554dae017391e03f7f886b557d4d4ed110f6ae7ed42d9a3f994cab7c7223d777084f3b76c95fdcc4eaeca1850d580f29ef981c74b2dc24fb3f808a342b5aa75bee8ec489c52e61321d4d94624ed55e7c76b53bf9fe2511624f622c07733d69e92829ed3f347ceb2fbe45398ffca46482160846727a97b210c2deb50abf0630e5945a3eb0cc3cd4364bd02daa31eb7f6862daaef5d658d9e5b60114b2b0f798857f37af983dd1ba58c118bce43e1c3b424dbf2979a049e3caf420eda516c9ff2bdd9edb6fe62b27b1735dda72defe8ab0ebde435ff2c6ab2dca508dd571a99d746ce98b58201bdacec66b88e453cefe669dbdeb2b68c04388f5fb766f75139e3671032d71bbef4e41d13e721502d6ed724e6128d8362500bf4749b3cde056c6b12f0c9d4d2e76d12dbbc76ef0ce031d03fb747569e19b6ee765f0e59f081d0e3035c04b9f3022ce8768212fd206c878ba5a5d134e412e3a48e7c886c5ac0ae65a6b90a64a466045f7b6dd344d166c7a3932ad46055f5ae7799fa3358745e9a3181a8bf0acb86f530ce004e308d337618939b0ea8e31f72b4c9d458b3735bc927399390b244241e5be5b6602249ebf8cf00fb2bed9016322d22b06e48ee0a004e21301bb86bddeca40b624befa8e42c102621cca5dd040ab582aba9962ce5f6515b53bf4beed8f511b5a0e360fa262706e1bf425987af1ea7c474a74e4c1f4afd87b41feef484196de3d74eb0ff510043dd025728976f3e60e7347ad8323556c5078a86fb98a44cc99a6586118ac4d8395cce2443c01007c04c25afcf4fcfdf3ecb1b1d1bab255ff565fd0e27e40b30c18dde9e6f0ead0cae05e016812fae08a3b3afe7cfc978940beb60b88b0ce6419878491786e4df05912ebd0570dd85de52837181ebc765bde58fe6ee49c0605add7225a2f64aea72d75bcf7ad316f3c34c5e38a6156396c8a22e80246255dff9952a561c408ea28e7116511c154df40eb9da4bb24a4a516c25da331b4b651131273ec21e7a2d4999556895405578907b62a18a625a741fa196b238676c4724d4e164732cce189db6fb78cd4ae5b9957c359e944967c65522dfc92953a35bb903712763f8f93fc9a9e139357bccce9907c5c0daa06a0abd0e2f18016dbe246b116c28f6b5cd7005b3072379f24452a634c84cb656ae35d2107b80cc660433ebf4cafc6705af8504b474ff66103cd03aed97bae97c53ff582bf8e44ea2adde025e6894a93319e48989afc93b86d6a78ef1b1272d85ccc63a64a34bffb265d200ef85327433194bc0394698206deabd3cae3857a80b61caeb0cb912185ecd655d5245f692ebd0f12b304b9aa87182ffc2b772427c49aeadd0543f35a824fac79fad31f3a434f5f55141d12362ad5cb47f444939beab22505e277b9fd82ff4f9cd6ab5d9d926501a6f9974cacc176ba116a9cc92867581f3ed147893d78b3999f170a12f6b263fd5d70ace91469056fb67aeacedf4c563b27f2a871d1cfa377a7baba918d47334afdb690328c262f8288393c0004cd5b346e529b998f347d512902fe5ae09e334f145c5363258d1de553fb181fb86be30200f8965438b02ebb4866551eb35c359a3d55311a9059dccd463008e760ab5efca75fca8e3e795c598255c4e1eb7ee52db46e0424529c039528a362af45218d7a5d915e07b7d60a4b764d21ff86571de5ac756f8fb918dced624c9b9f0c27123e9d2ad310f59093d76c1936d216431547ef0d76a8b302fea9951e94783f20ae4fceb6300760e73381f6d7ac463f58c1f7ba05e5017be0f21a46676008e22757d1f407e7a42dcf9b22de0c9146225ee3aee4c9768d5e912b8415ff8b97eb50c255cd8d60eb5b747bb679c9b30e13bebb8baa800f2c9df9f2b4595ee669fcf3d68d14052efc032edd9c827c752a041a93e38b7ce35943a6aa8197022a2f68011a747109093206e1a843c8a0595c117dc87b1a652f84ab29591a5f3382ab81a5593175c23367a8947a728d80f7ac2e3eddf320cdb09691a49fefc58716245927fb66052a65894435825c8931f6404d28da9a85a19bac9800539e41a5acebd1353ea8f74693b9abcca5255ef861ce57024da4daf1a5eb57f1e402220499a1f261bdf352c5466708ccd76043a0e4d6726727b6a4dcbd1fd148146a3ed8e13f802529a2db4c924199fad4ece0163096b82ea2a6263cdee2a908e326b0ab02ca4036af012900a062989e88b0ab42c42766f359912f32416b769311e58ff6afa2046720f090c985d4b465450dac649520d849d0e82f7dbaf5b157ad9b49cf4386ceb4ff6d07e94c1d69339b862b54f749bf618e2dd214ba36d2102669b6f9fc474914ff82f48f88d7159f24dd758a748ebf50802c9c724dd49350c7b279116209beae43ea6c5b860c6d19992546886532f68c171fadcb8d0d1c0d70379396ff2c194884175b7ab9d4865a07b8a29967c9a5269c42b122aeff76ee79c86c09784ee91c653f6cf06ec0869a1f0369c4e91886bcaa4577375365cad091ac0f615447904ba174167d8b79933ad4e70aaecc036993450481f3fa0f8daca0281ff9945ce106505228dbbae033a56e5bf27711e0c5fb01876f24faeddef98105317a6bdbd068ee860f35643ca26347b0ca94524727b59e3353f91e7feaf48b100f06e855c340d5f576aeb2f001d3cc7547ffb2c3d38e1aa37ed1c520fbbdc8a778c239c7404c15fd7e953eb6b9941f7395f18f8a42fe6fb549aa9fd6d0b67ef7ac6fea5854ab0af61605e38bca32f2b5a7f0b2ccd870577af96337a22b69d6c26e8df6455de3c6aacc25197cc410ef77a3735cfdde647531fd6445de9fd43404003ab4410e9aa817915cbd2f95eb4e5836a62ceaba1d2dbf130a7bf500bd9dd5bf05cd4f985ab37bd6bd87186991af148453c79f1e693ebf78d9dbee4db156acc1f9245ad980ba314135e460fa89f0b251cf40417e9f674e4cff9fa6b3aa44167a63aeeedfa21318c685fe20f4933986471e2078b488bfdfa9f861a34d22071163059c907641a21b60fba1d77ee48f27f6ee7ffaa40f11f0e7045456eafea3d3bc2f5f3bfabc40711863f924b97a7e3255a1acb3b594a08ce4df2aa9bbe7a9ae077e02fd54a2249be4cde206089a82e16d5bc2f729ee4f9666118f8091dfeb5bda17b64103b86a48822b96a18dd3a06cce775548ba1d4c86f31c3e65f4cb66b08f4871ae598bda83a08a0583d0d0e95f82a9046f5c3b85f280682ac399b32d017acd802a2a8613745095c94e1fc9977cd945575078fee3b08060488d554cea9d6d7e86b3121260ab602a40e5625573c17b5aa354b1a19ee9ce50c3bd089fff1052986cd69deb02c7ee25d792ede5de9276aba3e54c2f59db844e389f183ea584ffe2842ee5c4a7d081905cb55f1cf779158da2c29b22282f012a3bd08375ebf278c1c089fa180302037acc132ede353b0afcf003c01a96b6b950f22d940d6bd814f4d009a6ea84d27f21458dce625dd0505439df267d23900e8998b164a5f1d50346f32cbe702309d08c7555e9e85e96250814cbfcc1062a41352cc71da425ed81930470d093008f5c0e3bf0d371641e2529f38c92b84702a14623eb5f12f32d59e560dda8f8059bcfcb05fedf5f720b3f2b3f3249bf9805067833ff1f72dff685f00f78a3aac7a111a7c4722b8c0b8b35525ef9c6d14c41eff98b0b8d60761e00eae9b297f6b186a1d82487813dcaddbc4f3060ab6b00196b138756d4d05a644176f32d01b76b1a9ddff7af652c89976a284f33bad7412ae7f78f99cd2b850bea8491b9a22721a882c6ab883c1c9862efdaed771bcf463a44dc5a76c946097c762ef58246371fa473513219bf4a977e689b2b5622ec4769ae6e172166ad9e42e01ea0f9e53b54cbd8b2f9910095ddf640e51f5d5c84a6284656e4e75c86a57e9b6ae0a69db93e1a3a54245e503f327ce8679e5cbd03d4f675d81b4a10bc343b52072384aa181dc45b877132cc90f2def8fd333363b29fbe75aed229ea753ac8cf9292a2abf783472345130fe5a40c057e00074400c8c382999c6991312ddd0e7fae49665fd2c35c35a668f7f52e82545da99a12827a96bfa039e1d1e77629bcf9284d8adf7c0ff34a29b69f3c42e955c80c195a710540f56e1af39298bed31c059ef6c79cdf9060ceb5fb7e00646bc3557ac90e965af3030bcf578dc0b83685b720675044041900bb8fdba46ffa53cb5fd9cb7160a09f1946aa4ed17c2684beb3c033ae15f203afce7fc74e1fc54283786a8916b4cbe06174920ac00176698b40866a3182c3f7482a9e62c7e3f278989da0b8be188583a8e05db4e1d8ef84319ad49c0dac45f0045a37aee5fa7f1ddb6b4fa756705e1eb2593df7182b68df18a34c342bf133e77638a8909b43c0892a0f69704aa0dc189bb210a360f65b47e4d03f000960bb7c43ca1368f16810590696a364e6c8f611b8d320bc2b949df7709247874c7e11ca685d856d826bdc3acb035185070b923b937245ebdeb31c3a12ea4953b45e32732af8c01ff2bbca5369b89a26bef5e3a1306d04cd6ee899370262a25990ac785509d192c63c9a7c86caf5962e3a22162e88e12f33c69ef5f91562fdff32ca0a339adbe5628c3f57c047f234e5f5ca47490a359f626eae69d60f3879c527cd1f0ff0b8de6b4d29970a62111686c2e4859738e914e4d86067a72a22274e7b3a483055d5ac5328bed48707f0043fa2436e490a10b0f6d8427e40ded2993813a67ba83b0f145124e0007716b39f4dd8db7caf7143897c819716aab82f1eb18732e2e03cafc17874f41752e8edcaa4dd11ef24f354df4a2364edf7b4b593b469da50ee7b65f4c08abb5091f20557d66dac20451e23586c662a076e04a906fc94587c8e1b40008cf872c1c337457fca90fcf664a5d612bffa9c17ef051612e0347a4aa786836885d4d230c485236c27adbe95a9d674e50a098271af68173147a599a178a635689f4e74bf6b4219984a0c4f3643fb1eb40fd47b966f09e284a58349cb4c4fb1df51b1de7e5bfe3dc25c71661bf3a972703f07a2a5011320336eb3dedef2c71effb1ef0640226fea9e2011bf0052a3249d355caede2407cbe6726ccf22b98641d618c16fd159ad588ff41e0bd0ec3b4ec138de18f55983882521ff5fcd5349ebecfb77c31ee3f387f49df93fb48491abd24ffca8d26e62edc0f8fef28b20024c48237da6c832d980054176ac6de1625451c934cf89c2e9669305503ffaf50a04b6867562f89f315bb9d8916e44517ad71d2355476b227475a92f309aead4a18682de73c7b040f5b0badbad465e14f3e9f02587478edbabe3751107f06aa6bf2da4c18c273ca774421c4920074d454074d8d7070affa86f09925f4209707034d7750a3d75f55135d1f04dd3ec42ccd17cf0a77d917e55fe9b3f60f218dadb4a429f2f31083324028bfb5ca357c19faee99f169ba2ca4cb41e2f272e43905f7caddaeb6977e674e3c44f56fd582f92c5a018cc75413588460a53f5454600d972cbdaa60302c92032dc82e423082aa48a384da18edd1f3f98cb1348ebeabfe33687f7ba74e2532174629afa1e31ba8480f024964a7e345ccd1fe202046f246c034c78a083a687fb615260d4c101239baac6bceb773bb4755af02ccaae324f30598c5474142cd6d01c76f55e5e2661e3f72d7a7a40a085d1d9e7d40f3f50efa1e6d9d4cd5033e018d4cbdb377acb4f60dad0461bff9cab3646186f16662473cfa3a41f18bbce0e2e25a29caec8a40ce6ebfb04522c094469d29bdf59045f577dd5ac582efafa93bfd32650796a189223f2f55a8e9fa771aec86d0e7a4334ae1df6e607bd09b0435ce3c8a13a7e156f6720429a4b6a18532af2bbef6f708d48a6a2894a4c03c8824bb7194dab86285718c777f212d53e45af27e79d3834a084bbf5e380297e2e5531b97cfd2464b19ee27f0ddec79b1d45df9fee1746816ab56727562c2f114878abc48c1dc30495342715363a2c6ce346dba73331e67a4f359ea126333ab478bfa5ed359813b9acc8daca25a08c4a9548b60f159af65569074e22654ee6a9f17f736df75628ea4dbcdace912352bba2d86caaad309ee58ec1eeb40f1f914c62ae66ceafa68361fc3035c7d5685318d4fa809940a40b0d358a9e836b4a654cf66349d93189ba3ca6dc5398a1bfa1572139be5d44f58fba9fbb7901b704a1a2d83ad59b4788c29b9b8a7d5f2470d9836e915590be3a748cba5d8ef84e4ce19b45880a64b680980278e0f557ea26e7ff3cede135eaf2daf13f311e522033c48b0b9617b3843f545e5529df4564b6313e94550f55ea8df6fbc5515a5a5e103620988b546c4ac53774d68947ed855b3a06b561197bdf0906a5cef8f38f491db6677bb6167fd5e1bfacec456d5ae5d2af9df74ecc2c934f2a67859f0f8b25a5917c749f3b2e19ac01705e54a704431b1f2c759baf6c5e230cda4bfd9151a0a667c2e2309f158835cfa5f9748d780ebefe5de1e26e5092be5ae33ed66131c5dcc61b1b48d60ffecf5335d64f728d716392e4633a8f6f4f151ba7730524adcb4d6d595a2232fc06cb73650955f95a4b337afc1f875c3b01d7587933511657594f8aae8ac4e0440afcddbba1b4de4de3b7b4d041c4c9bc3e90951da3af6e40fa1f6f911f04d76176340de35db09ee12fe85700cfae901732a6a74c040729a481edd9b79f35093dd3f5760e9176320b369738790a4cc2af6a92ef35b17a62fe7205ab03b38ad5b02f40364950be4fdb526a5827b57388378098af4746b8a9ee4f1bd1ced39fe42790359532ae3ad99841227567971e72aeccf9c42263519b8c15bf9057013a6b233e14c79f1a65b20397ca37d9341c670ac906cca0d2a0da02ddc6e57d839f9876bcde78545cfee4d2d3ad273dd3bfb5df053bb1e0d15fe4efcf3504060ff7b91bd42820ddb87d7f3d872463a31671b2c0dabb7f59935db0c6d0f95b46cd0fc1514fce80899e3c978003360d7fb15d3ca881a61a99e0807fbc0c037eaa15c024d61dd125201833755332bf0d3193c7b5bfcc387b7ea1ca28f2147acdc071ef9e32949942b296ff3979a2ecdbac3f30bb6c679814333d5e40fd79bd6cb3ef0c77adfac22b8412bbc61174d349f8a039de48ea60991a339c216cf7fdcbac3a9f7250b4e4a81b03e2668b1a24d9664772da496b2448dfd3a701573436fee44e965ee47a2eff1b7b9a77555369939c99ebbeadcd08a444a09dccece42ed7406898f510dbc54daaf7653e7e3815824de01cf5c105e71691a6083da0650df9b566adbbe499622eecd19292e8f7d4279b31f76d4513b85d156eaa5251f86e375128bf21acd4865477e11bd518a48f15d900762e1c6ce4bf7a01f446d113e0c5b3a971e3e836f81df4b19626907c981794f1f3a45fc936b79433debc6332017669668dc777e4ee8195901ec2486ddce7acba87a7818665098b23ce018ef8ba5f76a264c04d27cb73768bc994372f98a915bd83024deed3abc9ce9d59fc5cfcc2f3df7ec9c3c2080e173f6b21c12473cf35f8eafa77edd6dee8a79fc6ba9ebe978a7761ee5a20329a4d7b5eb18c5a8c85ade05e8943ce4165d1147e3dea2d7a1fa3d6136fe59123e7008c9ae369233d0822e0a6a32de83a4da8f925731c7885c26832895f64102071add02068706fd8dadb6852ffbef31755b826a25ee7e2e562fec600852b0366ed86687f5b063565820b87b3f1ed76e2f7b611d62771c62978cf53bf68b2a2618c759abc56b1ecf22a42bbfe4feb3bc9df812d7b17ec242c2784780418ef0cc0a690528e506ad989505aef8acb114a09c34ea4bcc8b9ab7a772eab1913a57f6eb1fdda5c3e2a52567967ccda482771484b638935e7a5b5205bd8b5cafe5f2662cb390df42f52d1ea6ac81394e709d1e79a66e934afa1f8ca1e1d1b1f0d891316dd1b4df2248b7038b1e716e9eadac663f54cba12e0f8d563928c1ed46ce06c10b89fb1a1c7a0f1e3accf4c9e81f230476b19d371290483b572230335979c36008cffc68303cbe8b9715e9b4fce298e5a5d414df0ea9bf8339224cd23dfd424e6afb8e1d1ad3b38ff05fa97de4c3527e2b76a0b9ce5b3d2362be2d2bcf73449ddb4aafc4a69a4776c6926c7fa3c7a4a3d076e141c4f7505d48527d9e4d68d578765bfa42397d86ab789db1280aa224c4de572a2b11a78462e97e273efc5b2c8ba4b2b45ca57ed1dce4f0bbb23641ace28cbd8f802ce5635da2a42130aa425396803863e51433cc3f5501209ca1412cffeff488657a02d2a9d36b3ef5a605a28761b9f319ad469b583ec75f38a5754ed7ff69201a32a0b0304a3f120674020990156475e738f472d95e4297e64763e828d52eb4242138d62151cee777a2882b0bdb4f6120338f7575cdf138c1b0c83de0017a46c3149654869b8cc49baf5c4a3f54f172e229fce1c0615adad8161013dcc6275e06f5580f65ea307dc95f37fe3f8aa610af9a369278ae85104c2bde8f31f6337737e772a2737135dac80a45c27d922f44f814c9c9ca74815ccef68af119843f036d1fac988d815acc8a89a74ecc912f4324a757547b11de2770e39e0df42313fa6d6322f26bed22b124f9f2c2e9a6af87495817f4fce532d1b93359a403d218fa46a3c3fa56f255a73f53aea0d0bd9a7825b75120da2895c86b782c83d8d5dcf83276d470cc2b7df36df0d9955e6c42a20f55e31307da24bc75ae3f77879c9283b9bc6afb533e6a74186561171ac0d97eeb59bf2bf7d081d4f68eed0f1fc58f8f2423f3f0fb72af27f6cdadcb2054f9ce5a3c6a958ffb51015943433102ddcedaafc1ae6d387222be3f8d15c5881d1d82b20f7f4d51eebd55d91f857612956ddc13a22eba1eaa604011ae658cbe1b252f74c72a6e54d7f2740f3faa64d4ab2e454e84db0137b146c3ab9f5ffba981d61c4e46aecdeebea10e46d9db6cf763f70fcad670595e24792c89be35e7a3fb58dae30ce0ec1fc27f6bb6faf8beeb21522249787e157313df06742d139b866db667e1e2a9351c9cc609f9cb13ec6fb6ee7c3184e870c50d246715d5932263e361ee1ae387899165b3493df8f25840ef1cfc1f1c6583864436e0aafc4664a576ae09bbe90c207fe9062a1add9180524252a8e2db0288af06ca87d6d2320cb198539fc1815eb5ba5085140a189e0646cbac963c679d731b0fd50abb694f46418bee475515b6087d2e78bae3b5fc99f55d6659154b6bdbccba13d2d51d094915ce7e47a952e1aa8f292e01e2c040a751246300f63d722b9a428336491bc1afb3f0e8157a6dc21cf69677fe0c2a37dbee18a9a9f5a5102dba80e8c588b94ca54e8673776223ce3d2c5c290516135e75f2b2e3a1772c72ddea9f4a9799b172de12f14c258b2f8e0a54589cc1f84d686a4643de145d121e942c4f73af58c770646b590306772820abec642d66f37fbc563fd60c4e8ce96a794bba7375795a772f046d0cf6ed98b7d1ecb7bee7a7b5f5401ee80489ef52024db1941d372e782b9195704020488317695a12344a785e602c18de00524d62667c39f987bdac72246fc2c659e69b641993b9aff8fc6f95817bcd005d769c53698901bf8ef100b8704d934d7959903b2df7ec5b77474c568e225ba81e85b54fb3c06f285090df9d0f2958dba442d60cd360616ea2c575bdf2a96f656adf83184040ea7eafef6a1c7622fbb241bb89dd21ae9eaa6ff4fcb9cc000e360a591897e04df842d5d21bfe0df80dff9d55fe21400e982f56ceeba86d1fe83fec683f70b5fdc715ab4e40dfb1501c4268d5b72def8f55b0d4777e29de797b1da2e97cf6b728526660bec27332d60c3235789147ffffd029ebfb141a58425986e4698f0a33b39639e8c72aeed61f9ba3fecd55a3d517202ba018f015c97b44fe090dddbd4823ab1a7a700a0fd46c34029f2149a8ebfb458e7c1b0bfc56dc0f4292fb6bd46987955c11414958da2ec14f5671689a4e0a46310ccea4ffd37f655ca0634896deac2d5ded7aff1b886425cf4af75ef221f951d8627249834d6ae2a779097aba42a67e650f5cd3a2f3c3814b0142746e19452de391cf6f8781da2f870768eb9706a26219c0730a7223b57f65e7dcf42c00f01fe9344efbc5626c04475861e432ed1053250a07c1cd951c326fce88d7ee338f12a0e82435ac960b37711f7ffa2f8e1ca7fe3d85f9efb9f554266904a1f724aaadaccd940e45fab54dd332c3982fd66dba176c0c761175bdf50bcf64c8ddd868961a0936becd1a0e329e0a0881978343b6deeefd8d4da0acce2ffefd1d056992224545540a1fbabdf54f46f1e859a9d8f63adda2719e88c2de0456dbf66a85db1dea2e5bd24d50a8d08b286c14dc311b47029ae08dab6d9f083c1ac81ee2ed847af9f552a9e586edc29f16c08fb855b26c0c47fd01e8032fc2b0f3e7d2daf7490776d06c93e8bdbb843a1a2f6bbb2655be204b74598bf4221f2b6cdf46590d9f8464769f384f85f2adbf63261a210671bef6d2dafb425c3dc019778c76cb2c40b357a7531e1d26953fbbc2fed7d22e20f4e74bd404174b6b716de64e4605660f36a1a220ba3c1b0df9302e4db41529707cbee81676e147af806ff3e033c860436190ad986f3f923c23e740c3a8e357f80d412180f5e2c7db17c3b8bf19e2d5e550719125cbcded60bb57b89f5af06bc068ba31398974b91d23c1b823c71c11a2e02f1bf58b0c43c527a6f6ae692dee766cbf97eb0392fc568e0555896eee76e35e9b9df54e7bdf44e0d281db6e7cbaf74ee4970513fde748e35357c92c19fa4315496ae2ecd9c09db469844e0c1f7f22ade6b70e2105c18e92f06bc12a2ea74a00fd5d60dd7118939a124c20b15f4101700f420c7e944668712ba18c1b371a7a5051532a31b9cedf2dc0cb98f2dfd83ec034c8f57fff2854361fee4b199f05af8e0eac4df5a4fcf84bb218009df36eed81d19c398d368c7afe98a87e22d2aece7d47ce76112971a07526edfedd71059d121d5551436889e725b20debb9f06dc513d5a24df3532f47e05a288f2a70efc143eb4d662bd5ad95fce5bc166b6332d5234015e2982cee3f617c4a7cc26c546bcb612506af9277482254394f3510fc33947f9928ccf998c19c32fb73f388af49a53d3fe73e3fde36936f6e421f7263fa3833a186a1e1cbb922c184a67d9551051e7843bddd35c885af91252775d90f62ae2c286f8be05a6f631f53f7bbdddde482989b1d81a39492c7c78f7573a93e54cc282b0f95efccf4a5fc7bbd71d0cac3afe4f673fafc910df7b8c02f607e42488d952a0a96d1db6a6a60311460dfe558f5efc8065ab884e458b130ba54c501728221ab467167f712e27a50bb0431cccc11bdc90bfed826fdddb63b443a7574402f9bb7cab09bc1316744d687240a73a035c572ccbfd1a4bf500ba2c0ba46282ce6575ba810ee90c11ebe65640327a3e34ebb83daf07c1d714fa6086e28ffc0d500f329faca67eff42d01c3e16b9676bb14d080541a6e285e470176ef9d2070748db7b4efb6491d892dcd5a57e80add388d902f3ab0ad4bd5ba06d8f4d8978494cb6f17ea8c158be8c4f1b893cf001a7f07361bd12a0385f15b157e7391ced72512a76fa8e1ab621d97c2101d078a463ac4631419384ec534c6ce9c6e4fdf5cdaf8a35c84aa18fb93ebbfb8019d0acc65c6dbad50f71370f2e0d8ca4f018699d4de8185be5c5064adbcc1a9389bb3ec6286a9bd897051df8934d084cfd4ce27a628da11dbf98fe221163e0292b613395f819edf4dbad8de70bc68ab6d1473339ecf5e2e0e9b4a6933edb68537bba71b0bdfc1f1527ffa2f0e0022027a0b4a00681ca18db84d8f26b0dcf619bdd8ee4ea0a781993fd60105647c02b2ac323263f9a600e4f3529af6c46e8746c12f2961384409bb7f9d14898aa885c64e275d46d1f5676b2f14d5ec9fdeb6bc1d43913973431e0429cd5e59fa1e47b0f22ef72586560d60dc5ee0cfe27b96cca72e938b6d9c4963ef700b04a7b5fcc2bc49b994a4c9e7832ab96499e51996ed80c53e6507eae08a293c728fe98e00b63b1513b6dd1be99ad4e14cf9c834c439f7f9f3a57a58b871e5e90cf33a6d0327d10140c7858657eefcccbed258b645edf5301380413fe3d40cdb3b9cbed37e15aa9cc9946271d2cc19b09fbabe844bed05634dfd4ca1eeb099a49deb9cbd256547f0aefec92fa706f2eeaf7c0445a73dde2c0a788b0d6ecada7fc1b62df6e15f963da77b10d31b6fa9fb539666fb238984313c20bf998828c8ce48c3f25be32e998fbc95476c3f63dd7befbc7673f29efd6fe678a4f17095672620506298e5649febc8694c57a03cd14450530e9accee8d6e87d968b458214191548e811618730e203728266902deaa0007f71033ee118a5d99ef709a7c9fe3fb2d214f420334ba73c699b41bd02cbf8eb3f5fe94a43eb79358386f1d5c2679fda3a2fd5a5d64cdb7f695ae108b90ee703bd1575402d63b060c89ca9c0c916b6ef61dfe610022427e308af085f17711d898c2cc0837da1f9e1d885438ddff187f2f93daf01973ea76b3f91a95e7d374217131439626705d8d6a3250db2d5398409979e0ff3cbf0835d3f98058749766d7e93ea7a31e6281055657c83171b48dd1bc389be7345178df10b1664f4c3fd286433b8e29f49edbf152193682ce0f0fa155ab1e2758da658d2ee18f9b7345edfb992ea94266b73f97cb7177a5bd909879bfa421b9a0ee97516f5a1ef27badf3b7c95307a85e62553b88aa1597442d1146ca66e801ff854736d581c059c37af4b1e29828838bfa7835f8e4db57331bdfc28a2cff9f337c88ab54d02f10e236aeb4c020e07fd9fd2b0b0b92bd1cd8560c8db3a1c4a9fb12422aa355ff30276e7beb59c10648888d157a99fc0c042b1e7b413e67c421826bb70d7d32baf79c344f701486fdd2eef09291b8be01cb5d742a9eb090e1abd810a0e5fc1b52b78e9cc1ecfb2d00338811d83db3478f3d11a28ad7152851cdb06dd0c2e4dbdde0f33f2ca000a947ca8cfc9fe6d05b8b5a2fb0e193d8a96441ae52724088cfa7b374bbd65c81a1fc112f656d03829224668eefbf3e0684242d7273cd4b5c698c70f630334e42f0f95c8e225bc705d7a8640e7284a6289fba3beee89fec0faea82abec42a9a9a235f4095ad7c461ee99bb7d790f115cfd2aceda5ad069077b9588f42217a2f13fef28b44a3ceb6f2dd1dd7be64d15b1469e7a44323f6978236863fb6f21d53e9f755fc5e47b7aed19aa161964daa72eba597374e8e4231e9e746942d69cfbee7b90411050017831f5bf3fb656e83bac03bd613db9438b23e23cbe803bc9062d1a32ab093d6e8265f318fbb112fb9f609d3213ed0c4c5039b529cc3cda3491b2947448203f9722eee7f4715fbbae181b38aa9b4a80d31d71b722b79b6dc73f079eb912f1a3a07d8be7c3b8a897fc169f5706701b54363aa1495af21a1a8d7aa630b5ae9f990533c15b41d8d56b04f65ff0b0b05819dbfdce26154fcc54b58d697d5fbe8fe9542080f87ca59edc6cad616d3b3445f398c56018eb899ce8fd080a3abd1ce65076c6287a92eb9acfadc4b36b47b441997ea84256cbc67a441ff47daa5e30ae94715debaa07deb5dcdd02dfa8fd1b86b1a60ace55aa0783eb38388f70c6ab95eddc19de94ab8328180d593aab8a398448beed3a650739fbc39eabf12e01f9d63f25307185483b7259d53697e795a5ab095bcca5656d13b07acc873545365214bd4bcabf8c34dc25f6c515ac88ea2e5024a4e0ac50bf24efceaea2f714c328edf763d44fc6fc782157ee7379aa80704109c4d77431f07b4eb538f6d50e6d8d7f8ed36e77e83a7dfe726c5c68fea5e5a36e5e24296d8ea95c54eb3756dee97b493f48ad60bffc20872fbc429de6f013597609d6e20911e7ce7c26f907068fde1dd698c89bb3673c5f758915a79c2d6b014adebb4a8371989e59d6513370a19c49fcd7fd4c1504cb9dfdc58621d642f0b975124d808cbe5c306513a2101bf7ec886c8ab4f5cc9231904eb92444d496f22a9331e9a97e211876c52242cc425193717a4f95f73f0668af92ad4cde55b8871b32159de2328ec6e0699878db110cc5a77cd25836a69c126e907b3b8b6f64410fc49b6b8e5d812618524a2c4d805e72bd95780c1ceac13de7f2ebcdab06a628219c0c06ae04652877973482e6dbf67e602783d67cc84b9b0c215774b9c7d7b5fb5ff8591ce55d357a80db2479fae0c365a0956679790207c4c717ab7f5e6c21d5360fe05d8019a0471f955ce297337e2e009f8093cc5ae5397bf905186d38d894ea50532ffd753ecc645d74eab669f23c51a87c89feaf89f78bcec138664aeb36c72e3db697876e6e28301f5a410d40d83022d79d41a497ee4ed6d439c2d9e6029ce63aa671aae844d47d5ae7fb18fb6172cfa2b4246751a6218f566deb8784262a67fe35e069ca34daaa4f448bbd9047224965b96b105fd57c9c8240201c687204a9527961be5e417ca3fc8f054f951201f8053d972ab877cce09b852f8148908ee302b284a90ec544831bdb2d937463570dfed84f5061a44bc37f09e02669f662b65b14c18b57bb5c80a1cfc56739b8114e40afa7abe981b68c5ec4079656de77bb348881f7ac25474c172910e81af8b27b7633c95e4509b6ebd0695277625c9c0848c428d63839a7a1a24eef95f10216dfb9a6eacae71a3ad769ab1cf8b96babd31edb8a3f1e1278927c8d8413b6cf9caddac227c7b9680c858a3f5a5d509ac9b9b4042378ff61a58d36fefc43c8fa5cab5203595291bcca7b05b10d0383b8d66549d5d32d0819e207e1d7f952f59f01a369e93d9bd34b9f4945278ef9af6528e5742151300594c1daa04527bf2d4ea0d2dc801015a08e59fdaa9e491fc23a667528dd46b00666210b8624071badb1718d57ea918833a8d5b196e3fbb634c4da97c41ee5f77542bc0b302382425ee8e56b5eb05feb7b0451ec03ab46796e1b8f000a1b6294e46457430be1e781b2d790412f982e6577fe3a5399cf2ffa4d2193df51a43977f29a27a358d6c04849cfe06ed16fdd8d83d58ed20e277e644371173e38f978b368936a43af79991c5199561966f27304e3ba6bb8553b8effa1b57dbe16e47793cf44cc634f7879e19b0a5bfccedc01d3f4deb1541f74be415b9ada03b2d57ecb7525d39b2a4d6dfae34a8ee6db66a23cd32bdc75962d700799e1554a9da78013bc5185b0734b8ba9b542861b7f7e4992ac6b7e0783e4575369e7e6919d494e921e7130d8b1f2ec8a07ea2ad1630712c61762a326e7a09e8f97f20b908d7ed6c418e1a34e38bc65abb52ca0d8a4e1b5a68c347d5c45693f042c73dff6af71561e561c1386139e0f2878197df904eb5f88bb616a467c0c79a6f16525c233a58371fe33a876e9433c574456925ac3163ad2291abbe55ab59e896f894d30cd6307064c0ce1309b7df40c4dfab056f78af74942bda06675f1a4d331c49cc925dec207d8e93d04f6d82805fc3ae491f7d421eb1bea9a420e9e7329d488795d9528c72c24034371f8ff2d87857c5e0e55181ce56114f829920b475559813dc76e89d23adfa9bc6bd400c4ffe7b5f5b8f048b7b684457d06ef83ae51fc25236baf0c505b4145c7e98ec9e1fdc398271dc69457114b7fa81c80663c7ed7aa49c853d637e7a6e81640f08f5380f49ec7b62d26cd06abd44cc6d7aef54983a3fe9b5f2ce064f68570052f59dba87563df9fb4ae40c4423daabe9ed1e386bed285d1b8d70adf91d48e14b941a7fdb33aa0bc94ad66b77b5ad5fbfca0e157f5c0f1d02a01192dabca216a154cd8fc85a23f8bff08f8a614bdfeaae2e3ffee35692b04a8b9b6d580f92b6bada16a9876e56abcd3263d9ba0a579309855ffc9be5088312acaf069f97f65ae389d8c1eea49e3086a0461abedf6b661646a08d6514ea66ecc958bbadd3289e7385ae7c6341cb3c659b5be69e6309b2868e22205619c90ea9c26afeb584e1a30efb12b59d8b9ea39e26fe2850ab5689fa80c29a44e54391afb603d5c913a790eec744fc39ec0adf7eb0839f9ab8d44395a365d403675aece5370859da6d280c41cb3b121c197f905b775860838748fba1ab2a1a6d0b6480293d623706f1dec693772d6fa17b05f7136cbd5302f28910486954ad3a44409157dc403238452aee507524bca33a967921b402b108e47da64fc58d42b21f6ec6e50c959f153adab94b68f6594b92be22382dbd8cd7991723f60231821f3b6eac0af58de73c04c4e05c2a73d8b163ab1f77941f47a6e239b2d2b790881e9a31337683e508d47391210f0d01805426b8432933521a99a1b6c3ca2bdd66b62c5f2f9a57641567fe71bd5bf4f9cb56a5de8315335e8f07077889161325a0430ee9d8823a22b116b6b6f8765c05e89432c77a5ffc0308133c147a55d41cae32c7c14a3faaae981688ab9dac9c5b3a8639c4e6f73e9e368d7aa9d3b2cccb7f8a1b8106da397b177c5c31784cf956d6ef5bb7299d8905a32ecbe5121a44fbf146306f70e1c7978f5cf1b5d9a656f7096782b9c86b90422a1de48a7a7b03475a8311bc4f80cd0ef63b8b2717199f05d0a38a62f0fe84bf79c2e1939220bffb0275e3a04f2648f655cbd46b0b2a2e545fa37db8ec20229396f2d35fa610c783a63548c782c0ff974bf787b291b6d2319b9a7721b00b4d72576259e94585affef75dc49adb812bea8846bab50b2e3a859fb8ab2e51396ecfe86dc320c16ee3c419702c17b23bb7ef5388e69addded62e3bb92dba8bc4e3b0492f7cf3b20853042512653e4346ab7d05d30db1f252933f70476b9c3a592fd2cee4a868737eb057a718009f2ef978518cd289b935bce44af52f7f31e1b4c1b59da74280a49fec352061803cf8b32d5ae9666ac219710e9bb563c80126b7856733721f2fa6a4e55b857c4eb98976ce94b29278da1279a8564fca6e2453028dd2e076a5e722037adebe29a8d9ad095ee291b938101a29bbb695e4bf5dd72b80b646c6229c8252716728a0145693b74fccc22e863e2dd001450d3f8d8ec37cb47bde45030d7740ed3b14aa277b519468429913a222cd7326ba73b4f6c44e42095f73f65e827195540f458f9d435d5651fbfa85cf187e9c087c35804a2252aeeeb0a48a9ac52a49ea5176cf197115bf5a47df1ed7a54b3261ecf620e5d4cc47382d664d144d1ca7829ed2ac12a7399336d3d6c00ba5406955b00b0b3b985feb9cc380581711068308277a86b3b26b23a70e80b6aa8bedbdcad9012b7688d25b06830e1b8f6e6b7dc4830303c0f9b66467a91321b1b3e9018e631538519a8e6212bcff2f35d112589815f50ac2810499cdded9b680094d789fc1bad61ab3218067d99784339c875aa03f4d930c20a787ea16afd1edc83502365f178b0a30bd2cbfdeedce74ed99c6ec18fb375d2ad14527ba87cd902f435b4bc4867b51a7b9a8c7f96e541d65da5c45db65a6266f1b8fa3f2e6b6742b1c66c3c4170391d00f5d3f125800e60f22f12840e2d7cd5302aed67c67497d16e888808b0b14a1cdddcbebd040ca53f00c0f7b31401c64b665f43497363e1e80d619f78b596ad07fe4104e278d6c056878ae91af3ff4a79709c4db9695ea3de096db11652c8e08310cf236fcfa6a91ef07f8949214bc56a1152786a55a2e5dcfdd89798862a67f9bde7f9c431869ae20a21963d9e725195496f84bb36fb25e09e57591090f1be24508465372c9d09f13f42a6a8c55849407fc9cedb451778711ea32bb4ff8071e3ddd0b8df0c7c82076eb212ad7eee46aee9b4aaedd92b392c7ae52b387197f3a70d2243fe81b7b478f3bd750dfb8fa33ff2be2fea15802f39ef53119981f9be4550251d28ea6ab1a99db1aa20caad8b8fa49577bf6dbe19a720ad5730def619c0eab2dbed39b9ed37585d81de9327cfcc6f4a8fc6cea81e62039d445ea1d77abe9af35f22f034f06cc3a4c167db027dbfd106ac18767e2803452580288769e4f0e14522e2c38f52902bd46280ff98c4adde6d9eed8c6415ee02d27f5bd770ec30d85faac166add3b671382477cd4c784bea28cec4a92db257e20c06f53d3920c4ecee5c04a1d8e7613090651f08028a8d8d4a35c3ce9f6a48f8a88cd5af07ec190634a485fa93e57b1ae9cd5a32dce955af096b2f0fb4425fd4c0821736a702354daae178bb99d6b6cd4560e687e1119d7ce8baa348e8fc0eb4b369c168fe143706c4abe90cce7a1b893202d360dbfa1952a3f23fc7db70b10f0d4050ce28b171d78f5458db74d1582c7657babe7211b99257d1c2d3403b3d2a7decdc9999aa38101aad01722ab909a8dc6f8570cdea915c229dcc136bde1f5630293191fadae072b94353a068d41e7cadb6b09f09a5a9d6ad97bea98228fab19302b9392a99ff2c1b2ffec358855e6a40b17c9fd7d0b8369f960dc83a10a3640358196465f7423b20770ebd2df7ba1066e83160f751ad26e88618cb567613d5769d94340f9f986905865e6d57b850721c11306f650a2c00f5f97a6014177fd5f6a4f92bb40b1660f7f4b57882f9df4e66d514ef8af50b1430824b5cac5ec515de99e0ca7af08b88c4974760c50d2e58c9c25fb4cb81eb1e629678d8cd92c6eda8689ec2553d69084eb7b22d59aef71105970729fa276dc387d69c954793bc163bf6b450c7e3b1b18fe9ad82a6cd826b0c7afb19314903e5e577e4942da1f1e09e8d2ae674b5cbe90100838a645e6576f69a9303c4b0d8de0c15d780ed803c3e13a2668f5f094b4d4c8caecbaa400bf957047a1a11523992923395281b3624284f3b43ea8e116f3f593a2482829698f2c251cd9419fd56e0b778cfdf720e1a2917bbb9726c0171aa2e6e242890b76be84593d8d8b0a14e23817ae1ae4a6a84327a731e04b21fc1fb30d6712c9e3b4d3afee692d64dbbcb8ca135fcd4c4c60346b01744ca51e288da28eff16a6bf2f870669ab17a531d5b7bb1bdee948971cb908291a42f7b85f690d18fb513489ced1a294e5919bd37c996ef53e9d88dbe0a3aa3c8cfeb70ae16e3602ad3f5bbd0ed9e3390673860869582587085fccd88b068257290d8f4aa2b2835f137e57728b4cede1c6291a43f04c886f5bf86aa94981c51d11e8df9d5db4fe9cfe4e38ae0f6126ab32db009cf04e242e23f3cc00e51a2d59e2ae39bb54b4d5b25cae600d2d92ba2d62a7561c1b6fe9db13d2eeb9912176a50037e45cf63fdbe4896707b990b817701fbf2bd86de1ac737608e4b50a0ce22c33d95b83cf210613c94ebaad90d071eb63d904d9e661a3aa05c7520a1db4b674ae48cb877244ef994586d851ab013fa0e2c35ad8b9b3ec585d59f27ffa8cbb39db6f98fe3da355c34080145cf314bc3a103e1aca92da0672befd1261eda8be5c6cc7d8692d179ac64c42f609a28d67475b0a2337af54558d29d71398477b10df95dcd6bb1da94459b7475aa6490483778625cb67d8eba2fe11d8eb1da74406ef76c391a92f167dcf8543ef5f623a98f556605b5e88aa997e2b4aa4b8e9007a0409033acd315e94cca4ecb86f2b99f0c9082c8ea1c3214a6314e6db096a7f480d2620ba983c877a832df18f8f1a741b4f3b4616d77173a968de4342ed5f07d2a9774d2918ea3c4687e2279dc053746bb64715172e3f840e09b8cb1e277e8b7d7961ee5884d394b22b4619e461e307799c33f2aa99504fb6653834c96227f93277388b48a53e0d9b75ec03b596b48c9eadb1515cdb549540bf0f0a3181d4a0dd65598db10425ce849bea913afd7d347b5ae2b9ac5dbc07307e1eb35737860ef24cf0997cf0fe4f1416868fedb41e4c729cc6b690de21a06fcb34ddd2fc5beae30483aa494dfcce934085151fb525a62aca968bf1e92d0bc9b8fc4e494aaaa2abd39012ea38955c7189c3c372737d650e7c6f332a247fed6df9d09ffc272ab24619d979e67c9cf4c032ebda5fda754acf8006608d646150712a82713c0c1c4e360c1b73f7d03db6b5a2763eb28fa6f1dee15989de9f5db34cab4c65ef097829008660bf8ced91442b445baa088b6329f3f78acdd6a2ec165a06812b0d4f85c3addf257e486eb25d1ff7ffb158fcbd9f898f5754a19cd6d35ce9a0bb25f372694adef082818477ea3511617067824bb05f1de3256079093af5adf2f8d915ccab68af28c9c279492432ee832b52b12d06f802c2e8a80cf0c02e72cdc55d4de1899cf540874902b0f17f109c8f08e9131c1ce378c7020c940af2c01fcefc6481fe00e589b28a5f8945add18390ce7b31615506e2d169d9141a93caf21f936b9beab06f37ddaa521320eba26a72b92462ef73cf2a8bcf1d3de5432decfe1412a4934e82713804df2fcc92dddaf437cef64b08405bf9983341d89c4d103e055ad80c7f507f2ca8dc8f169a23791d87a8e4a1ed8c99771c61f0f9193f47d1fe93b7eb6cb15f69d75aad8722501faa3c905041bbc29bc029e3e21d9a3c66362fae4e99428c608a88d2bf1cecf734c4bec4b0f5537af92280c76ef9e4b0bfcbd7d22e6bb2548767e7281497de796729c8eefef0579d4f9554976dff1fcf7b28ce5629707297dcf7d081723c829f23a0eb76b49fae3a686cced11c9e56bb3b11fcf72a86b996ec3758e27bce86cd72380a2c8bb6046bd8c98b36a3d9fa42c9b1df6bd0dfc7f60ee045a329923dc41d8cdc393c304745c6751ce004456025d55a0a3e8e995b7a47d691b3d87b1df036eb6e6fb7be2be3c8ba842e900a3eec0521a0fe855f3bcfd12febaa8af73f4a6e249ed58c68a7751407be810d0c3254d960ddc0626eb06aaa4635ac4650e2c8d062a19582a87669648a35c43b223b451a598de9d66a0776a6df0718c46ce95fd56c7d2f916a989b55c44ac5632a3e5bedc3934d95a411cbec9d2b0fc9725891d96a9000c0aece9d22fbd2630ce082feb59464a0df5aa300e2081fc5e6e5da8d6f3d071a9c6f210bf4de95881978340c56c7893204821787d93996a27d013ee51f56686743a75bec3632d0f0df91e40e8a7b2100655acad41ddff7373361f6c680f709253ef38c2cab2bb4c8f7590607646d5c0fe2e29ca221a2dcff438696d2c4bb96a6a064871d1c6be515d8a973c3263a011fc1d17338ec2695f15a6b5b7823438f6d2f7f620cf5c5fbf18486e5c903c7e9a46c48b8c912cba3e3d823a2e9bcf5c78f7f47fae523e50495da9f7ecf7d8d811ca4e7d8e1d0023dbeb5766c99dbd6e66d4d856cebfba900303f5942c171874ca5f742c4e011b3ce7d6580e04cb581e8b9830c9d760a946497c573985c08f35ff1a8f371877d2f74056baac9bb0d8a9ccdacf4b4ce0fee98bd0aa9cd57122de4834bb45994d99383c182b214c15ca52751c8239a0c8e5c79fd5765911a916e973fbefff96f5208001a8f0e541b5a337e31bf76810fa084d734d45677066e504c4558a751e97c2027b5b0b53ebd10f7efe6ef1e5a810ae6f8aecdb6794fa296672263b5670223ede689343a2daedb13990b8d815debb8af14df6609736451ea624f3cbac4cc2d895fc2412b9f7aeeabef41c80285da184ad6ea2f8fa3e797d84bfeb753f5553ee26a6e71e8b5344bd0386b03ac1114058fe10477cb303711a243399f570ba4c73d1006ba6cb70448cc122e828905da322b49975951a08555250051638b1bc2bb8311bc58198e490639ebcc55f1a73f5371163ee0805f82f2744c9b603cbfdc1da85fa0a23449497daf874c7da2028e5afbc591affc3f92a07783814a5d946e0ba5a192e7a39971fa617dea73d066b89fd2c6c11768f397b64719df9a06ba7c0e57b104d16254283960bae3a7bce6e0c57bfffe8c6c924c1ee512b01e5957e3ca351e8de65df52b74cbfc8a9374d8eca12f47b6d21c1b284b33880ffaca14b25853ab4922a11a8412ca57af72a859298ebeec9a638bebd9f52a2986bdd89e98dd44ce11bf3e0502d29cb452ce53c7ffdd172e81e0d048519e9db7a866ed266337973279aaed1c5191d14db0581e45ba6333c10890ceda5516e786066615e286c93a35a244226a971b323393d3f03a583a274f1739b02cb0966f006804ba8abebfe16be0574f1ba697b7c8fcfa36d2724588f422e28f30e220ef245dc133dbb657a4192b721004ba7c7d451baeac7b3119d0c04c3fa5b86f2ff9031c857b18cb035fbb5f0a3a18cc2e693590d0616a4e3c67394bd919feb6f67543afbfa384d178267e541b2ff471a4fdf8069ed5142bf61ed62437f21d59b8549c74cf0be9f7f8ddc6ba1e67544eb89c411b6aa7b5f41a5a85ba947b9532ebf05deb487114b06d74aad191c29b11084a998f0660ccd887e02622f054cbe3395ae7d2f91b56ac228d73e86d27500a83dddad349449d49415d0dd1777986e999ed3199c16204ec336121b13ee29b450a76b4db6ff6ae5265d38496a3a97345fd73431999b0f8d70077e662c073cc3fc4e62b454fe94afd63ea4666822fa3b66049bba963020f6f3665a8ac2fc6d52406cc17cf9c4d01c52fce06f30642088238e54b7b638fa98242378377b540cba0f4eea21fb72e261c5884e6185d3afbca7d7b38476bef2c034231457998d268403ea1cedab4e94f00400300189265b97bde53dd4d279b815cbca1f1961edb8c6191c4ed08af06d99ee20ce19e197ce1bdd1e226a5171496e2bd273d1748c6b8b3af4327cd81c16209d8ca08a5f9aff70293a616d7155bda4d235413a5ce51914841ddf76b7d3c4dfa8b6d6c15a7d1e59c454841d07f9dc34728d5132212b16334c942aea1c16c41a9d89cf348dd4275e6c287521e622a4b078b6b1049dd319bbe5db8ef483d4daaaf775cfc50fa4dd8fbd406877e3bd26ffdd6c08f202c974c22069431518cca37fdfa22d611aaff2203e01e30bc33cdaf91a8d64b3d5e1aabdedf3541052e275adc3f46462deacfcb043c8a33dfdf2492eefb2eb4d47ee693976d54bda1cd4ccb068e8c05bdb03e2f54c1d08baaed671fded8fd6a9f8cec0286b1c2993b979c06cad4a540ddbf070f884915c46a501f64b6edacc78beb229a78f5279ef6d255c83bb88c170770b80e0327188114c915f255ebb953406a9d2164627be68b4097967e60531e3b7b41360cd500f7b2e3035f30b4804d6e856e0184c9776220ed5436163dacdefb1296707762c76f26c49bc36ab0d229f4d2edc90a41a6546b7d59334c2e935518cb880fde58e4c7325c413b08d9540cd0bed2aa627610d52c268f5da4c6d7724623de46672244f2dde4f46a264c7e52e2038efafdd72550f4cc59476196ed9a8db9fbad203059731eb516f3497cb909808a6fb5b7f8e005370bc1c818b35326816fb2c0e6628ee6d9cfb400bb4c9a73d59671f7c3cec487b60d867a58ed18e34a0c6160dd78497833c303bef1ac53881871f58330b0db3e378a063e5a667e90852514507881236a2ab37dde128597728bf1583ff52e7ce68645fbb6c8777e63e75857b9139afb9288a9e3167c05d40f4f9a0fb694363a0ed3e3b5ca931f0a85bc2023a61c750ed92085069e60ab51720529d5944c0d159a4ffd7e24d561239131cdb189c96d4bbe5d22ceb4758c0d6144caa22ed61aa5422db11ab3c45297196e6c0db4622ab6d11f976e18afbb4bd9d7584bf8b696c611e030472cbdbafe217fabd7e11a0fc239ec9b1c900bd9c586d452060c218000e6b5fd3d845086d8a211fa23ab77033b2f6b7cdb66711e0a4c7a6ebcaa6381b1f6bcd2dd776c740508fd7a3b124a4c7d81362930e59a7b17c1e6f89bb2eba0de48fa7428058fb4a8b59d8bc9fb367c7410f8449edf129383b09768562cd4e15938f0e32cef0a67757cffeb3eaecfbc1752d846d52a41aa7f97bdcd6bc00636d9f474a44b3e5c31bd26d8d130563af2dfd03c0c5d1e87dee1295e0a143eec638002d91f00bd8c5828f0517018856a7b02a9d67362196c31f0ce46fca818adc152b85d51ff7aa813dc0ba6be1eade990110e01dcfdfbd93d0b1ecf02f0e301e8fbc3fb3b89494208f81b87878fa72f8401c46f9eeeaa69a87e6a6b8a44da93be60a179e23f410823c0b95b0b26308b6178a52de34f8fbf9cad330f88705bed837426d2bbb33d683e7cf148bfa4679ab6bb1feb643c18f8e989b05f0ef367fab2dffbeadd1a5c444913c50cf31051537f86b39c00b0342490c44db666902619e8201a9b585581b7dfaba8a8677a569c7b8f77692a37dad5d06e27f3574a61ba27f254017b76d3e9872803dc206ec1746f8e95e9a12582bc14d3901f854a069228e7cb2afd873ef5042ab36efb1243ad0f1dcab4ff1eaa51382591e00ddca857836b78094821f1d9715e894bd27570309de6e740f6b45295114a63b9da4cfd16ea81a6ba55bade821648c96a3cba40db7037541060c080da405ef51d3f725e10c9e7751de4563eda2853ab8c2dbc90e5f923883075e3bda39ed46a55db974a29f38a58b9f3430aeeeb05dec3032e9fdaf02c9208c254978fd8fc982ce8d2e3ec3b9197f4cc268324df5200d0cb659121c17c9377b9ab1d550f838978560f6f1c218cce9a058aaa80ef982748c6dbef156b984143fce866d08701fd686cafd8cfe5d46cb3406ac8f6dca95b9577d325b060a466d6af5d0a1fc10de7aaca412d26c17735a8a201e8d0454319bdccc703be1e2c5f4f7e66a7cc8142ec5a03a51f860287c1bb333658230ac3d2fb1efa3dab84b19ede19285d0faea8e7c72ceb856e4d29facfaa9c2c51f983692f20f3ebd92c2729755d9c5314f98868051a73d8d64e38a27c5af6bba0fb3bb911fadb0a8b47df18c36448305cc320304e23f8b71f8ecfcb753a6e20e029e90d432a55f856ba661a8635e5a227b8ecb636a8cc65933bbd5d42479eba198557a624e5d7fc35863af127d41ddbf3fb3ba5afb50e46761e619342b293ac58400d398309bebc18b643f1acdd460e07fe865cd8796966a704287e069c9d5e7dce776e11efc3c410588c85f2009df70c3f956ca16fe1f2d0557c81f452affa21268862b29936060943aeab3df12850c39ed51e3f6c3cbc28acb6d33f5084f08eed8826941b08550eab127319ace05db6026deb6bf8d1bbc09453002f6d8172bf80dd3d1a30b3e827c63783852cff610d6867017b9ff38967d97568b20a84f87503852298592edebf03d89674e5d42667b227c53a1ea8e318f686bf4a256dbe60f1b0c0db0b2eb49b98fcf89a9d847ac6c9aa4ba79b272dfe9b998b3f8f3b414ede7af887ad9cd06461bb0c8d8639ac8ed972e06c5562ebfe630caa25cbf5c67158c91e3f7a7efac05d9ec62da9641390c2f7813a6ade49e112cdc9b54079d3b71ec07c9d8f6eddfa4579326d01798724124a61e4ab6494fa1087edf2c581b42a1dacce03af818b3afdbfeb56e36ee65b102e6756a347bf6ff7f9eea0b6e1f766e137c5317ea2f64de246a47083109138b754ad56308bdf0555aff70a14f0f01f79d984b0506a780141b4a368bcb2ecfba48002186fd6af820c49081a67a736b4ecd6260dc4104c0866d041146c86e1825b55699ccde40d9deeab74bb5ecd24d2c7f236e3b84b127517506bbe01205eb25e4798e77a1e7b7d715c639764c0592c62ce35b772e04fc273ab24c6172cd2f303a47a019a1b9fcea77d5117a235383eda5317a1e835ea6a7911018d3289b4c104f0aec8e218c35dd9048dbc2f476920f405c6c6906fb112b98eabb5fe27fc4326ec05959acf564b5fdebbc668af566d4ee89ea63077d23ef40b61b2a824a8fc876707aa0eb669cba5f2532a9df3af359fceef317ce3629eaadf73a3278539846ba76f79fa68327675341fd79b8de704eb457fb78dd8dae2d74bc88595e933568794abb583e18c28e623c14ecfd8b4f5e7f6059e80d69428646759b1ee44a0ad360e5d34b62675749d7b2b8be6a493d6fe54afeeb8e952ef08a1b3a7d2658e7118bcdca0a95c90a13a10253544d5415e9ca99dafa1d7b8a2ac3f793220b08c31ef76b4feba6ea13d1244ca2aae614370c40f630eecb1cdb27602e0d186e93132d4e922fc8315133d9bd30b56f7615f7119c08b5a99b17e3c1e49327b676496890f5d98147aa475ab2f292ad39d8b21acc92111d0595a1b4235fb4ca79e25d64b27b43f835fdef8eb02d9416797ad57c51471e5310aaed87372bb1fb111db95c7f7887005f00af3896f60b946c64395405b706d345aa74e883bd23284199a76d2e6f53a6d73ab6099fec5d32bd030b214ea8400b8e549f724f2a01e9b2466a4258411e00320775ca7ca4123bd510a4950f4d46cd34a55b556c55d114e2a714aca285658bc76bbaa3a20a80f44a82839ce6c7870ae06104b9a094e4c71c9e01b966730cc3503d831e47dea54b3cbd826fab192432c23aee27459b3a5432ca4cbb760b71d8e29507adf55a582fdf2a3f6a43d370b66ff8e869c002606ad0b9093534a225b04ecd83343335b7bbbfc9d54e9ca4f67f5bceca7b07325eccc6b87b12fc0e64e6bd2dbaec124b0f822d6103020a456e60e1c9f9982d63de8d8ebf0e670ac8bf16b5d2362f98eb2044d5aa83ce50a9c8e6cd04214eed23f214bc319f666db24f197afc86955c8007ecc6377712becf95608996db44142aed160c6702b698c1b324c5605017e20659c940acc8861524daba6a4967a376bb3c4e80bb5d3c291604a635c3f0b593554632f48ce82a647c9ef300d02497dd6a5dc610e4dbdafd16404b810818f9ab79330447c51210e1a3bf5df734d6acdfb628f6962fcc0683a3ddf772a9136e50032326e8b166cf533a4873608330897498c677a5c1735407b278d45350bce7a1de1ed6292f92f67fa21e0cb83b7b536490181fa3f2578ce143d742b6f2bca7c2bbab868672b789063f832e6e43ee3bee1c70f9ad19a6a2e9282305e827cc0bc3168401d64a41b4537b822e5f1f26c50fbf1d60167f8f249fda80fcfc438e91cd0bb038c317330942be6477ccf7d0b0305f6b1d1cd1f7736e0c7938ee7fba4806bb3d6949067906c4f3caebd335ae15d1f5e0cefa32768a1650ef4fe3f55f34cb71560a258de5b51759b59f4ffe1f15feda76923fb4ab44c6aea9cd614e6fc78bed69cb02621741e9006bb4e4bd3a3184eaddf0a62d5529b916ca9f298fa892ecea15ba376e103dd338e3e59283b45d080f49552ac14bb2dafc06ef97df789fae30cc0ec20d4f2a7ace6bd48af9485c075d44c47146b6b8e176af2cc48898d8d812cc283765771a05d7f0a151772f85c4d70d3b38f9563eea9936fcdd13d4b0c0879ad2a755b73f259566963e734972b482b900f721baaf1a42bd4eb9a131ff0fc38a105ce782ee9696f13418d313d2da95cfefcb5a5c78b98b3448ce5958577daed1181b8409f13eec9b5addc192ad64cb9291888ad49ec06a59538b24fa0a64d934688cb54047952edeef548b18396f61e3415ed8a3f7b860bd22214bbb7d703dbda0b57c360dd03a8d4dddd9ccff2962bd32e9d6dc0e5767dba778255b2db69f13dd8f779fa151969709b43987b2c0c51d1e46e33653a9bec3e81112da1aafec134bc5f5dde6d22b13da15778577a1fe48c0aeb60ca152be00699bd0d35a41b2455ff8bba5a1ad172ad38242b18d1c0fb7425e4b911df2c8aedea3171456054d38b28e45283bf68e0a4ec4054d43bc3d38f1b2b195e142bd28107b5764b0fd0989814fb39ae042d0ffbc3e53b89c49bccf831bb2f5ca796df84462574e56a3e958e5cec18f7383d0efe4b3b8696ece40f5b5bf7c4b66968e66ef92e2454364a2d550c9030bd07908aee72b18f3fb5a8577bd6d2ea9f89d3f8e86d2611004fabb429a9fea0243d01de8032675a2d38b354ad1cff594b1e1b6952c02f9ff14c3a9619881f12919aa3735d0aedafeccf6fb65a2079d373c4dcd63e7e9e0358b3a76189743bd632bbc8bfa04aec796cd012e581db0ff6d54a625bf218b78a04b1b8d9b1a6cd04740871a65b2e7d5b81cf1115322ebe6fb26ecc68ea273cc8ee702bd81b0a0dd98bbe150f12c63544637d2c79140df2d34fa828d8557b71b2aefaa49e29d4e7d4ed00ff903eb23b0d2f0d88fee0ff1aa97407c45293fe7e3202a34af68c6a6bc6d7fe9797cbade22f632d9f2ce3ef1971f01f4dbbbd6a5d684e0ce951ce99d2d2c727731a9ae8a14c6690087980466c891306977b1dc9b6bb17b7ca00ca1bb4170070f00800326a88fe04569dac5b2cc1325b0fa4b1c564d68722a0622e6e0b4809a5b7d545f3a6bdd71e8e437034416c266a399aadd176d0ed7c1bf700f915e82ed2782226fa731d036f02ac6b91578ccd53c0abf615ea2839e4211616853e70fdb4a4685413d224754c84c54e4d7e61884bf23ba7357da18436b4179c0291accb410fa0f374e4b8852c3a8f5d0b8055bce3b66990e049b78672f1b4929f70232d38ae501cf980d46f8b9781f53554be516a52fcb2350e3d1ea8d4754c2e8a2b7fc7e5305b289d70d364392a004d41653655881dc4f54f8fce789011f0db3c9e6043156e31146238363ca91c82848a19dabeeee7b185c3f53d28aac1fb36f3c034e855019301f12880b548d810b3c2d6b5f2841818c1a008b33c36aabd852615a1af9dfe69e322747dbde74954901e30bd09df42cb9ccf5ba0afd8a3d86c063c1235b8a010a5ccdc5c42c71d8b4360c3f19678bf5099164515ae6ae6c5e5feff37aac7f0d944524a337c0a2d0642366fd263002c6305b54d49e9674d4e8596e383db3f4c87be928078c5d8efac7d27eae53cafbd721013c31577853115125f4aad448db8d5c576c08903b20377e3662032cefbd51fad71e57942141d0c6bf26d3035dae7ee50ac94269fb783f3292870c88f3d42c243c466150c11f3fbbd35d6ca04a40ef3f25835f20cec26c2ef4f7537c2104fe5341e47b0fa037bfe0d5df90058122e7906cdbcf5e5aad61a2a9996147b8d60c62b5e9fb5bb11a6040a69fd9974424308a881de1c90dbb545e389ef78c1a310d8400196b133e04b513319521fa6f8df9d80ebf0b876a05f2049d18381ba2eed42ec478dd7be3090fc538cff61b2690d2860ece1fbd2847a9c0be9e1d0e590cf5ec816957c01529c72450913f45d0efe0fa8ed1c7e046b9b410b147c0a6aeb6f3b243d4e9a48cbd9f106aa88ad00b3ffaa85ed80be79bdd9a6571408587142461c660bb8517ae3650e24e36d282a3553c5fc08449a28a464fa7d3227e6952004f73d63d46296e507306b21be4556a10b21c24c09d79640df7e277071675e8bf1f7a7aba9b2c6448e12f4f7a7a5b1a2daf2e5d67c9c723248090800c08fca5a40fbbe52edcdff01194576987e31ee76bc624e9381f6bae6b6831c5d3c98ef45f9145fc78c6131f581abef8f989427d8c90d021d5b557873476a399b4abea4367e0b98145ebe06dcc5fd9a01c2bcd78e1283428e450d05022450e9cadec33fb6bdd8ef887c5d37500b4736dab55ddb9d47ca76499cd7e509b95a9b8a2e6c61851b2c751339566707a76799b5466e863a28dae504084d32dec7c3211ff901d2b0c2e8a14011e897fe46919c707da9c802191e41a32b718d4f395780add1d16db58da40d01d3f5277340cd4ea1f80f622309f9fea2ee26ae6448f80a70f0ac2185f129f0a3ee8364cbd6787c6f45622760cd3f852ed3b5776bdb1dd4001167f77e60f773affb3ca9d033bf8587bfaa563065b82317bd4aa2e4a74e6b13cd5c32ac6ac0d880a0ef10ddb3278521c3b1fb6032a0de442e75f30b0ca9eb37b4e77bd478eb65de5f0db6de1a08c656a18764ef072a096544aa06ff5e605a811aea1f3b60122de54a2c8352410d8b3fd4fea50d19f8afe1b2c1a5c4d63955293949591735849cfc38889398cab52493652e30833da5d36b8b76b1b6090c118bc56fae19b2c4e1b9f27d234480d557c457f372dbb43b73a2e6ea99107f49a80f94c85d9043081cddaf0e1149001414d4266a92148ebd66a0652067a4b86fb9e53545dafc44bb5ed26361ec28866e7a5423782bc01d9f097b148151f89d96559ec1db572c48fb836097419881b9b233567f4328c87848b5fb6c59ea4e37e63e09200468254d1bcf6ff99b7a9f9604173a62b6a449be970b1e1857d45b351e77ea473ca67caa225f1264013df5b75b20a13d01e337175b111d94110ca7d80e1e63cc8574e2054b7138ce2a1d9b8104b7f4ea1750b9b6da1819147ec0e0691da02703e3c7fb248373ba8570cd37f21d4441ce2f9870d24b7ca0c57abba79ac434ddd0e104d88eefe6a21850e5f4261b195d5844cb237e5c28a527455a84d410ad38db0bec95ee6312e287eee160f245a695c795917392acf6a84504161ab22492b879729609e8eb84d5b88eb6604036015df32570944a52d82a793f9cd19cb593143f2b82759746684d9e1ced92635fbbb1443c1d3c1154d09eca61777dcf8c46bc5ed0aea84566a3f93ca556c6d282083bbae4e4340931be7a2a3c47bbcc0833ff82fdc913f2f1cc6882dc4b664f383553fc6d1d493a58f82182828913ae9933b8e1d46ba734c3ed02e1967dfee76c6d7a12e9ab78efaeb6b90b4df2198f4623967e7a638da16a13aac64e5538ccded2eb40b098aabf0d4d3077985410be28dc5b9397130f49a8b2ea4ea2e5f371a2e34ef2deb34ceebf350122c31ace49ed85b107dade3a26f5d9e504d36a5e1d9e30bf56d049934d07ec7f0e134feefa563461bb73a41a4ffb335f24495b28780f64a48eabe74a909896b3562f3486c4b9d51827984c8d8118da988c0d083d86049814cdd52f6e051c820d8ea46de63e7f710b8100651538dff6e6824ecd9df0e2fcbada7abef10bae6b84e462b828cda7b60b246bcd2eca97382576ddd24251decf7cc7081e7829609bdee43e338ca441daf9701874fd3bba13782fb05c23e586a15666b3cbdf92b6ce34576e37e20af4c59ec34c22674978d5034062abc7cb39ec2b64f24bdd2129f3f72710c18dc77a964e5dd258eae832868306b16c06c6aafa8267004f1327a6b00333a367d7f01e6aec58763cc4faff9be95affbbcf37611e67ac5ce55a440cc487798be697f5447f29d5899237192a7d74c9d3b2270b3a36ebfc49c42d7fb229d29891357b01e2a2198360cd0687b1d2e0495589b2f4e38406bc4f9a6407de17444f61315ea0ffee76b0d5287309c8aaa38ad546748462d7c5f5b873bbc24ec9fb7b0bd488d33bd92eb6c4d65213546b563b4402a65f8efb0a0d142fce89b317fd4c83fdc6a14791126d8f3b10077194be3cb9b2c2fa918654acb5017f2732e6e23e21cca18968afc4eb9b3aa47ded521b40ed8f2d2d6ceb4714340cf997e15b632822853b23f8323e5ad192dd70cae2a22abf02140273e1f7bfeafec0b850ee30cb250d90ade5d084181e4b87dfc0c15985b2256d3ccd1a219c78ce8597fd3b024baef01b3931926ccff32e4002b8b1ad5c6ad5381ef54ad7bb3f78a3d1deb6ca7aa6fc2ba070c27e43c55a1570528b7e36335160253c7d5c8ae9d68902dc1e6e27d96657834d2e60eb8ed831a260e07e307a6bda9a44bb1cf7de86791352cc78667686e60e05b2505c4a4c351e696e16ec514af502860308fed0da485b92caa676931c2864ea179e987bb0032f276130a8efb7e7b7cd8fc43b30e022e160c243efa72cf71fe146242da622207c09659fcc2e030c3ab96b73f2b67372c175b4add20e65f39de4c034d4d4bfe4ffb28901360c70660b3e5b0101dc656ba8fd83849789172e4682717f05129129d578f44dc42a38593e22113ed464afe39119515063bbc14d695accd673f1872c15e6627609501ba83982b53346ceb56e825ea3a880ad256aeda33be9fa068bcc863a7dc678e77f2d964dbc78b5e39fecb9556426924af22254a7de49e86b926930e7f8ea227d23e364c70b765b2dbafff3f9a9aa22e4fbb270449862dc3dd4fbf5148bcadd455b209e0873db0d7ceb19a73c51c4faad04027b6a6d29b91c01344d1cab9ab294e42ff57b70bc24623914d782683e123f15f70a9f33b335ae90b6b90d25da8f65459fcd60480f34b85c82fc236a210c15658ded7e8ffdb61b1ec433297c1b88d6bce9994a6b88a4176634b6ee46b1ea0fd36db333438aff92d4e0d3671dc959a9c3607da12c0cfd60729876a1403210f2ed0e4f01cda520c3eb4c1ef2614d97c41c74a520c930db356fee2272947eb0e68c9d16bc5573d964346011a961488ab175bb7058bd8a2e7def7fa689f5b68ef6f72f044fef1739adb41aaa9e95bdd71f9763242c6cbd47e40f4ed083d7974194a0051d38239feadb547cea974a63fe5f25e1a253158ab1586be76df1883d40126ad57e07d1720ab09b758d90e37d03e7f00a51da1b57978d1f587ad71d61a829826cb0e35c80c6f17a837c37ecf005d3d6fd189f9ce9b17ae165642d2a580676c91f79042e6553e72b48cb6ffb6ffa5f316c44d7544b50da218ae3614c6166f8cbd1b03a7d3cf2df1bb375ce65f9866ed83aba88d81618ac415c8fd487079e94498f306573e8dbaae8895d1ec004cf06b28d051019a485b4d65d71586fa60b914e044afa97f0e71d6d640cdf310f05d2d2a56e9c3bcf09f8ff5a15b81f59011cdc4bf2858f4fae92dd6d8fc4976fc19f2e3358b2bf53e043cad030300adeafc520dc1a212acab466f47c5a32769c71dfd8fb05a8f5f55ea5a40bd6f18e39ad0141918902c2a9f1792e5580c4bb4dae29d5faeb2ac5f48af0e4ef4b9f672122a6af18dfebe9f38eb11723b6c95324eab77758a7b8c37d373d5c9e0fd2e17a4071163f61b86b796277a78588c488c1b5e655decc259b1dcd65fb663998e975e483cee28cd696560038adb6e0eb3678c9b9c6c8ca19c0661f8987bb7af1e30e29e0e88c69c6debea6e54ca478aace012057f31736907848638b5e142f52590148efc6f2169ec92d83738b0d251d27e1b08977c2fd5932d06d924bf98523707554fd42e212872e57fa5cebf3ec99205dc43d926b2898294fe08a6ba0a04137f23eba685a4440c470ad1d19f4bd23173d6e05e67ef3455f1ecca221d5997dc05e18b9bdd2ee99ffaef0b6a7572df724b17799c9d364abdf7aa5cc1b19d31a0b1cb9f419f34ef9410f482dddf11d033510e451283aa2e95834124067318e53be81c847103ec8d957c9480b8f195369e55547abf292ac7401283ef99491ced3b347a0f1e66f39474702efdd0e52f1013a8a58cb1e566e93cec1e436ee56ba3b52a6ae16209443646e9a10b42675add41e7d85f638a867e2fafd2bfbb8c5369fb737c93f92130ee6f13017a62570f3a2ba7fd0168461a4d0dca4c28d8c7a4809b4e359d7103d58977bb6944bfce59d5d9105ec39aaa812210dbb70fab5c1d3f593606eb0a40a77d4482997406948f0ac76575b12fd02c36cdd5f6e8facc6b5d3ba127a7d5425340e8954d9a7353a656950c25a7746fc149d1e94625f968dd3890d5509557d69614d0cad0fa5814c8b23a9eb74e128c8557537a3e0c3dce8a7ee2c74bc37bb6d5aa841a599e320699a9d1236f6ef0f7fe0058b2898d5d0dc0d5080e69f8061ce9bce8512d10e077ca44d585fe2e3b0d861372feb30f35f2381ef0f8b5e76833f0e80c17d0a58c42d44de81c2ab231b867b45f309f942d8c1d2802ff9d9c18ca7a11458791af411c174aea5e7aeb9588da540b6312129bde25baaf82a6cec69c45af46556dd9d6e65ce518b4b60d49e48d68a0dd83fd2ab650d8c950c75e9401d041d4fd3cf10cb90ba0c1e892d450eeb2b5c3bb439ac4fd756355bbadd8ab94249a19d9d408d4632545aaf4e4e0304ad8c4175f07aeb66c3c32bb477ea09d69f212530c1035afbcf8f6b09c924a7f901f5997acda107d7925ae5daa80b815b5d1663b85eda40dbe1f7a0516e58f1f579f5177bb25839d1673c5164595d72ca191da98943f20c9cfbc110bb5289418d30b4444de3220107aac14f9a192c41e7777759b73cfa0fb49741070971551e72eb9ffaf4564ca2ee345333a63c25b5d2c54567833f499f6695c581e1906cb3954ae5e9796802a4175a8c478bc1cf7bb23e41de1cc17f94e0bbbdb3efb49a4dd8aba0150e5cd8528f29c646f1708aa4e9a8605baed7dbcb64d49d83d63a04de49a4b95584f29f3a55b208e3bef99222e1520090bcfe31a7882d540bf3164a7dfa304fd616858bb0df2c83408416894e60b084c3d4981d8b8099511aefd5efe45a019e977d63044ab0411877b5057865751a554c8b8885617c3911b2234e28f12c7d54eb546e7d65f8dd3f15b5deaf06ee737a1aca7a021614a8c68bcb2a212504685afad327cf9f4d487c9aae8d9fb55fb4232f6fbc0d1bc454598f05bb002856442c3fd3b4575b6e67e4fa40806f5cc09bd0203bb520e679221c5ca7ae0c431bd80c0b23882e40166dd11ff94c54c58e10e9960f62166f309bc0c5dfaadbe4fe74790132b238bae25692649cdf6f218bea15cf7d6831b39c4add51a9e0061e20e67f98030fd07847f7231b43f49e84ff2bc61da59d2235ea11ffbb0c0f8c266225cf9c76ccfcec9278500943ebbf6698ebe966611a4d788786a570339bf3d74c3a9eaedb02f6ce028c45e5e93fd723cd7012a2c9364e8602aeba144aa8592f7de4cebfdb59bb99e8b6a4dc072cf57cd844d561fe06816cf5a80efd3259acd0a5f30399aef31e0dde6f4d104a42e715413a86424833cebb021f20b608a6cc801f6fbd56a11884ab09fea414650990f5f1ac1a7d86459a09e27d027e706fc2a909efeee7ec5cec3c734cec720fe23508e37c4e5aedc50d6e7c5d83c7bcb9dfd9fac8ee8edc3aaa4b68234825ccc139a05185f1b814c14308199bc8b7df39824341f20e64445b35a6f0797c1f858c6df088929a875691247799fd80aa2d9e1d3db3b9aad447062ae152d1ffc4f516b1903a18db4e7d05d461c67469aeef3c5cc00fbbf61e707672d2a8ccf76de51b94fc662a75a6c23ea3efab455905c6126febeead6ad51208f6168476e4e731ae7a15d67a03bc1de0674e06b63fa7da3a198142f22e61087fef8ba2e63bf1a9f1289e1f5df9d3e168f330be087ebde34a9e6666fc917eb2dc38d4b239fcc1b724aac04313dfc0acd4264875ddd027f63c7e25c1022cbb528070d9280d4680db443e86d3e4346ca5dac7d122458cf727d1d46df48e01fcb08bd84422f165c9f5d75862a9c275560a66ce08c01d46f40291458f9dd4a7f911c636045b824f2e62a4c2a63179480e10225aad04872a18c7de9802a6ef27bd7d77c8ce311e71b3e1619f0c43b30cbb0f33e18733821e127dba91e0236c202756f69297bc62f35155ad6f2f874f9796057772ddf60e85c5d8260f66cab96e6c3de1f61bedeceb7448c9260f73ca1f6d5fe716fd4c57f3c7eb573f78ea6c7c934c3f715ac506c77e4bf51291cfbe16211bf676b884bc60e3a715861a036f26f79900e3159c3730421e66a6fdd1bedb283500eae449d7679831fcbaf8e18016c90f998c5571e80d89aa28267ec4070f0c672fffd9c1bcfd9702848fdf53b0518c92af28e5528eb891ce79e3a63ed289e9356560f5b71a9d48dcfb3f4165a3b097771b36d3d00c7288f75cebe7ed40def35009a918dafc42468ade2db632dbf0974baa8ef3814246158c7b071bee2f9eee3fd56b28054dc3de742ea5754950100ae967dd5c98f1e9d0269782fcb1668eb461f72b8fa160eb9503c1d9df970aafc4de6264e88576e1c5a01862ac7bee59b33a277fb80f25a19fcfa31cf5ed96446e2627c7b8b0fe34f6eb85ad2f658f4f6c1385e79524bdad68a60565dd0d7c11022b693cae37313cd6bde8ebedf8b531f5e65e351cb6a12a0f5c4d8b614c11d7511f9b9ef2c67b97ce097036315a432778a1346ac5379798e66d48006fc1588a7da466506e7c7541bb4bd61eacfd096ab2ae95270e670a80f4749b5d9a08bf687fc044cf52dc09bf6ea2562ff2f1f86575d8b1adf707a61a7368dee79791e20fa224ae1b90d55dd92bba3af9fbb7379a2675b74e87b133ee52b00efec180c47b06c49dd361d6a0f82b874b7ea4abb4b33fe6580545aa1971b307417d961f05191eba5ff9bef03b2b1ad0ae402cfce706ce66138ebfd407c96e2fbd5238dd0d6b3281e6e5e70e11219988fb6bb734cdc4f52911fc06aca5748d70edd1edde4a20de7820d3e037bce6bdfb8e59ce54550568c9c04330505339ceeecbe6a020d6d091bc07c0efe308f98ec66c29dd78d307f2037c9ed5a24ffe24cbb7e85d25360e0f4a662d20610d5d89a23348b41226d8f0cbd7fb9373d5563616dca4d93712ddf0778857c1479ec51f00f18107b29eddd4a577ee8bbc015ddf929232e73afe2b1ebc571ee6fe0b733471ddb0ee5ceab6a3567ea3e1ee9427ac486c47040ac05c5bc297c2e6505c66a0e34b2e66787c2c78e2f57c3f4fe19461e6c90bcc69b07fb9d23ffcdc0f72b5b1ab55ebcd403ef1ffdac8d85262785d75f42de48288197320882ab327cad449a75e0d7e6ad2845ffafd26674c69696191d7b23e8d0eb5b2cbd695d0ea8e0a18377d427fe3c18f5ec0236f3461ee22eb14a34e37f5beab45cb622d358cc2b5e909ee1f4cc4441223b97df8334a61493392bbced0eaa7b8e3832699dc3a03e9283a482f9effe9888be32455aac5c314c95049b5933de3511c2a69ca9c601d63b7dbed8a7367e25d6f50f8e74e126607a3ca7a629fe5e6f8044c0581bc3410a5c06ff6487033d91d56a6cbe75d77b30f842e97339d7c6514b649ffc2d12b2214a271e96839825c3fb29de5291dbe0e6711dee08190c0bef5a7d56b40f682059a78cc48ce48d1d53ee985ce4d87b2bebf6df247ab311200ecd376a6dd64b60c759f61eb116186121129bd6e24eaa6ea936eb4ec8e5dfa74865709d8dd000c6f2a7085273417695c2e493d7d6c46eda6a78822722726a1c7683a3ecd671c0a9dccdf80cd79df2522b8bb0d3e3e28631110616f202d3c55b152e1171d5f11fca4072a0ff2fda9cc2f7be1cb90c080e27d518882d7e9bc093b3dab6d9fcbe470a7a596e288c2625077d0e92a4f01c3ceb2664575d1a8ead28daf2f6b33493aba5b7d8a1fcf49980b699ec56dfb0a278a35c37e17356cf96b53ad9711d6395271a4fc80dd85022338dcfc9af0be917f01a7a2bf6b78d2e4b2a6e094b208b4aaf033c13faafc10e777873d95a0b8d17f1e303710279355372994e18c76876720d640f19106ec3eb14d095be37d9b78a853fe88d736014ec6752abda8d94ff219864a461de51f1cbfa18010351f9db45316d01846369563a9e31a1e9b2af75f5da8edfb6e31f299b941b51b0d2eb4c281609197d8fb1a561fdee9b8190d51cdaca6c814334695abc85bd06bcde92696182c08455a0aca47a57aafdd38fa656a2c3070a7a4752eb5ed04a53e78a0697342fe90c76e1f88f340ed675112bbb954e1b87325aa81c0d301b5886ebf3a4d987859eb8e13a7fb78e110d4d062f2e1034ef771cebc24e6478cc8bb7221b07d4b90481614b9476ea8c5e2e9de13916c424e663dc1ae8b5c8d24ff61d4819050edd1d63c888c537ff80905d5c0f95f16edd9c6105d9fd65de93afe0a1ae17be3e3ba96b2c44a57a68fdb3f87c9677a51f24299d3e4f19acb79f1ccca6b96a78a7c8c1b008e7d92850b51fbd8569fd01b4cd8a66e96e65e290f2756e271b35d8f1c3381ff7b54760cf094c5392288a425ee947930a88a21675bf4ecf72f097c3e5b62e30819632a6ae8e0d2f0c6921048e2a6e041de0fd27b3fa3c2dfde9af446dffbe22e0a6e08d043c476ed9f207d4b0d286f2aaa40d7cf419771f4062a4c6819f8258b52191f045853ab275f4e2cfe5557fd6406b2877777b573ff5ced1e713611d39a03bab1c3b32891900089b09a28fe25048088b03d6a99aab12372958de2611aabecc5b6f54a94e892fae6727e9073c6662022117ec54bd9ab131ef5e5b702ac551b5c58ad06f09723ced92c3062db84fc28bbe1cfd33e29170cc6148d72723d0d3f2975fc62d42962586e6fb62fd5441ad32f4780170ca6cd0d54d9be433e06ba417b219604d34bc987fc1d3e01ccb4de8551dfdc87cdc0124385c46edef04566a7af2141707050ef56d9ef100c7dc02634e23b4e3df4cbdb155f662617c8bbfa8648701a4e51add62e58f0dbe1181e92d42c6042962d7fa89618e43331122c0b2edcbea7e2f27dce122bff45d297c497255e45e71e3f78ebb5db7828335de3b2c1c3ecb9a7d1fde237fb52d678776caa2f3557b5c5c0b70212f0f0a6dcd32b816ee5911f2f9411dee9e5ee2ab11c176d375ff8c926441fc05b972d82173dd775f9367827b9abbed7df632f7f6b8287f711d7fb11f8c6f9819321a62bfe13bb0dc49d20f328dfae56620347821bb3c32a0dec2a44e04570dba085e271d117305f95a13a994060af00e547b77dbbc79715de28c1ab42e416cd0619018f77849446baff2f184f5e34dbf5f3d7cb9dcc1169472ff0c2c9645e8db6d734875eddc8a24e8c9818ba84d21ba4fc549e86f905fd69c15c9dba9d1fe1420b41d7dbd5b0a1c1f7c47f0d77c488c067ac14c81f8a64f19d9e225b2b1704f6060073636bb61ce7fe35d27a2768699960b4a25a0e7b928e88c218f5e166218c1ce01330b7480528442366f2d7c00614e113c72bf1f88d72d4cafe3f680f5c94e363d7365656e1a390f76540b1f2bb4567c4111d5ee236bb0a29b73bf27a67ba6b9d2d8087d5575584d1a2d71ec996ee51987a6dd7621a04d34902c64bf5af7af8b917404a3ecd8bcc04373489318877a8b107870862578d1805c974412e13037374b647d60f104cd094f8ff9bbe24564509c46ae71293aa822f377ffdba2842d5a028aac249156a62bfbb620026f5f793e905353265fd6bbb0ba6bf1d921a676fa3b9ae0ff0c46d7171652857c2569755720574759165b3abd256181f910d540ff55f6a128fd694ddcecda64912ec9b2a17c34ae5935e27c831fdc439f719c5bfa58b9af1b1bd8395ac728dbb369d68f0213e9ad8a973b6c6c71963f1e9d32d0bd5449bfc69a677da16fa08837528b95d2afbaaa2d56c9c98aa24bb6aee638f6c399a92a6cb810b15ca84e48fba42f1a9a2da10dd61a31b51e3879410732fc9de9d5f2b597022e2f3bcd092151248ff754e3fc3f056665cb5ed691e30491126495c3c79a3f668c7a5912b491f2f7cb49fc384a3d2036f248e3a48b7fc222228dee909e0827e801ae1253596f0e8dab422efc5eb747c655878ae611dda1711d55f4a8218a52c2b931205721eb8b77860ac91f33f64187579dd22f151e3250c05a3973eac8bab1eb47df6ad686539735812bc3a5b64f3f090f1e237b963ab3876998005836aa6707c516c2ee4e5b271816d58bb93a56a083c103058415fedbba6a0c5695b7450132ab414b592a886b5a535a18eae59a18bc996bfff99559453b7d1bbb84809dd611f914a52000bc1ba185ca61dbe0300c09b6538e2c6232c7ed894ce3890e407bd3d84a307733d9068fe0dcbc704dcb2444087b8039746a53304c9f9b77ff88a374796653a7c3fc6d1177948d915062507f01750d3cac7d32d06674f6a1720a752199e0af655d1588537a8a7d1ae248c47696c8415f3ceeec014a57618a12fa5cad202c1feb599e16ebf4e49fa020c60ad55a3cd9e522613447c46279e8e364c73364bedb3004b1f47ca24e24c73f0db6bae4017de4c03c0091ff1b0b839f66824dd5ea4c205467494e247f9a9ec747b76d02e43372b095ce8cb1872255faa4cb6089e31a8b013663b69ee695f666985c53285a62d43ccad52ad4e62106f414c0e2afd920457121f9f0d9ee3fe3adf80aaf0c0885ec7125f0053898cd20c0c413b6c21fabc8edeed51f9ff6a845576b6349aafa74bcdaf842618488b0058bc09a2d61350eeb552c355915fd5cc6c6bb6a5fc28b1f5511c0f40e84a13c9d846053ad880afaf9673b7eceeda505cc7fcd4f5d4d8cd4647e15866a4f568a329d89ad94da2e4a4d04382f9714edb744130a8689ec5134df47c9b39ab5ce44db89bb09255e2f360f24203f00e0fca7c8fe28b31eff50f4532ad78594566751c3881361f81fd93986c50bc553a9943b5f64c1d281592427450ef746df8621c979f879c5d39ebad24c9c553d29374c340f6dbc28ce423a1d3a89577bd02fd6f68cb5557dd641be0d4cfde12d0b2ae99799a4620ddea22148a2f0a4d5a8165179c2012607ba3a8001283f168acb6796a987453a2b877cff83687b425d60234532dc87bb60eb1f693501e8b66787cadabaa709df8d42d7b9e0c6481158d888374e55d41d724c1d45353804243499c242ad903f895020ec2caba4738937d38765f8a2c724bcee5a3ae3dabf3d918903086fe6cc730412ee33429e0a3fdb0416f0e5d51b0197bd331497644232536a6aab28d01bf43e48e5c0a0293101334b5000fe8607b50e793f079593e7cca45a17976c8c0fc8fc7bf296c0c00cc789f55c1031e67848017faf3c8bb15b58fd227beee45a76696934789ef56841f34309ffc20c85c7d3e65e9a5763c4daddd6d653a09c97c832012fff81b3208b734708a75b0446c3fa49bc172f667103f42607ff30b6716c6b792d22dedfaa184c96ecb4290959f74ce2382c76caa2814f0e9be7d0bfe46672a5f9b2756b5d7f825898f21b2bed12a5e41769b3612aec83e1393de76fbf7770e400326b97689d6e1c2fb799b74fdd6f85e7c6760e21dc9260dcc718a7224ca3811a42f71a0ce007aaeedb6e51a4359c76a3add2ef5c0a4c621b2c9dc82b0ac4466d51049ea4f60cfe5c080be124083f642d1e896ee8b00df34da13d30e573b113fda33023de864a5121be16859917b3832e187c33ee3f159d4d9262ac2273143cc6078d05c96b723870054b7769b1a417cf1c893f4ec4bbdd83cff72684fc1c043863cdd6309685666b35096bbf1054bfa8cd61a605a55b9955e1c8d9324f734c5d01dc50c5240ae5c1fe382d0f6e092c7a5491d85d36fa09a08e03492b2838d4ed19419cd82f07fe8d3840d949062d8fd111f985d6e75e702f9cd73b63cac7fc05f72dcbf48738232cf825d490981e281339439ecdbf3a956b24a28ec734200f3a2710960c76351769be3aa16782ef8df1cdfe21e35f824fb3643879f582ccdd956860a1341fceefa80efc3d34a516938c52e2b8191d13a3392f998f30207e4b8024b4283561c3c9bce328a28cbbd053c1da0ddbed10bf1af500cb748cbdc086911f785cae60485de7a1073fbb8f6a13817dd78f647e7f4b04ab114138dbc41acfd909b6cda6ef2a09fc1093054649735f4eaaedb27bd0fb561063101fe3c599204a5c1216341164c92ec6ab56f8243206845e6a9e813b6ba134d6772539c5fed0fa527717a73649f9ad4c6e4673f870867c4bae602a27f1006e0613db03928d6ca3a721ba993b4088a81e32f1667486dadf279f4b320a3b5e9b39ec6149bc94012413c556108c28cb5249c1f1c7eee98556ba674b19b814a5b3ff366cb0f1ea133ce8fd1755aaf3f146f5da21fe3e80d0c8d767af6dd1c91a0b707228b44479fabb46e9c77c27e4d8482a9aad2642603b551568d14b7987d65aad6accacb25dc56244dc504c1989278a6951de0bc9df09461647bb217c0d6da1e2fc4fdb1e1c546a294d951c149c490ee4a111889c72b4e89f1fb226f8366d89236d3b6b2667cac611f2f78a44029de8f865b756d2094459205223d4773bae45ae2e9e688e2c58b24c0fe529b13e2d6b71b70d602aa4f401ddb9ee0d6457444268003b6214913282fbc4ef5e3b9c139566f8f83176a974c8760d4cf26532a73aa2e9dad2ed2945d854aad02d70eb66471fb44dc8fe8934711b8b7c96f1dd8a7c3619c2fb1785e589d18bb743052c0df4acc35c6e6b349927aba830d8cc3de46aa6085739e7a9c5befc58597a6aa7d1ed0053fb7e6db95c7ff6f3bb2b18d956337c60184a8dbd78a4e68b246b19e3fba915be474b5753a853696fc7b2e646ab39404b7f68de34ae7c20878b5da99b4a6c1970ee3b51bfb998f232219895692941edc6c580f9c45f3b0216a4b126d202387181a7c1469c92a61bc36f13e154a0ab1e78387ce55a2ad7725136041cc246a4d4e354c0c8101a13a3bb7ef5079a6d01210bc76e9234ad751f95edfd0d0196290400f8a1388b673ec2fa5f4f4f9f4e9b3174b12175bc9b43374d520e674f6cf1007d62628270f1e1e5ae9d40c36ac477f1043416a1af4bd92e3a47bdc18c9ce475185ef650bb2d4843486147771ec7011e1483da4bd9aed80887c61e801c08d67df8419e96442cccd669ac4e810904840c79aba2c694ea34916370ae0d6c19635079754c16aec221751332fc31dcc5482d1669a5b843549ca3755fadc24961f913217bc62d401bf6285ef3a43c6602a6e647121bd39f02f150648eec8db9d27865f8841935fa6b4c64a71b07bde59dd3cb50bed5ef98c5289958bf4efd01b64649f736a4c4d8c043f1aa1cbc65b5f99f4525a2001f58cf46d6991c060390d3d6f97967b6ae3c0c5ec24c0c2bed695c3a29aeb833ca5f84b4df97a803b34925c6201626b4be9880c3fcb4a2d580e0f48d7409e914b4484a9bd32a4bf0e40e692342bc835b12e4d20fa25ab746c65caf464ccd7a62951c4d004f6fd1f09c1e81039765b6b540297d2517dbbb8dd61450445190bd7280f7272059838f41580353bfdd8bc1595ba1c0954d218d2802d9c395ed88e18f405c0fa3261be04f229cd97075b5a4ba130deccb056455c25153a30da0cc4c53080be25dc0f7e09e5cc4d98ea9c7d0c3a41517085ef9af1660d1ac1a58d8cac970cd00bf95185a2135dbb42f44caa43f84d9e9e30b90d036e2b979fb38511266bff2d98cd4c6bd94c34d8fa21b34c0395b9ec655d81bef56929cd8f9af5d9f0a4ee7c9b1d198b75cd8e0deee528b85a4170bf8e634c5d7059e488d8323131c7133671ec63440eadb20ae4e3b99a755c8b5e61d2d3ce5b02a9dfc8d2e860ddebe57483487a4ec7fcbc2c0c75710ae22fa7f461c286f4ef8e4bc1cdb894fd2527cb0db5e6632ddc3093ddf6eabb0e1d4834a72173cd5be62533d2ba16ca592aeeeda764ee78c740e8ece9fd494c50471ac0716e3b73d644347c82c67ebb1688ae80ae3244057a011deb7f1c48ac0d11ed584f301b18ebbc3630e3fa6201ad517881d0f55e89975d7b6cb53732d597989b7cc1259b0b57ee57402a8636cde95480a381bf1630e2b1915a02f70148d39865904cb037388679a43b329b38546ab1340378f09fb8143323c4c22c2684bce726d8fbb0a8df1be2be4ff8e18a8724bfdd66305d240859782718b6cebf6ade449094da7498e06e413d5620787581dd4405536882acd2d3d540fc817e5552db773b897ab19bfe09c91a0c6f81fa69b2fe1d27a4c94a4f1587b3082ca23f4deabeb4ac71917c2d2b15e7134e481bf7927425cca9ddbab7a8a9272ba20c4f6b538dd32dd6d02a31454f9d0e4eee7017c1203a3c050fe7678907c0903536708df499fc4ac9e9e2fb7f204dea9eacfac757e65f4f84c805cea9b14fbf17054db8bf3d27b1e9505e143474ff8097a44390bfd7ecc6bc1341006c6ab8f4e2252fb13a22c039dcc11ef6710a81c5314042a2384d5886832d1ab92be0bbfa11f4a416fd3fc3e7cb9b26ce7adafc068b229325574b6645732afbafb2b7c95c84f2aa14382da7f5e34db02e4645904767a8f1c78339e9cc6daba8bfc266945400d45b4a2abcf39ed5d3b654fe17433dabf80f4778e893fc12a2851090de76d1ad136975abe99d1156426162825e0d58e1a4304ac5e8956d4c25db884406f5b5961ddefda31b839ca063a381e305d512b7d9238c4e31fc82c35596197b05e1c52d07f08a90af9dc1ef63a53aea4e8de8801f8a08ca15101e69522e1ac65eefc6c4d7db5f04d9cc4b058ec4699010e281b2aa7e99468d2efaef3ca4ca29dac3041461155e25d049e9a7173f2984ca1813663c0b35a0c8d1e84d4ecf3e9c3fc1c0baa748302c5024151efedcb2a887116ddc9207c40c19c7c33efb666a8ee12515b5dbef4fe665604bb7f227b6949091f7f469465e9326cbf0c1287dc15df73e7aeae07f4d368e8f9c435704e012ee41a6b50b32928f7606bf9a705ae5dec7bc6ba0676d019124ffb6984abd58a0bb0bfdc9703a40314224bcce75ee21dca371a304958c128021d6640ec5d5cdf149bc63fff0b67cfb9cfd5b2b49461491b005fea63b8d8c2229bc4f02c530356c111b5cc9831ddd46eb12f3bdddbaae2e826965fba39517a7e9e393568b62d7ac74dba8bb90b18fbea84d115a8dc9f35986110cd9e43dc0d6c9924defe73c57fe52141712500c9fe07ec4bf18adc82caa3e44dbfdcb6d2588e77fd53696e85ce66a27672bbb20cd83ab7b01d131ea152d4b05368856df4f2a66a4d198a91d439a8c42c5070d9d834cb75b3ca6643e75e34467197eb9476d3f0d5799060a4da5859ce45cced2cb71387cd8dc79a7a8d640684e3246fa6827dc36cdac7600cdf5c66510d6488c5692167c029c7149f8b2ae0feeb72d178e552b0e5b2960d35a53ae3c17f665dc8b7d0a87fc4c7a6b971c09fe8ab9e3bf3355e81d0d35417383ad46712531b7490b1f0cb20ffcc2f4812822794c15fb9ddf6ce72e75551b37c5e7c691fab4f38ef81f679dd7763e9776ae3c34267d702d683a8c76c486f3a87c131f7d7758747b18993a0b410a23e493a7920a25c61b252f5451158db15262ee087345ece6b86379eafac554606d70d366cf89a83d3e6f10f344e5c4cec5f8ff9f97e24f71e12189e8eed608ed5b091523944c53e7fcb107b18698e3a25dfa718998b4cbaa34a6bae8b20c9246f0f8abcef524bc55de67ce26cd27367a30faac5133d644d99d171d9ab5d1ef1a745aa004fc162a474f88f478e290c1b9d6ac73665b3fcd8d2a848600bb52ac31f27e16e82523e54e21c035b324e54e69ec140c11b6b7262c8c21345b5bf50a0eb8618e2ee1e0e59a073a0d2704a87a1495bfb1362199c143991d023a811e1f53d92ced3113e9ed5418f7c9e0877de181f41063592862e1dfccfe9c86ee488500aecd82d39510533553b7a5acc728c4c8b3a05bc38f18d13fc3944ed1b36bb8fb50997d2db472079d1572a01372dabc3e7fa6cdffcc7b51dbc07848c3bab03d1f20017e8cb04ba8f3a45e222e49cf5f908976acc38907ab32291e8e323d7b203640573cef3ea39d15a88ca628a33ee118060e9a3127c60dad0973e0f54dbe78df18a3725606c79455ac0cf0dce7981a36b0af49f436f328b38c104d414ca3f552d1857c832495dff3a2a88bf0151689e74490e902fe6d244787fb42067380a664dd2e5f60ab9682f55c2ca07f3e941f8002936503fafb6596edf44e8c4d1f7cf36d602b301deafbf3143e4f7907c6059430169526585bcc645b5ae1393a533c23f983ebd2f4fd0cd6e5c628792bb19e9497cfa50a7a254abc8d96bf4eeddfe15fbcce8fdc1d1b11614feb7217f67c70be04b955a877810affe6210324eeafa22d36768afa78bfaa39b7934860ca90c715c80cce8f9aa68955ed12c85f195b1636a8f40d2859c8d139e484d05f9e022f30395231c2e22e4b75ca3eed6c1efd36d89b5d1f7d0a6d8cf983080f787a5518722f7a2e45813918e8cde98771ff5b36be5ac2eef84709dd06a5251483fc874c39880b2b265e5def4ee807edf0945e61a173a62784c0b7ae903c69889ad6720026a45fd602936b1097300d72400ad38b7139842b8c9abcca37a13edabb4d77a655e9ee3755ef01ceba604e47f86085be8a45616804eebf205011d88d66bc7dd7eb40ad8ab174ed0e7355f10d94e9a52181f1723d4f49d8eb1b2ba771c00f44edb6967677dcda5ca931936d0b89cc7f5d50dcd21f8c2b657e717365b9c148fc27175c8b80200be301d8be9c1c32877feb686943f10c88b9becbd3ac4169fe3a7990081db091a54998e0225bca136889112b3f04bb404a969efc15207effd9354e9892eadbfe7b336c1d2cb8212513a081dccab63e36cc7417986a23d4fc1752f341f8ec0f28148f63c0efb631fcffdad70ab8520ff560a175b5e624643e0a036b65ba13f2712ad91c1ea19e50f7f8ac709a7e2a2450cb25fcd3017583d298f8d1bd5e1131b62d2fd77e1bdb8abe9e7a5176d1ff1781c4832b8717af68ccba9daa45ed375001be315a5c5b6dddc2e5801df32d2886cf783f350ee9b1cc3cfde6a9c87465620634b7079f05ec1db3940b517c52c137c4093a66211b2882a30471df3b850086d11a4e81c7de13b238c6fb3c10c1f512ec0adc910fb4a045ec03ef52eeb30434caaf8f8b7550e878b06b9e7c644351fefa0510c8bca037484f46e9700d191fb46664421968ea6387fec8d2df4963252a33f50ce03231ce363ca8cbe049d4da470a624ca8e5ddb89a0df380030ceffd7151ccf0a13b9ad3bfcc1d449ea284ddde6702765a777f216205586012167a25c8e3ee2a99eb739dd91928f16f7dcf33666b52fc7304cc4002c5370e681bdd39dddab4b6d91b38504b356560153ddead041c97f329a7e01274b923741060b2d0755ceb254290ca573a77015dc676df4bd621c579ea067e6da1b9847e5d82d82b2058527b04efec6fbe453c8eddc181baf89909b9889f8c10612c16230d54f2fd3b241fd57fbdc73cbe7aee65c8dd7bdffaab40baf61f9546b45e68b7b9e6e73e86757be7b44d66739612005f9693057601e47d0777f64615e89a5e978eec63109b570147dfe63b924ddcee8bc1495cbb32ad871e7033f1fd7b43b01b7bf355b21f694383fd7a9829567a4c750aba432b3b6858b34b270d4ef0ee69e05dce6f878682451152ea37b421213e48819a7d4cfd819fc20b83b3ffdd43685be2b4aa299f1a0006272db3ff0527fe541654548dbe9e3f747f4e232afa1ee2c20a40bf8e6430e1a05ebb0c2537a759dd4b1215306366cecd41ba3d02e75ede43a06eae3cbb3d47a69a302711d82f3233e39092478bbe1735ecae096bf48bb788034a3b8e11bb088a047b4c9d63b1fe269f0d9ec3a24798c226fe2f1632749e8f17996c071408ec6900daa637f0ab47c675ad948d36c2056480457f3475be16d1404c0dbae72131d85491c71b10e52a6acb75d1b4209ac3ce38e66bb6336976b6b2f22cba9c6b0676917c211b50c7d81591b336ab5d356e4266f0881c57246b6085969da2caf1105ebe9d8ef596ea1825604ba7919ccc531b218311d8687773bd4dd2762989fee75e9e59c841e72ac44aedf68e94dc83f6349fee643bbaaf62f031d094c436879a050e53f2e5452147df6ce55858a9318e1c69d882fa69a4e106b427155ebff9277849be0d20c7c8267fc28df775c8c3841313bd933b6978eaffac3cdc18553304202394841e049874f995c25e076a78f8e1236e6037e2a44e2232d71125cdf699247544a1287b5d5b8ea74108442e333c791396d869a2c02fa35e299ea4120dc3b5576615579cbb9a7dfd08722d920d56668bdf8b526aab869c3b5db39f61565326e66b74dc843702e5c4b59d4b95e28214531832c544fe9e53401242093c778dd69c8eb6ddaa6bf8eeff118d79aafdaedf918b25ca47e497cf24e783d34a78aaee9900b8dafb15f7505bbfccc14545fdf650dae30e61c9e4bf004a72149da456e94aeeb9a62f670b1ac59ee74a9931450f67cc8ee60f9fcdb2f04d4c3af72d32ee5003ddf0512f56d19e2df05353a091e537ef6cda2ed1ee9bbb32165024a353fca5f4ad5cc4418ee4a492fa406f1e9f91f15a67ca2e67b6aa32a016527b10c1e44c09952a3bb17d66e57da1b76b9162a831a28e9d6538e970455458920ab7f91416ba65707652801d60507fcb7741812e3cc39282528c1f5904282a2d1ba36c314348d1e1b94a09e22939b83a6926d75a2eed4ac0f9f9426d0bdda08dfe0efe7f139af128454d1e4d2d176fba8d01f128d70e2f16badb146d0f1a1102586b872283c3c2ce4e91d7b6e2f74e53a93e93bb1f70e0ac3d37a734875a9760541a5521c760a2b34378e38499fc03ceb3ce338f2d0aa096688be372ab5a93bb8167a845504fd8174d5204c92c49bd2f272f7c7ca8edd3df669003b046b62826390a35c45e610a345fad8e2f3d60b346f198cd8acec9852f3c9818a40f06bb88cc8ca4b7705fc4615221ec3d4afa9d465ae2b8923f92e3f776e884dd87c47985ea46126b150af8d3c8991be144f6397572f783b0aa54bf36e91781b798a8d16cea359074efd579dfe2b42d4c757bc7e6d8ecaa86c78a10c6883598a068f1f641f2e1a29fefca7ed303ecc826cec58e4aed985b504598703cd93f2ab122545ecf28abc0a71247266229e2a50b075f55c7be4ff8c399af9c5d813eeb91aad945cb654ed8c22b64ee989761115718b8eae749d699c5d90bf81aee2161b276ff2b0c122dcafb85673c69db486f45f22f9f97d7e187e905490824256769cf0adc52cd29e8eb5c66f1d8c065e5b3618995c82e8f80c2c674a7f8062960b3c9ccdc4103932a83eab8996d324100e843921c3e8f8d987c19453215e190eb41cc2df043c039134f6d72460d2ea42fac9f868ddf5013439cb50114529f3558ecc26194e9a8a48bff0c71cfaac961d6afa2cd320bfe9b293d49e1dfc4ce162cdf201f508478f3fbb9bae5729e197b9838a321a62960f0322cef4b3926fdd8871af72090ac957939946b9034241953f29f1e8d2a20dc85be6d91900e56a97feaf05a65c1bcb666411e235981dc22470ed40799b0dedebc64b61bf48ed6c3442b891c62c553962f4dd7cd0f584bf9adead76586fbd559d0b2f27d841f4cbfaf9961cda98ad4b7a812b4eb5cc7595f7f603e42ad9f794f88a1bf7e59d502314e56c716bba9e73b058e9c0a2dc8d14026a1b2e75abbc36f0196f05d2d676701b0205f60f462f6c42b88ee10215be46a3f507170c95cb1c41b940d47771b8c2c8909a1c6e53fb16b1328fa57cd56a6d44674cea40a81c36e62eace9371f3a7121bb257617be78ac31a428396d18390777517ad29007980717e07a4deb5369358470423dab9693aef8ac4449b137bfcb7519808ffba4aef8f865930e5588e4f58ee98f75207653ac5b89467d7a639ac5db4576c4edc561e39be0fca3ab0117b217b549a9c3aec4b04766c0fb92a85441c44a87c89d1229f849557fbca2b02de09bf1b2abdea01ef97c4cbd0d5a90acbaba9a16dd805aa063ffc35032ced76e2cd7ee595c6b8d2edecfdde31e543992e71febf89d862016c065a6e54c106e4164153d71efd8d95d055e04d318b2cf9bdb63706acaaba69ef76bd0014ae8c3cab8ebf8ede4d081e697f3444bff2c8a6ac7490b0ca51f526306714a99f97e169f8099f3141158fba49f5fca84138f62894a9336267611b93d3347f710438e9f5f78e4c638535caec48b661650ea4c2d0ef42a91f1dfc97ed022111da83c00a11c57225c63e7d75e801d9c9d0f4b423709f95c7551a224c58ebbc8f50150c2d9025044a43d55dca895340d5d3b3fa72fc67662ba39f4f4d4d3d6603b3e4a1161afe9bbdec04f2f9a1e15f28187fc0bb2070827818826b6999afd32a60ce87db7ed93d48f05b54710d93103ca78287237bc6e7e429ad05ca907ab123e9e6e4421f64c8bbbbbf45e2041375a3ae5751f79fd97dd09a9ce550c4d94ddacff3734e5df1b57e25e6234a7f51ee79bc91ba2704fd5d6b42e3e887f7905512def0c54d4af76c7e26dc2f71480020ebaa32e53da0eaabaca6a6313e7863a01a898a54272a9020eb7480249ca41605079e343313743cfe1b1120a52c429a0d7e86a564c1eefa405f19071d6af7501454eb04d1d512f4562fef1cba3b8a8370432ed8b8b481167c54ea79cb18e9ac74d17e01102406f5ab3f067f974466f08c8e87ec13b38dec4d08b42fffa8f2ec415b550291482688ff5541bc826747b523b4e4608b4b7c252132b55b7b9d0e1ae1a2f0cd3ee1fcd80853a55e9e51e62b0f13c4e8a9f68790ff2c03801d4e89fe642d3da1187be6776c334682f80b12b06f4443ea6194159dd2765af6530991aa020a3fadcceceed88472ee5ae92d8b25e874d2ffcfc503e533a7da68e47d33ef9532fe96227a53d2bacd87abc18cb66eeaf8fd78cdcc41a0cd3f2f550227ebe535c897f5350a2d236eea3a4fc9c27bf6136e14d752dbca2d4bf8b092b5b4bc67d99d0dd780fa03eb6da0a9b58d2715289afb2751aafc158ed9671dc690aa29940bc3693bb658f62d01eb9d5c8013f0f7c6836953720226e9372e9decb7aeea50b00010976c8da8517fc2055be588fd5785d824fa6f1fa80858fef2d5364379c1d879a35498c16fa578601adc119cfcc1fd44b599311afbda8e2cd8a3a7e3edd2a7e2ba4dd68804d76eb01f9cfa3459b342fea13781418cf054a2bd4f29c19cb346ffb954869a937c1def3e6d1204332e0d4db20ea78cb20097596225a6962856e95c8baeb5a3318d52ba9db578c401368982310e7c91136696c1f2436a4f78f4e276a191d97b8ee55613f9ed77251556320acb574c3aa99e59afae1d0a7a1987cab99aad594643c4e615ba2254f5df19c02a5fc38965b2cbacbb65fc98c11e4daf1620a0ea4a3e4fba65b076696797922248f1594ed93334b230747a2fe7764b5d181a80b88e9e0d9e945ca09a8f30d58eb858dbae3432c102e94c07c2f50dc8b1ac24566178c86b1b03fb680f6d2f7b9b7ead39ddc878825ec2066847aac7cdff133e9dbf089c3ab1fd4d2c431e0c236d7a821c1b849428ed85d6322f4a87ac84a809c784727c2c9c187c5f63182cebf2e3de5c7c58a58a0e68d85328620676ec6fa3b8a31a485a2f0c3604fe7b8efb3220918a79c8cb22bb810e8a37273183e6a8ed498b4b77c1bc93bd3700e153faa643e5675283438e74e503ea1d741ceec9c675c6302557c00d1155a404343b97a8d3016b403baf0fc631165b0f9d6e174a8d8a71ea93bd9b6bf6150ef27e0601ee982d3f791fb50d55c30119b993411f3b3ee1fd1958a2fb37392acae9b67b6f1695094301a4ce9181e8ee3398151a6f660b3413a768c06c154feb4186e95e9a466d5a094e8119c996b8e80da0de8c316f3e98d0493ac2de9f7777726963543ed5db54d8b8150b878b7058de301a90d5c6c8e645a5b58225b554e46d1baa129d07fb802ac19222c35277da335e8c448eb12d76f631e69b794d1354d93863adfbdd7a302e1fa01915bc6b846a852d37d35af8905cf8abd3f7e3a6d86ef1820b23f0133447a65e9e3be4dd7d383432b9def92f88631bfaf51d6fd93e7cd65bcce059c9f48af7f4549935969f245fb9f130067cfdec326c5352e9319163f969f885b7cb55440410bab4f88ba709e89ab90b7cea044c763c6a5c69f360195af3db66320fcc72600183b674ed7198741027f7308a0accb6e436e7cab6a9a7a5d7525d9de6e848656543ee9fdb91d49432ec3d384bfc272af44805e76072ac44299e46a1352dab237b734d60bb96943984506f57d7140e05e4ffa95c7724d8b9699583f90b5bb626986de95b3e45047b069bde8a54c8a7e5f4422be915f1725083a678c0bba33906629c7d59850d24af8e37229a43c00a0e5f9ec7500681d31adaa010ccbc0035c9b7dee730fe1147d058bf97bcec87c36e5fef1dd7bc9be9400eb9361aefbd61187519a94bdf2496331052da4d0c875381ebafb3473b67b59e498c5d4d0ebca840790a6ab5623c8b8fc8594f92b12caff215826e8e1c61cdb2322bf938683aa9059cb372bc22007652a1e99e8b81b8bf1d6ed21de04a37dbdcde5d047e280736447a1f31223a3cfc480d6432ddcfe4e055e447ee7222351cec5d637a37cbcdd56444d5b8db09399294f8fab9c03fd06301fd3ef7c60210e1fdb868ddb688db429ef80b3e62c63006f246e9a70adfe90c845cbdae7e907c11ac7ed4b150cc19eb07b3909c932e5ef74f64fab23eed4e93f9f52171c7291ca58b364d0139082f737cfd5ed2220e6a58b5e11157b4c0fd26c0665500524f002bfb5512d86f78ae2f4c491d80986d4ff17758ea54cb7b7de5d24865feda0338454190d8c9bda6ffacbffc0a6de39128dec20d302c30841e65f485447343649927fb9bdb68c0c12e276abe7004792db90e6d1071a630eec32ea9f5e14c39fa3f1bdbc740cf061927134ce84ed20565f4c51fcb194e204e3a3d02d0e5da6f8b0600b7f8361a3e50f197c54cd90aae892f1acd44daf3cca17c8337bf2d4f686799c7e6ea79eb6d1b6f0d59b74a01deaa2c7a76895d270344ec63129a8e44ca759cf5f2d0ffe74496b6853dd3bdb2aa61aa2343f531c3221c6d3828117a364c87c62593a9a13675889f40c161043ac277b0aae65b1e60c37b8bcde4d8e5042cabc183fce67f0721294d357ceae85dec4b63765746e5de685c4de3641a3973378b16518041be0a57337f8f524fe2001b16c242d5c0a4e147ef14af203514aa641067667acff2f49e5d3d2c47ee03ac0a6d569f3f73964f1ab10658661b7eb6fad1c2a98fbc4dfe958600201e3be01273af856fbcfdf0d30374b97da4d361074a11ee9b3c3a10679b28d530cffa2682c732e76baed3105d861193ae84e84df1f4430acbb38362ed8ce61258c88a03728759bc9ffb0edca086b618eed5d1162dc86e2d5f0c0ffa5893432d4500dea2fd1ef369e9a3f5899360fba53847a0545523d31b91c0454af0fed7d1dcd819d46157879c7da72b94f10b3a72f7f175b36bf1b085307c5e2b01349500b1d1fc6980576f93633dcb9cb950d4878cb495b552311fe11b3b32fc5ea7ea0a910a79de6034dee8f759dd81ae0b5c55cb5c13d5540e3d36cab72645dbeadd6ef9952cbdab113b0f6985ecabd782cfcc7ea26eea9a883dfac52988cbd2c967539c9ba18430d26ea63b02ace200ca5b99e1f1a25cb7ad0f642a54ecd59f75a803e04a8001605807bec643ed97383757d0240676e61e61a57b2be4781b24c2c426619d1ca2d1565213b5b45da651e765a6ed530faca7b41a838e1f277946727e9a6de909e4bfb0d15c87cdc6f66452fa1dd34322f30ed13f3f2be35e116e94d9c687449ec8866aabbe117bca5b4af0a358e43c350f746e471372f182eb08ef60f9e5b58f30292af795fad4553923eddd301f7fcffadcdc9bd9e2904882077ddbd6f238151ace470be33e24443ad3ed4f06fbcd97a5af9c1ce2937af4f05ffa843acc8f3500406e2bbdac39ec6b80e4b232dfeaad3639874684ad18d7988a801beac7e52d056ccb3a646814929416f5cb71a7dfc86129870842ef14a2290433545ee8411a25e4d053b769d8f67ec3692fa32619e47415e1468e293771f0cffb887f6ac949695f846711fc1481427bb7bdb09e5185f3f40b4fe8c293a032f057b19e3dd2e8f7376046cf3f5bb74475920c11494be56ba219be23320aca22f033f82282c5eae6e9c3b40ca72407ceac122e51d45a85b15faf9b62891384af5bd8805c10d25dec99f46c787a0912915fe8874ddfc1fbcf508eab7de7e0bb0444e62cadf26e92618bad30795647fb7091a0ac457ed900d8fdab375502cac419c06375c0e7adc091ab404c40a8976f2889f968c3bb0c0720d1dad4caa30b770f66d8a8dbe15d15f806472dd0365661305347776111a3e7b09e02e4f228d6f772d679206886fd50e44fe1ee6f96f7b26a3ee88adaf25a6e5e16e9d16204ac04b32f683eb9b27d62f85b88fd7eb4cf49a25dc9a56d43d4cd7a538a575f3b10dc3696fa46b8f3d3a714a56d341e502843e706fbe990dbca462b50013d14e7841d8fb61b67900c5dbe93cb6b16ae7ea7452b5555592007218c236d0061edb5d93a2a3bb679ad730f0d2a0f8704b424062947aa2c35b209baadf4efd9069f68ca54cf56315e60d27d7258d6fd93527f166ec02716f07e9424942b7d18ad6955ed9ce33cfdf14389a9c0242ce965239e1a413a33bce35652155732a4a7eeabe6f19627a72fff0d2aa5f0b53f6d22ebe56a007bf9c23c745eb1f846548c30e3182fd32a5330b2cffc063885e07979528c6c8154eabcce93caa84fe1622d1ed2fc81fc2d827568c97a2b6a10051b4b891ed88d8a372fc2a31b212a91042c36a2397c538c7950eaddd1474f5d4959597bfb4f530857a64e83c86ff3b458cce97b88e2f00e01ca6161654cdb286baa2508d646bf5287daf2c2bea46668dded103f377b69bde68ea0784c976e50cbf1db813736012b45e4dc10dfe417b2f793b4bba72cadd87bec867dd1768adef0106d91f924adc1d58074017e94de456ffe7109ce6b3bae15dd9de5f37ba32704083b80cd7d38b0bc663930e4516a29f151f861b6fa06db8c2436f8e39cfb8e56a6f8d1b635cb2b47aa6749cfbf8c278dd06231fcf6a266bd7c9e398a27aeded7ca9e0b8b429f8905321469ee1f024637a271e5523893d80ad242d1008457999e34997e7961683a493bb36066bdc260472de362fdfed90ad88fc65816c7024f64e38cf17e2d7ed7a9ee3ae0502727a5fafe6fee57f21c060ffd6b72cd1447ca09085a0aef85c2b982045434383b69e6ef432bc54e6ffba49ea85a3dddd2bedd0d853b6ff3447ed771794a47a0ff4bacaad04996e3267015f5e5963e2fab42b517ff25af0546ae53db52b0d67b4ef46b14ba918ead0c58908a59bc9d635e92d1727dc82376bc7a3e6673e45bb24f3c92f97df4ab549578c85595cda44b6655408b9ad029e8d9d2837e7feff9a2c30be689bb159e16459fa353c5aa4a6e689d357444543ed4c466ead1d168df9c1d7f50d77bc08e458eb4c9066c0130867854e7640d83ca05df7952d37f55ef646ee7475d0a8dcbc6ffdb76f32ec7de022b416e350c01a91b5c47193b6e3330b31eedf1765d397093c3fbc623b3914c85778a7be54c8dba0a28f3a28475a274b5795f90e30e76df3a6d4562a2895d98656be5e415d1d531981d41926d965ded8c077bf347e4290e5d189a0b042c96bc2dacd6648d209b98e28c8c22793e76d8cbb7b07babe76d6330d7e312db0871761bcab16fb66985f2d711ba6d709c034efbdb37acfa0fc435fe70be1642a0edf3a36240a91e324dccbbba1d25dc04b83e19f2f46e86794abca57009d255b00cf2ea123b5af8961978a67e8f698d660e15163f7ff1812eeda015833debb67110d54b3afe3f991d3632030428fc16cf010e56394f72857fd83ed4259fdc0ae4d7ecd68c2434fc13826b11b75ff64dff804a32c35f6c0decadfe7da5a8157a2aa787cc75f1743f813b0165308a95cf0f10cf7635cc4ef99a3d303c6c456733f8eead244ab5ca24a607bc66ebe76a79254a23e33935550e63c63754a0b8a5e60ce5610a4b9561910d5b0ad04cc86ef0353507ae75f6247463fee29912492c48c1ff055a7861f2102da38c32f9750a1adc99c0ff03778a362c6640f67c0c9893a3a61d293362c05095d3b3707be729a33cd1d673c01e11c676a861e9106ca00424aaf33db04705a2b23865413b6ecb7afabaaf9b61ba37766e080802563c6d5b5154d2903aa59f7d1db250b1c3dd50513bcd647b6358b2ff8820586858822b5ea52461d145a23bfa7ab668a159e50a20bc70530cc2e881feee7358532740eb56e1886ce250cbb858edc2b542e29514a2f51a60ba8329a97274942c93a3e2018ffa24d45b982c43791418919e194e9ad650d89fe613aaed4021dfb4079e2a46dd167c9c06992071649ee62c0f1987147f6b3820656def62af4911792746ab7b12990c2961627cbac1674d32c62d3bd4a56cced1ff140375ec36388a7ce8372d893d31741fb903c9602ccb2745ffe07b1269b72df1e3f6c6875fbd5622fdda069e4659b2a7094f75b2c70545e50535a5d6d7d80a6ba22781f52f91414c86307934b9692e932a8dbe4e05095351514b01c416c93b1372489a0c63b62bc0be3d112da9595533efcaae6e015f3a099fa3b5f2fe93fa8108813d89eaaf0557182079f5955bd7088aaae86f19a4eae1c039963d89cddbc1b928af8d6793ec7f8a2d87f8d6257d49bfb48e556c78597f53cc21c382a4fed452e8f81c33c24cb930601a28182cd061e7ad4f44ebf070e0c4d3d26751e7732ec44c359df68a021b3927c478e6dbfc6ec198a608fae9e7d3aae36c50c82c62399c148a5dd43577ce22f768634c2e66c41551c88e9fdd287c69706b6a0c9341d151c2bf0c203d47914c3312657e203a6828e84b69461f9ed329339ce388c6a5c75e3dec3ea7b41434d34a258197e5f696fb2c564e0d9ff42320a63caff342715f2a8cd5eb8921c71ec02215ba65e3383c620af498a796288b2253a7056200f9d1c3f088efa244a0c887276c9946cc597ce2a45ac4cdc179808feebae16cfe93cb6cf2218b80993b40d89fbead1368612e6862f7f63d04b7498478aa80655f4eedf31de4e2ec8bc4a7b71aae071fde885bc74595734c9fab8263ded2e69905fc4cdf86997d06a66db3f212a8ba3f9219c30e4860250459da9aabd6875ba9e4b2ef66539fc7e838792edb217361eee6c058aa993a1ad7361d541df186e9e878c9553fdd2892e441f275565bef9971768ac155f5aa05dfad324a3215e5e0d716d2ee33310991488e8af93bada7821a9592d8de6ea6bcad6abb65e3e8d753a404b09d91f7c1dcb95ff1c1035cf342912e2b2cb8679bb8ad12ae66bfd9b199bf8fcf3d610fae6f1c135ce7c4f66f8231509d805b936e9812a1d06151c2c4cc8290be187266ddaa0b8ce816845c8def76f2e94a7c358e9e5941593dd765a44de30ddf7134d89ed2e266f713d2acbeba79d3ec350a04db305ac97235483c6f3e37f3b1fc631dc48f55339a31ba3da4197b740c9f0357e958e6340359ab4859060a0a44618216636c9839b8db00f12bc4818c321c18900845fbc4bc0ebef3b1d100aa9bbc21fb365b198c9a0eb2823678068220f11ae491c881a4348aea24030ecfd0a120ff37cb2ec86726dcffaac977b0bd539a45f03ad89e3474598e13b5ef3a94e50fed05491fef242bea702d380622e895444ac58b0e94623cdb908ad7ec3120c5048e0318499b3acff25e2ff2eed46405533652232a30a9fcc349984e40edb2fbe9de5b812c95165642c56fe1fd069cdade2459288ccf9f5199b7e4a56b264950a191432c2de9cb2e3c5f3a4e82b6e56e2ec15720bbec38b93906283bae5bec247f37d7991300da79d416304bd3ae271243990d69e240120d53657b459224d5f2cd3ba4bd8b478efa15c33e8fcaa4780c4f408abc4e74783de92e7e7c6196d35347ae666fa66ac2b69c36627644635ac13841ed7af15e00bad35d89c7abb8af159dddc5b9561af95d1b9e089ba1075d4a1ae141f0e2d8d7ee3e546b6ac30974f8f4c119f9f4651befd82e341b474e3d07c3b12070db1a304029f2659a4013d42466378354bda358fa2b25b4251fefcf9136f400ecbaae63f7e6fb68437c0e211c7d08e15a50e9ea4a03130fdd160a4c3c29cb639c00f0b9a79fd0c20a18997edfbcc86b743214c2ba627ef8f69ea3c6c0863d4370551ceaaacf70c14976794950167f02694a625770dccf6bc02a31f110ed086f3902f4bf77c5d4fdcf3e73e25775207272659a2bcd1c91c657b5356d453144c1f775ea83bf25934bbada2b4f3934da4131b3c4b1e160fb7bff3cee4684d7d15438c1e6087166d78690f91e638ba62cce614873a851f8d53b653f0e69569aa227391b9481fa54a8ba2910a362be8dd5bc5e9c9a2332caa4a69a448c1b4ce9c7d2645e427f658f7e598bd4fd51bca38a13550bfac890d126e945547edf00675642c58856bc320b2b8215c188622714847b8d561401029a24d9417d063a876b18973f3b4b942d8f0d8bd396e52ff602d77b5521b81ecac58e6d7e4e4f40057e109cc63d414bc9369b4db5d742bfb100a0b4e27318bff38aecab735d98f6d2dfcb7934f81f87fcf034ecca4ab0726598fd64013875e4eb311a65e2c552d4bb02859127fa459cf9024e0c5a7a836265c5dac57ec2538d0a99575befa04dff1c0c16aa1176c98f9482e2bec837ccce6b798903074447cac4b2a6abde92f20f7a59bbd8a3c248f1a1dc3d91e714d99a32192de09d56499423037a94d63e9270caf7a5cfe4d74aee1b50118019e8cb049afcf448f074a2e58024b3f319cec0a931285e7c8ad42f416571dac6354e3e0ba2ef26687c7f62d77a45f1151c6fc22d0de4f51fa37cb1dd93a799c5c425f632f8b3f9fdf43a69c582a62fb1a8d2077542550b4b35b3f37c47aed8766610aa9e9f7e3f0a54cefeba6c1db4241cadba7d80589b674104256b32150130c6f6d5aa4576e010c846fca9e9934d5851385a00b803a4ba7a1eaf3825c002a67c839f7f293925830614842ee587e2d47bb167eb651997072152f73020674a70a6a6ca4a1958d117958a4ae155894ff09dd822f86b989c3f9f17afb8a7140884c211be13ecd70d1c4b626e7a2dc09526ac73566a1b2ca5e6f687b4f55aa9c561a1bc975b0baf2307a337e146bc6559fab7cb344dc6b94c188bdf826296584defca6351fb134d06db730a60b20946b0f6ad2e44bfa3d58c569103332fbe29e037c603f5f15110ae1b51bdfbe7594440ac4b6b5b1f144a3f8c49f70b92a677bc8c1b61e114dfce42671f9b31cb56d5adc3f359ae2c5a9970ed2167a3697273f4bfbe831ed4785016f962ce93cb18825a7fef50ea6bae72829f16208ed22aa09473cc34c284b5d16ea16ce801559a6a73467ead62632a3e1e53f094a09244551ab72a8464e31d8321fcd05b71def6be4f13cd534d806995f739805bbf7b423e3a6daa46a4c0d30afdef840146d934431fdf90250fb7e5c85e029bad3f28a6dcf633bc91bb09dbfe03c191943a0340d3e864888d98300658a3c25fe8d7b26a0007c9a840249fce6e773f9237d9dc27dcd8d59af8d1968e7c46fe4a10204c57548c2482724cabc658b1dd11eb8346acdfdf58a25818d29da0fd2eb14aa2cf91d219c4af0a5b6b7a32cfb6751b0d86a0f2a06f32a1c949b3e1847b7f607bbafb4a0ae00d30049c33642cd04e11e036b33fb09ad34ce1f9419141868c408b250ee1268514ce6dfcfdcbac1fd47eb9e6b78321d73ed6a4ef30b9b47d567d35a51b8d9dcc601324999938d720caf6ef60a59066cf1c4260ff4892bdd8c6ccb130199a47ac47f41dd11aefbf024d714cea19cb7dd9ad9332c5f70bac687789ec7251284019cfa6d146cf29c3b8c9cc72ef5cae701c8953ea42004723abff30f726b0aaca768725592ff3920f78f146dae7834722775210f2aca91ccc8504e30862a568898b44b1a6b1fecb006c9a03566f96b76d02e0d94c38bd94d7d51feb0eedfafe0377a24cc947d7e949ab4f8d2dd84bb59448c9c5a2fb2b6f517d382fcd0f58db7d61983d4154fdf4efb7c94dc5b74209fe8bc36c5d3586a042d88ebe8e6f9ac31ce0178f645de2691414a816d72f611144b77f6cd48885cca3c3944c66e39c4e5f47dd93d3a0f0b98fb58b9b3af6623f93d0f7e58904ccce1fa0a3acfe5f7f66e3652259fdf070c85b6221f43d5d7898580a3bfd7603a236a6fac55bfb5c05cd7c61da3bb40e686ece3fc21e8ede3f3640cbd05e683bedc5cdc415f5e8552a959bb07a53d2f0b8f9a31e1077afea51c88347bcb36a8ad3f20647a3bdbd489ea5026a1a00eeab55aad94d5a4606ff0c0d8985032e47557c689e36741aea53763bce64de651eec0c3358fea6316f444e5dd0e75d0510892b868f99ed14f9d5ff65cae23deacf328e3f6e81f4c467384920339657f0bc9181dd2662e6dbc0b04e8619bfa8b9a84a93ea1aeca4f1c622bf547260c8add7d703b674ecb3412392d1d82a5b7d427da3b94528e90d8b055a6d0b15f4f57d3897318a8fc7bc3b5e64f1e400c409b754887ad415c4d5c91698a7ea967f1aa4d2205187a6748163ec47830bc8c988917d61dd0fd3f61dcad0e1b9ace61c52ec90d9d112e4059679f5b89e01bd03a909b274f3e449c47a0be5c2db9d349d22234288973f20c7badd4ec23b4c63fa30a04cd5e833b9a95ef268e034201190ea9895c4c35b635a1d170c60d7c90bf2c0978c767a51dfb0ef47ee23b12adb9f85a227999a6338f2ce509f733e28ab0f8878ea91ed86f4ec33b52b0129c47e9d5d199eaaef8acf93a7d689eb896b00d2ff8b7cd3388f75ac214e3598b08c83d5f0dbd47521b7f08a219c9c00496b0b1966626bfeeb1cbe29670445b6b6346b888f491a770804be7933b7c8aad053d335bb4e76fb865373c4ac3113f7fa0260a0b3c77b4c684046aed2e27fafa9237dbc7ee787835d4bbe0dac10bf88484f33fede3f2225616ee7a792fba4eb202eb21243875ad8d8925631b54147545108acf75d9ebe0f184481f6c9840cbb0f9c56f474cf2c3de5a85c852349928cfb3025e1a45606fa4cad77ddbf238d9f0ce98f68612286219d82044bd11007e6f15c7cbc40db3f73dd488564a922a08956641be199250b30d00810ead161b5f84c3550677c26df8343e65dc0a7ef9164f7b1f47d534662b3d8961931996d0e8fbafa8ee2ec35322cf80f213d8d9d42c8cc484caf433128034e7fae643d482de39dfb548d10891d5c417e9c14e96276f25ff3cdafc95cf4ea86853927b16baf0c421f1b6d0ea6e8755ba15edabd17b669b8543791e97c6d3d8b63bd83a9301d215e5f35dd6b961aae1e1a996751bbbfeb9e5c3ed5ba0e6e0508445fa56359e2c8d849a13f406766da3840d370b8f0868918d5c7cc71d038071648c105a58ad64aef7e248907a8a781d49f6c2f1b7cc5123cda6d371844bd8326062d1c20ca5550a234bbc2b5f0ee3e0e73efc833ca1b33e65a56dd5e34ec6f36058ed7de06e9e390b456da16579ec9e931335e3267c1e23a98591daf281c927c413c6d738e7e6c38ddeb5cd7fb953490ff4eec0585c50c55ad78a9c8f27d3723ad9646779632a7e69fd1f3cb7d703681375c69047bdf8cff05a4ebc423a17342ab6d174fb7a7e188816fbf2e81cf8c09e808c70f42cfe6d4ded1cc4e93081c3c953d67426beff6e91c3ce99232b8ccc055fac4a0fa4d59adcc64d83addcef1ce383917b49402d1de9e0b0740bbff310974ffbc93638e2ed538e2f570e05aabb25424afb0f5c9705748ae2980cfc73f8c6f13d32e644d6c1fdcf832463f24d0521dfe7a16b7b6be48f0deb02c210b44772730339c8872458b48d757fae287984abd7bc352e6eaa5e980813e6fed3910cbacf046fe7c40efdc61d23f0a73ac4398cf52ab0ef2f13bcc7531633d6ee6786ac6030d579315bc3cdc5b306de276951ae6537b2d2f09f7f16120de8e65816eb3706efcb642bc9da9c748a8a0e28e6ecf0455672d55f8341869448aaf9926c156579e1d8ba44b71ce0168619b6973b07623ec1553007a9420c7d652575179e1d25f4c436df697b693bd687e5d349fdfb0b0f974073134e3a64922c963dc90306e1e2f8d8c1c33959903111fd940bd356d0a03a576cb40a28cd0a1352809f5b0cd5f7d9e9f1bfd50a02fc4d45b52ed157f085d05ce17466380d26e3e4aafaa2a4da14032559d4490c77059b1767018157f7026ab6e401f804dd9ff7c0a2750140e9677f762707715774a42ea9f665508b08112cd08d814ef0e02379da9cd49d34aa408dfa50763ceb515014a6e435dcb7c6a03eedb52a535367fc0df5382fe24dc67844542f7045bae7befe95bd90fb147d074d04685ddbbb515c1199e2d24c3062db8a2557beb6d22fe54ee724008d0b04cb67c692f4102849fd48b28351c95977810a996f125a89870cd3e2453419d2cebf5e3da70437c1b895c2a6d0860000d80cbfe335bc5d22278ff74cd31ca86c0ac1aea5a4b4f1191551e311898fd290caffbfa5cdfec97b414a37b20be8a80ff066fe0f8286086591a321c8bd9233ba9235a102348721d2512d897b41f18b5ec5f1bbb5d552a52dc0041605345bffa192fe9cfcd820e232095ccd6854299cc3e95708bc880d39d80c5137c6f58264e42da66ad4e211e867d96b3190d54bb7b9a790209d33f3b74f73b713d3613d6722a5e8fef3cdbf14cb0610b3ae848b64cbf5092334388d36124c74c53957d3e03d7aa2f76a509af9b40143400bf5761d44d51c957063909072ce44487251d9f182be10740052bc0348e2356e62e7a57c5c229c300f1913c5fde9e71384482f31b75d97ddaf861d5666bd798e10cf22b937e7968a006b549bee81883f1fabbef17079d1f2586f2748a7acaa8ab39c1ae97355799f8da2ca1c6f9063034ffdf0c9ec3ecf51269c91b719a6c8e469a2e41ecf0c9278f1663f0d94733f4d37d87a590d8b7a65377f3ed671881d4b483453a69dc4da2b6462b5ac6b8fd1e7ab64f2264f9a6670e0222f7afd0a1fe1f0e555b7d7a54eef5e8363fb23fd4312da55836a94a94c018182dfb8de0b74fe7507054cf7ac2b2d8109d9688e4bf8ef518c92bf2d80f1e53ae8ed6530f83e0507eee58db5c7092721e5dd6674ed676f99f494065e5d93315ffe26e059e1806d732eac3fe3bab361fbbd56bd598c958aaa287118e8a6a2fd4e46d337bbf5e084c11c3cca39eb22949b949c4b7b1576fdde0b319403c6bba6c8f932d249591284ef29d4b7fe63b81418362e5587cd8afe944023c9c6d3ccdbab660a1c92ec0f7c582de8d120cee58727cac95f37bb40825aa2d618b386f516958013ef6b54ffd682ab13ae3a3e91c761717158dd6eaef5ac5f223403a4a94232249dee29f75ad7a7886471bc83aa4dd6a77651d178a4116d7928d91f8498992e42dd90a7bbfaf452290fc8265681bafbd042348343b6ed83052e2400c9bdccfce48e2c9af8b7507d445090797ebbee7dbdbec2707b6f997dffa7d7cfca0d538bd587aa5558e2048b2e2a83c2faf658a027a5dee8b484920ff59c031307109cb99bfef23114fb9d86ee5d6954b96db04da48935afc54c22c53be09009a58dd74a703ef52c8a9b5f141919337d1f20bf6440b494812cf3859c480c7decb53d903954ea35fde4dcf2bed6d38a4ff0ffc9ec06f7ed986c3ad41487890c8ef95ca9a5fee66e2903e2a4d2bc5dcabd9a79588b386b0fc30cc5d6b8d8251fdba06b6d95fd603c00353cb249528ca54016c41284e43a251fc9e1fd17c008aadd1be9a21581c84c282b85eee10e800aec781518d90feb4ae4f7d122d44569820398e049efd79924b826d4ef8569dccbd9d013c619ff3d0c860c39c59256f16a866776efe40cc6ac2f2c52886f3a268fec777a5c4d1c2ddbf605dba63b83746f2313075098f975a9f8ce956297397b41a32643bd3c8f1ca91634f9b28a71e8e8d71a4d9735c80d136d553230a14ddf8dc84bd5ed8706528b6622c92982a9b05af507253a593d201a6be5310db6b1dbb812c6a9e7e03ed4e64c19e52f9baa7118e7d6c3355e6f91083ef8d7e70efc2a2e0d1c9018401b7184647e47fc1753e11e72718d6289809d832f179a3362e7a6d1de52823bda1d929999c4f8be7b4e219776732e6bd12383406bee2cfffe0b8f3d23c4e1a7162c9c800f1c4e2bd3417c4820a286fca7660e7806a6806e5a0353dea78f3365841e5468a9c3d6fb6eee807bafc9cbf3c697e086ceffee132386ba58fcc438bc79dedf8a5384863a78061290b6fe61b858e3604966c6ccc9c041910f80e5f46d3ef84bf16e586596b9112b536a9904073814c2d6296ce8430cfc22de876dbde714eb21f1a2b2dc3f624938c36f6d8b451962cdd896213f6c3d32dd02f31410dea3564eb4a3787a63c3bfa02ca6c31ed28666324b1727f5dd76ee64e8b168d96151feb7900f171ad7c666609b96dc23d54f1230bb12d29c1250ebcfb9c21b0e6423f79702cccdd83d43e786ba091e59cd223c361b9f27eb65d8d41a7f0e90e7bbf37a71af91050862ebad51b5968a935fda83d21ac53bc78bc1d4d353b55ff1c5593f252aaba64c0e778a5904cdc51ca8d47af5b54ce784f85347c4148e451ec43af39fb82fd473bc70817304844d3e61e72dabc5a0e18458fc1abd3477dc43983144105b1f93b9c20f9dbcca12e5ae38f53a69a195c5a2d631c7f246e598d315355310b821f14133c5ca632bfb8c5b6f6619c95b27eccfba3abde7117c8d455693a1678624fbd985dfbb2c252d62599096e6ac90702516abb4773e3e1cd36c9416a604069ceb37d05143b20df4343762a38638d7a83a2ef04bf2e7f6f4243695fbc632a72be884844ee03ed530017307ecffcc49162ca05f1aec22dc2f301b98419d8a608e761e43bfdce95d51152b1b96251c0e9f4a9c0da1a26cf6382679b2f215939a542791f2884e72e020ad5cf8eb59dac0f2773b489c6155fcc1de3dde1cbe870fff19c7988eb9a60453638ffe220c5942a462e2c9a5f8d29b804d59200e9687076edad42450bb8630da944d339dfcef1938837b045ee540f60b1113c54a62307d20a2009eaaf160ef3507952fd6963cbc1cdbedc45db6edac5e561c573464259deb380584f8a0def262389188ed7506824f84588cb15a8a28660014fa4ca5818a4f8eac74d8afc7bb5f6db48457d098fd935253ac4891142e5de21c8f79bd84b3f7e9058545ec14c740e0951d2932456578708f1e7237bd9fe346759b42e9aa996293824fc724468aa77627b9e3fda8470d570394916ff645bb16a9f4e325a7e754bbb5bf356eda0c5536951202a8b8354f22f9461ed76604cb538b5801f3926ca818695d9d82901afdb54ce48cc37dc6cf3ed2cd44a007ccb67d10e48dd3a150b781c6ce78de847906b8e6174a5054b3a6ca6e060c88160cbaca923d4c4c66f042f916c7bb122751c2e35c189ddbf5e93342994c19a1f842c172ae9af832933907a1155e6764d66ad0e9c8c303c54a9e68e9ada6b09c60ec85f301db03a12eefaf157a310f8e982c871cabe49f2568062b5f0eb70b5a4ce42088b6070bf170917a63673d3e4f578dcd3faf0136e7a4d240f78ebf5f6ad3c9a34802169152f9f408e14ad1ef876094ad6196da5551a6fd190507efe4d18919d724856e80a36e6d3c36a844601f7e409734b10a6ceb6a83b16edb18300dfe7c41a900777656c27d851454612010498810767fd726d6e23f3d49532118543751832f8aa7530f647e194c8776673ae8759a7761f16818afee761e6d1624647ca1939944df98f5ce169e5a844a45f0afe65b0387cbbf4f36f7d264d5dbca13029e11f6e8e33f109ea278dae3d1833b246f5e89f568eaab774b08b90c4f5be0beae0ab0169ef90cfed1dd6396712ba42db148a51212807881134944590750627ef00f8c0cfec1760d66a58b99781c5dc9f9f772a41fb5f3e23a51858a893869629045c8d16257cd6431a8a35262c2ca52f2790b89f632251bf375b896a1e8fb12d6af4422d426d2c7577fac6e8dfdf45390a6fbd0d47d45f248032eb10fc2691d73e382caad251c2c0a4901a8333c06beb4933b1756f82ac3a399d676822c597ff89868aa40442dd9c799bf204b3ad4771f0dd3ede08cc9da42a2b37d75ee3e43e5159125ff17b2438e97ea4d891210e7f1aa3c23ced7d3c5f90496bb3a896e1c35dd86dc2c38424a2735199013c377aeef518d017a89bb7f2a52b67e9f755b93638dab7a74389cd8363cad0aa60a6476b4788aff95bc7e13237ba63f2d97ddaad0cfe00f38f97f467da775a0550a5b72f49c2d9b2f334b08f58afea1a3fec78b2686dcd7d11fbcf93ed51dc76c60c0fd1f4c77e6c49b8bdf5a8c55c193a450a55050197d1f05a7c6f556610187f5f181c0a3fff8a2d09c73ebfac6cf4ab0ecb510ff84ab2983a7a7e7186448327899f0165297360770b3032a546a23916d63a459f2dc8780f232de8a297d9f28e41f592749325af89c47e15828cd9830d019af053bc643d310b5e903f64c5af2a026f0ee5f613fcf4aadcccd65ac25106be738e404c68d2148cb9bf9f9112d04965e7fbcede71b12128968bed4f602c76c80f37a1db8f8e250e4e5c08ae5c4592973959fdf224112c77a0145fd98a1d8358fe5407af8be7bb6211b2731f573ba13dc7d61cf048f75b87108bcf876d76bba39229f03980dc3e2d5acb7a1a47c6e8d0f623e63aab6ce22ba0d244c207e68fc96461f73c873018166659e1ae87e12400af4b95ac4f09c7ad2d80b8fcaddf89e3cdb8170b2c750501d42c866503be0111df2040efec262fc83a5ac7a7865178366eb0abe3f1e48f67283ebae1bd60cd99f7badd406be368326e69a0290dab0c2fa9512093c90db5874b4640764b914e2b6c0211bc6cf3631e119995529233a2e925038c189d4b0a4e09e776a457181bac384d0856c144827d93e7efe9bab029824e6d9be2cb12e5c192636f0a1e4e0738570b0499f170ea700627e921fb5c734ed26e8652a10a5241f9e8f25af2f8884f4b8356c3b6ffc5059f4dedfb212e51722521ff97e6f7e409b866b898b87b2704999eee5a7f791541395685e8cc6b4bf2feec4ca55277ab622af74cf46b5453984ac6bf75ef49048ad1b08d38ef95e186d8ae05ef1288cc3a10d8705a97ea3eba6bcf98ef5c72e6b40b439a47539b91f772eb16e745e69e370df3eaf6dead921a8df73b86510c4a4cacf8a9a112601fb24f1411a7813540fc2af614ae778416a145f1c95c0c9cc6381ee48bc81c94d6a737dce6094efbf6eb52fca1d5aa89aaeaad00d2b700337493efee97a7b5d9c037b49b9289a969e9c7d7c56143551c290de12799db54623442521a9d36f53b0a1296c5539183f9f24feb80021f6a3d0cd783636ad663fd36f89c4f274747f50d8e706b769940d5a27d282213c26e3ef0e138f701a93aac6458c8c07ef9ea85c5e118d2d64d3913ad07094dc857fcbf4dd829ca65af1e4e9942f4d802e1731a94062b1693343ac0589b6fcc45dc4000054083d3bc64032cd8c7e2bcc2714942a8dc86e09cc604bab4a01c9b6e917d7994f4f796f79c15fbe3dc785b75eb28f66d4bce2a1db0cc599d6d3b7c7dfd88d0143d3bb15b1733afe4a5400e33989feecab2e913dcfabaeedc82d623e80d4a135413867444dd6f1d0521e8c992df924a1ffe83c41e396c08f1cd5fef4e074147aac21895aa388f6e3209c3de0c61b885440f8a63ad6370913ef1282e7b81ff47617eb0e5442e8f6167054749c5021dbf0405b31af301cdb9be43eaf9adccaaf5c9f01424544f1d5e2d9c8d921fa4a24fc415f32a6a5f7bb16e90429fc1c37bb3580d8dd56b671c573deed540beed78b057540611c605e6320c9d2f96df3c3f3663a16833ca02227d6aeeffd7cf8aa9b13fbaebb98d6f6fdde826960acd00d9a259387627110af9c040a8126acbf18f00c63f65c717ebeb5fde53bef40f6fbefc94e98c5872b82126012ab9b7fec45bfeb39f7ce41af22273ad8cd4c16ebb77e4148f0447a0014de5e5d1c0e08fbd8fe69da1fa2944ce51a45155698c8cff22a6c6b53b791615e25ed8119ca4d4058445fc6aef92853fa9ef13ffefe4a331f2972f5111b88a0c7bdd0238e13221e2e5be8d642e810efdcd2a66ff3f2aa26fabc894dd6c5b99fb6728e86dc98b70893224bbb6cd512056583fd14d157225f427fef924b5a6fe8629e6eaeab47088193fdbf73ca90131ec5535806a92011f79d303924c5935efe1f7ce6e4aa2e47aa4b2945815dfe10d7cc50d3296ba551cc232497a29497761903caffd10bb3ca76f8852da9e545cf0ef1d6d9c53f4bb788659d0db1c7a3d5a95173a1aafc7c09c9e90be13ce2fc8b680c51589818d7af9f9716ccbe066568ec4daabaabbb6db80aeeed04a3d5a4eb3809a32383c358996c0395083c08a3aebc68b3c25b31adcda589ac6284204dec2866c71d13b80d9713082eccfafe6c8d8016662e843a844e7aedf6786a021319e1481eded94662f3f63b11eca96801346f959e46813bbacd480dc9e0a27e8e0967f10ea8497c3899ebe40af38f0e13bd3f486bc1ff3d1308c6e065e959f548a32d7c75cc507d81a2b493c20338a965faaa8c5ca61af0c73e751a4d6b002a40256c14648a613c8d04058c8cbdfd1b094d9621f578bfdb12b760c52ff82cadc9bf2c7a2e637371c6ebae09a52679444f63fc654642992ac4935257f6af99642796760e3a3ffb83f322afb795a7788ecc05f6c56d20a130ba3d90cee6c2912e1f3080985e8e88603124ffac59cee44457fa7dfcc50636c7f35905139941e29090f8035fbb394031b80c8c8ef448c826f46044090216a0bdef65585496d913acdfcf366270712c39fc54a21fc9ebca79002cf8b31ab1ea4a335491cdf9a40d283b22fb9e41e494d8bead59a0616dda651d366f772db1c736c8996120f3ce6e4488d35627d7ea3bb3f2eb170bb8291285f780af6c959ab5f7b53e3bbc0a203467a06f31d32caba6e00102d64dcc904d5ebcdc296e1896dfd37cf57b82d5afd3d3c780644ba71182876e76d14bfca2e502b5970a0185e6330a8b2aaad667d41bcf5f1c851bc2a508b2c0dc1aece9d7926d7e44d6802fa2021fe5ed00cd05d729ed11c0e07553bff6814933ee806236078ac2d7ab6e42468420aa12fdfa179cfea3e304cd095947a3d008db9c779e0dfaced9e5a2c398c8d64c2017e5065c256a971f61d1d01bd35fc2ae940e5cc30d8d3dd1ee98831d65336c2a6a56c7c33583cdfe0506e138b2c98add8b26abac1b0aa46eb3ed72d84261ec6c2565399b875b6c42cf15ab5afd9b809c8244f8f62e166f585ee0b44a5de09319626f5446449b19f170688ca1084b2a3e721fdad2235833bb1fed5c2587bae8aff5d4859f62b95b423f092a6f3ec27424402d20ac0e2cd6e7c9870a32078b4f99bd9c587b2d517896c9b3a90f7a8b2936fdcaab065d4de6660ba121c04ab78ce779de8d386d3f4690bf8a747e0ad8ea7b8d3013dbab8ee006c82321382d45cfb4efe5c12820a88e48b6a927a30df8c70f4c4f4603eb7c09e80e131a2534b1e955d8286729ac3ff4d7026214f94a37f47d693ec62fbe1f1a69ec93298fe6d4c57c0db05f1c3f9503bc6a4d6dd1bcdeb69d89a829a81be8aff4129c1f737c4c31e659ef2e456e4f508cba887b1455e0775b4ffaff836802097ce45b97716fa056e8b938e72c6f7c355a0845b4dd9f5f6f06bee487bf5cd2cdf0c475102151f62ebd4cc5217a7d6d384109017a491ebc9c1e0fc43bbf92c844db6057536e5d223e1e5d8582205ed8a3d630384a09767ecb5fd385460216bfcd9116fb3e6114a6bbe95bb4c360bb053e941a88c9dccfea5fc6ea3eaa879a26580a00fd5997154e392af8b46cdf8bb763aac12dcebe49dc2aaea18cae51db0f1c5d996d30a69cf459642d824b0684ad4a1fc53144f52cdb23709ecf6fd6538072bf86552a6fd2d82a282374380e7baf2d7fc597482fe9747ffadb1f1e9649246bcd57be811b80065b438a1b231da27f4550a6cadb1d59eadc3ff01db36b7343885d84f84a156a7d270c3766b89b4418adb4a78e8fb20237227f12f5487a17db517ef6df2e14f96029ba75a4ba67feaecedf0227ff6b0112c325939af107ff09d089de5171025daab9fb0391375b04b2d5190c9b82bd858dad09b71461d8042d47017dec1a084c2be0ba127043c4e60fc9282ecef9535132853e2a8a289b8d0cf6c71e103b73cdf1a1073f12b11692d21805bb0e0264f518eeee3d16c477e59489fd8b0feb1fdb8ab052987ec60e02268ae6d698543fc8bba393e6806dd1713a3ace1b3919cecef79e32d9fcb68795ecaf35e63d243a009b8b05d190ae4ba79d1bde454e9ba05aa89c9453522bf45cbe8eacc56d6f334c01934f671e489da6e43ff69dc14034f7bd2790ec7d5ca5957bc71013f4115257ec9880b922a0fc6836724a69dcea0da3138d90f70df46d3b9892db78117dcf66330d58929534b9789f5df06866971992f2f745c4707c22f83bc05ee9a6371c6f823266e3ffc2771c0e86bcf616788fd3ba2f7c1a0e422f3714aa3cfbe0c72906362ed7e85f61f0e40a005c56793ac6cf7c2b48913e61e9e45a5f7a323956e4c936f7c943ae5c79e73156debdd9cd7825aa922c968dec0f4890eb3d5e5666ad75f548d71dd5f43f8c2796a51e915b824eb095a42d355ea941456cf06b8825faeed17bb2b3229cd12c3d543edda6a326291d59cd412a9cedc2ea90635d1d7b978deca07245c3d32d8877546898bdedb283649c87c65d27421a61d58eeca48d96218a3538f0e2f5542f10644c098d87ec695cee6e5679237353c118795616594482ef6ea8e158aabc90d50130a95da9a3bf0129e894791483e324b9ab6482db3afb2d7f6d8b9b7be23ec4c0bd87c41e05d81e3c060e8fa2adde70a5c6b85c47192ffbf09f1c02fab5176558ac10291b8caeab2be23e00a34a3c8d3ddcf00de00fa3ce795f00b97a3fe2fa570c08466bda9f9252d1b69f712d901d4f08644c50469b0ef74df16cb6d2ef23fa37dbc593c3f72a94880aeac4595ba38ac4e935c16169f11609cdabfe23bfeb78cad7c01ba4ecbdd6d6d14d49e22c89feca4cf1842f64c3a7c55d383702702cb997c0d0b6b4d03999d5e5db5c7b6181ccf7be2008997e276c9902db49835e824b90bfad2bb64f9590042cf99ba784eef38fe93fa60cb4393b2c45b2971dd8bd8e3fc6036c944f44b5f18ba7b47d744304e64083b2f21210c818096aa2207ae478f0f1a64e7c64064a92502f7932f7a13fdd75285d430ce52e72409056b9ac2443bcd424d1c6012b76eb18fa194c3976f7469d0fd1db5a6e8782cadf91d5d7132e74504f5a2630fdc6757c62c96009265a6133dbd51760c072c0416139d21e4c599dca3476815331eece765f3b8151bc9c9d0b4faa8044860e073af9a65453cdadb233151908cef3d7cd876e170da6103d9be1298ce42d604d3963093837fc97438ca62a4f265b9bf394424f6030557af1654601864c0ec9f296445d419ffdbb2c29674829846671c697ca5f90eb1f21b81a3468c7fe43e0d6cc7b021dbd94bdb8f7518dd402e0facf62899630036343663b1b3cc27e84dc10549695e94ca0f373794cc9bdd994bcfeff873d89d4dd619c7d72570d9a0cd9cc74672caa13f7da5588e28a6167612ae05e3e4167c8af1acfb3a4f43061be88a65bc739074b90b1a12aaf314c6e187de0ff650a88a1e0738134e03b292046ab9f79b52c61279b5528c2416947ac0766986b9c82986f12de16667a07956e721517ab64b6a967e4f1f6d8eb76ff61c59ed0a1e198d82075c4e0f6567329e33fc534c3453d4acd7728363381cffb15b1a14dbfdd943683706786d6173b9ab1aa6adb49dba89163ed1d5a0af1320543dfd2a602d9ce0733190e2a37743493239c19258735f5f2b060fc1574a0a3e3eb14f6fa41b995cb2d5460a1998081bdb11f201c456e45c535d7cb344c40ff00378d68dd40286da88dadecaf96c29bfc3575399eac2b6e18cd847dc22eb6491e8951ae6c2a86c712fc3b1f0b34eaef7fa0818fa9d54e322e852044e325b62586995025e65afab44302633ef794887c3065966a315a3fa360d1c9c3fba4bca57b49961f5577580f0ec4358edc42cde755e6bb5846ea8888bb3760bae846bd7862b4166d45586bc1c16edebf5cdb9e628f0dd378728bae0cd2a677cd25bcd9a64e6571343c39b88d937b9081f14cede979c231df5a8114007d1d19df991e47a0b2f5a2cd3252d5744298d33e9ddac3070b7aeb3f4b6e488eaa0e94f4a7c91e59104c73f09a79068d59d71d0ebae52e97589ebb544fd0d18189feefff2f43d87659842b9fc97ab82a095b9eb21d3d9f46bcf0a4c068f45776a6d3c4d06d70999e6d28825bae532bec5ea7891cfdaeed28c399c8ca85ee9ed34baa8d108860718e9cc2f12aea97b8b49c74a2d051755b584edf885f8c45532bbcd3127d36588f7e9553a33029ddb508d2c68ac3b217b3a4a037e1af0fccdbd0ffefc84f45e79a0b6707ab8a19f9944ac6c0e5c61531f36e70697391da6f484f43da58d335fcf4ffdd3c3fc8c316e6412af35bef44c043a3f79983f6247d416508ad92824d70326bd69eef83ff993acbd7197ce7770b5d025506a865fc77764477a4b820a3da1f9d4d0a824324168f0bb0105729b645b9ca27302710df112b312b9ed876c89df3e00dc1aafe1789bf6a1030e0a0095a0cb153faf7bedf6ba7a32828281c4b95096691258396faff1baeaeef6664db2a5960b04321bc0eaae33bf297a9bc04e9dd49a2ea27c8a1c93f7678fff91b12e06b6f188a974dc4630542d91217f5ccc5095fa9d7b86539aec572f85f9298bbfec058e824c381e64eb0005b62052eb3eb6aa37048a74df2820f7b9c22e4c21e237c70920f428dd06f031bb2549b7d9afc4fcfab748da4266050c3e109843d054ae30a181761a89d0394e107fb4aa88b11ba9f8428b3a5f1bbae4a48a6378ff9a43428b3cd17054c9c87f16fa325323da0f7601e4cf3e34b1c293e11ecb598a7a6c0c7ad8236acede494248df0923bb43c7ea050f629620b7baf0d6123b3c038083dd27527abd6d5f4feee50d02e24608669e14ea4ca7515e95ce6d7cc8655c7a3d07c29a6758d38019432775c67a5aca4a903dcf1d7b9cfd6d89f6c9f7c210fc2d2ea6157045fcdaee30ac9c92a58e305b9beccd76911d8ee84d7337d3fddadd5b55d31c4405b1e70466fe1c31ca98616056bd267182482e22bcf25a51f166b8e517a47ccae43f89f85fe3234ab3048b73f302d2cf9a83a031c8b49b0bb32e6db6c2e3f5fa19f783d603b5f201ed5a607af03efb01dc06cb37fa6db444ade19d494014bb88b1e2ffb3fef45fe7e83f94b3aa897ff509793800709f353f8349318008c7f85c970906d770d5533241c16053278072b9b0b0953142a403e4b99dd384863f48e143c40b6fa57f4d52b5970230a3a299f04b12340e2fc7d3b85e9c73c0a6313b1ad8ce61ddd877deccb99d3b7ebb2fef13fa3f8cc4ac6db326278bc3de7e53d691b267c08d4f9b2b6cb51bcf9d417024856532b74fc5278210c279bce241a51212c722c45e3fdc6ebd45d408f60b181971cd5df3e75d3e73b955bb174bf2c65c2f43196e9a4d0d75df24ec34e255d8658258392203cc2b8efb6392d546846f14a349c4a17ea3c422a18ae223ae8080d3631b5a49e5d9442fce8e77b8f9611d0e76ce20a655db9ce9ef22d0a9a24712e2127d5614c19cba851cf738c7d0836413fbddb1a81e48e05596f30b2222b9fe4302e3d15168be0714ec2f6dcee68ed26199d89539c659f9834acb26e9d486595f0ccf63708c1cce34d3ff59db78ab3d27676d2accee1fd9a50c14e7dfe57e30cf35d9880a8fdf238fbf6a7cc36a05770a945daa38492de380d52290eb4e405de23f2f6f57e26a51a70cbe549bdb28c6a3a352c73b7d5dd4ee945e4974357c171a0b843bb6f9c80b4eea98f63fcaac46ff775181f5a13f885d80d8eae9565646afc9985629ca74492b132b22cec9dd3d1d2c9464694627774fb12fce3985b60f0a02ca6ed6daa2e93cd323592bc65ac274bed4e5ff092645d813da835b339d6c4b614a1c87e56b884f5e0b777ed8d59b6a8b52cc05c56fc023a52c76c811cd99c45df5812b077b04877600d2b12b6b15de971c782941eb3bed01bd94fcba5b147ca9e3d75380216ddd6214cef4f03f999fab6a5a0182f1480845f61bf86ffb71ae742b88b605f2499d35bf1a3a645b50f23b0f25839d7d71da35fb6dc3e40fcd47807669ada0456fe5622687bae0ebad10264c0d8d244fbadbc62fbbe41ce0ac6b0058aeb71f4cdd29a72f776e056ac1a53994fd5e1bc1621be403eaea57774af04e8c53f6168a79b23d9a8897e94b67539303b91b82578eb991d9c6e50d07d3e16d8c32b170e6cc1a6798e437613af42dc798c24b6d45426d5a2006eafbdbd0fc591f61d08da952d13fa30f9551fac23cdb78515bae6d67ff0d000c92403834352dd33163f60f4f9be682c6bd157c21fe11c8ba1c0ca04fcdcf2cbe7b88a48d311188fc1c9a529610f3c49cfdf2c275e184e180400b15934975beddc64a8aebcc0177d1cfa046271b3c1b9e6a9d9617cd8ef7a6e0466af97934258897152975935f5e5cf9caf6fe7cee5c2959cc3048970667fff958a18776a2958799ea384d4b52be883762d81446f32399e6de58e58f4947eaea52e3baed01860c77abcb7e68fb4eebcab97d0161a6401370909ff1f3150161f7c50a6ea1e1164364963fb797781a901a3fe86b09dd7d051bbdaccf7935f63ac95b36e6e70a81f894d2545834b6e48f301adcfb4d3c6f6b4d36d11ac9f7834eecfee50828c0e22dc10cb8fdd411cbba3a5ed73991df43f631a74e5b017dea70509364cc624b0607a72394e9bdcc45b90aca8e770c4fd794378907712fd0e25ec93e8089a8dcfbfc87e4fceb694ef53f981812964bc67aa792288973dad1ed468f41d3f39db8a09da8d744410737e5557ce1f940acca3060b8b8a1f27af2a505bf5e1f24fdb66d6ad11c9093fa412f3874094100e411556de678f4f6fbbc9d23d7d47d4e1511279ad15736c458f82719e4c2ab547ab8ea119ae83dde3f31954b3ee665744680479af8acc5616c337fd4511edeec49c80a3c170c3a3399ee0f3fbaf1cba086ac402e9bcdb8b7346ffeb4e52a84e3020f2dffe4c05eed522e9646f71adb02deeaeaf393143235e3ecce93efa62de1a74445154d22dc18278e2aba07830b8aed4bbee5beb24d8060a571ec6040bfee1282eeaee1fd94c3c9aabe85dd10977c63faae2c7debdcebc60bd3fb4e659bbf6c071fe8f25701305c8ab07cd1c98edf87e9c63435619c2de29f24a474a4e43cb6521eac18002dbbf8d69ae61fba126eb2d3d680ce713723b850669ab09d7b254386b7b0ae3544972be901ef676d6e4bd288d82a37a3aa2cffaec95585829aa83c1fe275535c607876a2a7882ff71b92e88cdc57e01fd722a0ef5854972630fb1bfc5ae46bc08695cb1790e45c8bef5f8a069d273e4359393abe8b8b31a05c0a30b3e73a9c6b0bec160c01d4bda6ed0562fd0fd14a36f42aa1dfb346c278b1971f393e775109ca87b13a064c80c78d90ace892ae029c24e85ba46b485b6d173e60d7ca4c3adbe6aeed7144c8bbb56bf3bc8d89f5e74dac9f222b70bf33bf7b18be72ce08cabd0934907d257ae45ba9c1762e8ab67e282961647391822314a705d9218f78b83c45de52e1a608592f8382d407718ccfe708cac63fa2122be7dfcea41395037fb71bca5f635524da28d95f6420f511e9136c259dc89114eb883ac9e7c52db1dd9554e3d8663f5a0a4dae22c76698ce5cfdc927733e19512bb0b8e3be8bafbd588c931b5216e85ff1c4e02f34200dcdbdc967f7881ca7ca539a9a76186437b9b4f0e3cd555e85897c869c7df2a66a762590f4824f4b7225f3bb83923920dbe67e1eaad3deb7123bfc8c4a48e178cb9eb385dc3fec12df703d01550038da89b885ebac2bddc202eca3a1fea3f2952ee9053a254abddd9f5c869ede9c82bb14d15b2c29b8b8b099c40ab4a5e5aa2eb6c9c3e3e487ceca3b07c970cb3d55131aebd8482185c67e3af3aec27645a4a29d7615bda6e26310e5de23ee67d746bdcc6097e05a3cd50031976e53680a0ccf800927704ee1722ec98058a6262dbbd7d1d5787fbce8a5bbc2850f7cac0424adceebac934ced26f1441e4d5304f172caaeb11c62fdee6b8f903db87a751bc240816a583736565d43a6b28c792dd8f89c81ae6c4ad92d4f32931376de8438d0d674f6658011b72bd1653fc39887d5339dfdbc13643a8cee7a3c04b93f47eacb93f8270f56e9b8557a025845ecff5cd955e6e3ff380bd8d87a879ad5887eab45527215181e7a4ed794e88187bbc14cf6d4828ee4b5a2bc1369010e83cebcdd64d12fee4589096d5fff0e50a43ab6db6a42c3f2b93efcc3965c15105db8312a6033bf13de6fbbf6cf0334addafaefb669c612d538d32dcacfd624c065798c2a08cc681d32bef502bf09e47a0bebe349f9bf0ff56243ce061e1e37e57f58f4f13e3fa44a1171a5f1f818b4863de89107774ba4dbf9733dd4576849dbf2c8594c76efa2ea50a276befa2b5ada1adefe6c1219399805b300a944d58999ea12a2503bbc492849a551d977528d967177a25f4f93fc37501ca54b213907d6f98b6f30ab10347b5833af61d6a3d7dbb35eaa7c83557867ea0dda251eb79f92925f8806f6419ab75fa89502b101c4bf760e35e85cf2eebba5acb5bfe12b384e8de71dc3e23d3558424f51d557aa11f0f631e62d590a3c97997855d8016886aca694763b4a8979266cbe39a14a9bd7708f9576ce4db23076a2f14c458c3af8a5fe66bcd09a8ec3d649441687c2c3c1d544a72128e029a6bedd4b94be824b813898f6f117553c4f87ca63f3a62907d2713c8712820ee2c0cf2f34e90057c65921dd0b506b90f31452e4607b979e0f4f3c3fa83225a6d31d2cbea4d33c75d4f75e19c88d5374c6e54a11dd239672f0ca943d00fc96cc25e41275c9c09cb8e7cd19fb5dd545bf066154cd83c22fad986ba3c7c8303d7724f5c185f4e5c8f0d49719195a674944249d2f1ac75ac18a15dfcd94cf9aebf486b4e8dea2ddb362be401c6a7b2eb666db9d0bfdab3699f6a4a59cfe56341f61062388ba9030a748ceab3aca1bc663a1b3f1a2fea69cb015989b7782865cf43344459a13de3c7b4cc1e9067928b54e02ce10a7ac88b4eebd05edd3446bf937b000116028432d221141a5369caadfefaa0357d2177a6da5fa4cddc88888a94d76a15a22fc54f231aa0ed07bbbdf70404e54b37a5409778188670c843af86607f2077d87d7d96fe521fddaa441e2bfaabaa466e8fd459f765c0c24c1671b3ef5eab7b55cc32d5021f2bb023d00e2f0f649cd6b05aeed274a1ca2163cc2daf925f44d6fd710b72f658d717bacaaec54986ff8623389a8e43ca2015a102077e7cbfc45f4dfd06a1236d72585475946ca9e30511a37b35534ff7d08c81afce913bbc21144ec5c61557ba9fcb2d208529ff5a6010e32d56727896ffb0ebff4d5a440597f044fcd7a8e7b5040efc1170aba877e8179939b5165a9f0b8dbe44a072f65aa6fc9a383130ca30b3c06dfcb0806577b34d4c33802a31da9bb279c63842c46efdc2c2fa2c8e60191e6c113cb206b93f258d30ee171bccffbec549b4b2ef4d43831719e904cd77b885ce0caa485ce434d464bf94d3ec362764701b530f1d4f3e9eca78f794b42f80f2b6fba4784e02188fb89c13c5794936249bc76e384369c3a9d96b0cb5f2c7d356a55f186cbb8dfb7fd0442edbac6103937085d5abde172b622d40fba1608047fa716b19a44832c7e8674cfa54205562a2e516c316bd2f1bc47d870bd04900366a018f4a07bbf739702b6f42f7f25c79806c48924cc341901a7f7100fea1e1ed6ccf57f1798d48050e00f2057a58dbf99062fa2968b706b0ce285502fe73720b1b9a60a63e5fbe60aaeb06e6e8abb8a786456880d5363d2b927982060c1307f4a338c484debc34bbb8883c4c9b0fe4234996677b3ea2ceeb70a22eb2a3730d4eb3fa4b08216ae786ddab3474264b763ff826e51d7e92e380aa866e8d764f2634fea73630ff1a866bd687a28b5e6ac74bf570b056af72157891df879f73805a028dc6fe8ebea684c5db30d5784ac4c949067b000839a62fb6150bf30dcf86eb29ac8ed646eb2e11692a2cf6a14a9e4ce3faf343ddb9ad79c9003c6981fd5f090bea6e886d21c6299b68512874eab2a21d13a903452f1137e6b3ebdc2198e57c920bc219db99e1431b7610f7b1fcaa2bcfa57f30c6848966a18037ac86b288c344277683da654e68d968fe7b3f8e0b6a4a798a15bc40c48c01555e23676b15602171a1369829b16d1eaaacbd84e1c910375a53a26b10d9e76af8f045de413051c8834f2561939f1375ad0a1121b960ba637e05a349cadfcff8c0c53fd1dfc67b0f3ee48e99b147517262cc5115b0247e673d6bd5697a24114221a3ec3d3ccd7d7a724945d1b7fe9039fd8b8dd906bbaece6834c04d94695ad3d42a62c77a333a3694e2af3130ca49dab0ce85b3fffcd97fc64c1903c79e5c27de0e04bc16c0c95f13cfe1cdef980a2429acd4371ea528a5859cc4bbe0dac95d50849e42c096e786ea30e523d2ad091f3bc1ce833466ad95256e00a00cee2ba0ebe96e87781f8c4fe714cb7d3aab8666038c5d51614ad9274e914fbca0947614278764f254b3b44370002ada9cd54abbdc6eccaaf4aa7dbfc8ac51b85b74eb961fb1a88fe9c8478a10705c1abd1086d55d737f118f2f7bb5ec0d86dbf2471cdaf7dc3958cd396593a6e3da6281bd668059dfb976e50a8bbbf0ed9b6ab36a58b441e70fe9fe5f557f0e04a9956fcac1cbf6c33e451268c5f7bbd49b29e3a373b8bab3e46c9e8be3b2a3e5309cb0dff48096c530d67fbbc359f68e1ae9346581cc3b929a6e3a53b6040d04373051714d47b8f52c7d87a82946f6394e0a82909798109240a863da401fe570f1de486e3ee45e9856bd3df3966e2ea98239fec42012c3eca2793dcc0ce25e7b2c52a7c74d1a647274b08720e5d2128cf875ee27ee680e424acac805db4ea8a0b6e025e06637623a1572d1295b10c0337bf6f4800cd0cc93414b8ccc1e476c6c615760484a89ddef9f57ad69c1fb4c45d7581778153b49e43c163ed9e7164ddf7a6f87003e29952f8632cb7c62ce289eda8e5a326a42ff5323f9b8d256b45ee1b52da1654d6fdaedb8e054bb6e48f85c85b122ed83b897f438014db355b1fdd964bd5b3dde9eca7c09cca93ed1e6a135a51218366d7a3aa0e07c59adb1fea0df33286b85144a2f40db3c4daa512559a071e518d1940ae83ac94694d889c12ef6d6bec40f176ec1d82b5409d3775e83eeb7d84b48d83b4ee3ff6ff63bd75f8539b54ec9c69cbaa9cbcd20f89e39fed6542c4c16e00fd0f2d63fa4b84180f719f29a1cd6889a154485cb407890e42e2063a38b0606e78f0b32e6c53ddf34f45be1001bd35500872150d127fe142f57ace025f210e25a2d952d42ee28b0c47b1f1b5f34ac21a0d23241f38d75c88a01c115786851aaae723aa083a080fe1a1e669cedde9c2ed11971e81cbee87e5f8638ddc5d73191ae979f1b44497ccb91087b00a6e723a21da95e737b6aafe1b959cfe0b1125ef609c5b0eab60a8a58d1c2fe3d4f2a162eee2f698adb074c8f1fa4b7f881ef8c0ed4d9cfa55a7159b34a8e9bdab829724a20b28d2714db9c499c4a6bb101df075c4bce57aa05cfc819a52b675e4bf58217687ab5645c66a270d7d18f473b931c70eaa30258c9a42c8e3330b63dd8f4b4a529197fc8a0a7d05681ac0cf347c87e08e564db732ad0d49b6fb507ceb54bd09f95f15eb77b869f19a0deffec350df90959260bd2bc44931184b62a08c195efb5f67e1289dbbaea703b7aead7c5bcc0a5e376188908325f4964011b1a8a2c234370acf7aa4444bcbbeaf35c6cc660335f11a9b74184c934fa891a37deb8de11de17c43925aaf46b2c4bf75033313df8af2d12d8007c7cb6077384f7d817897d95cc1b78b60b52842238990d3fc00dd8cf8dc8c31e95fc8c88fb1c359c1a9253ee55139bd1efd7edce6e85b0aee5d21c15231ffcd51dbc9e018019f498ce4ae5d4646ead4d2ad595b84cee3633c9cf0435f6722e01f27d2fe923bf83a6fd1bb9e2b7e8047d332cf1dd6d2e5d6889b34e8d13a640ef8d529ca481ad6cdceeab864e8a0c50e62c17824fe2754b7c037fbe78e738841f574d99fe637b61ea8f374487d9115b8a885f13dbf93d6a81005f824fbbf281bc70f3207eaed0861b0c8a5e0db4be4c2b7945c431f06486153fb01f9e3832dbcb8c44736db6fb249ddca621aabfe364fa3e2de646406363ba6f68f96602158db593f6ea682ad8b49a039a7210e9b251c17852086bfa063980d3e41f94ecbbd2bc44dc4b88cb9e1975e92d4d92f025fd6fb7bcfaa35b7471453b459664e834213b789a76a4deef40cf0712ee6946ea6b607131d739a988c8687f23d7d597f25b1033c3a1a96ce78cc2e845d4276b54e7054b03da8ad8891d5d24ef7d761d573340336e45acfac17c0e1c4ec6aa682c26f548dead8ad74e7987bbdb63b8ff59282115ad1d9c876ede18bb4cd15c3bfff68c168e105f267d68b508d4b59042eb82d46f16f52f1a00a57ea2ad27ab5ed16365eb9625e5c74d9cfdd3bad1d4b2d71bbe5b9f392e80ff4592b20352cc355af05a64ab30cb8aa1eff17017592ca3a1d3b45cfd2909d0b22f5271d117fb19d81d9c2759d12546f070f673d634ee8a66f2c1a29488d6abb828d2a02e683bd78db54655aed356defab023ba8d775b7c6efb57075d449e166f33c0c7b198f6ae51edb5794f4fc38bc556d94f8584b57a9792bc27dba4c39bbccfcb2c6715169a641fee6eb08bba39804c52ac411506397bce4e51c7f3592a153a03a6f9c80196b4c3d796801ad1357ea841a7d4ff41657bc63d60871217cc6479f522fa80f686add12f0fdbc25c33cee1f45c904d8062251cb84581d2078cf57b09a888af70b09a653acc69cca83c02ac3cce9479b9133a6a534bbd7b7625fa933087e9a8f6382ecdc4f5d04e0fca26de8558cd23e6ad9bafa19cc357afa76546d9e7c157674710a939672dea5b64eb9b8f82a8314def9e2b633ca94a8f472e68f03bc9a08d085ecafe02c17c48426c63e0c13db3fb81545619c4192e851e5de96bc47ae6c5ea69561483820c721e97e69354682c6e4e55f22cbe09208e5cb33f119713828524299b85caf6fac0d997fde63acc13035d77f1a7fc93bd43d32cbfb1ea1054ac696a3e6db2dfc7e01689c9ca67806bae8d048084f2af9f86921054cee604522c93df1cc0670c4038afd5c6e02366411dae4343f17d37bfa3ddc30ca84f60c8bd571d3da67da4a769b10792cb4ac10aa74c20ceebfc3333718cb119ccd22d2c0fc0e2f3c2727115530b463d088b3e59bd30771b5156ad9fc4f2661df5865f3ca42810058463f233e45e7bb56350ce113acd6578eae60e9acc2624d343fa93074ef7dd29e5cf78ddbc3f0d871d89ca654e5648400150620f130a0f417fc652dd3d59b48a9af00e0df2ed0450c10b4fa46009b16a9b33002005edded0e490ef162d49c97a6c0e0b2122194419b4c88479ab034fa103b9957f12282be9f431e69ab0f81081ba92a1b6df1b73d414cce1b75494276d37b52aa22e8e3661d6839e479a94a79eee08639460ec262c0e715a9b5df5b9f14efcac9ab36c5d4412b9098a9f6aeddca474fac8fe13c093d9b9747c24351e447676148e353756d983c341c7c75d0b7ca8ba61a2250cc40d07440eee934a48146a38bfef218218ab8bb19ec810683a37747fbac1926924cea5215be5470a40db6bdea857c781af74fea42e9122a8ace1871d88f42024c21859c7977847d97edbeb26ebe4d645876fd24fbd65644b77951f5bd75959295ec26dd8000655af0de1276d74b2505353afb2c15dcdd29cc7a0f977d11c433828ab4ee35e3ec683d6f4353771487767c6c990cd806c9768cbac96e419fd545510e149badcf4d401d84689217c3a07832aa8820a5c6827276b80ea3f259d3c7583f9fdfcd15b5433dd5169b45473eca1183f40f696e0782320463343003850c6217663cee2c9c201f6ca60f1c55ce1cacfc6dbd98b8bd6ae6c56ff666603d9a9c4541b9d82ea996242bc99ad0e276d43d0ea67ad23fdfe73f348110e5bab687fbacb48a3e581f2b2b447169ecaa3906bd25f8083b047154381f559ff45effe749e2a180668edfa9d3bf078b1aad286667a2c43055eacb6b49673952221d761783b2caa8b6d4f8956bf7f2b1e4acaa17175a9bf96139b56209827a1dd6cdd6fb039a23cfbdd6c8eb501d6d0e8cb0cf91f21636cd4e1f07dbeaf9215c4dc65689fa4ef6c58c61acd692b4aa3d6984b509a011430fd62ae9732beb04fbd666d11e1133d49f82e83f3c84135187e336fc14017dfff3bdcf98f8d1892287eb8d048bb3e558452a00303fefe63ce31c9a4a769ffea19f10e7c16ffb9852395c22ddcc73b1bc106be4991b9bad9881e5c7e245602d95d8bf2c678d9cf243595d8cd3e0041b375919f1fbfedf3c43cf669846e8c61682d665c15a494b05cb91a139582dfa5a3118e72bb6bd163a86303b2bec840dba2af8bba7469c9a528010151a5060fad74812d0b71a88cca21cec546602ef9c8e146aef4945a510efae8a0825a1cf5cb2212e605f0e01c54489002c6d4532fafb020833f109bac5069381c5d2ceae383d09e39d113958ff45bddd272de33514dc4103cd5099234954e85f969fa6abc57e68e9f560c649259a0e3d1fd56f27f803c01766fa91897eaac84a468cfcc38db106974001b62505927e911557ea9f57b3f9598fe7044ec570dabffda0776658c41b91dc50bc40fa4950ca12ecd0b4f727b3e74af4dd5be11dcf5a2a626e30e7da32bee5c9b2707136ef610f8d4567140a8305e360dc29cbc0315cd160d3da94c9dca1c28f9876099e25f3737a422300fd2c73824f1bf4c27a85cb45ac3128a79c328c9de82278291ea3ce98b7de40cb15cc00d04b2a6ec102b44ebc59b9e5ac112847e2beddea4925d34a078aa2b98b5bcdae46beb56fa61fcc10a0035c51911bae2b8420dc92967703c8fbffeb83f14abae22278bf5d54311052dd44f9762661a5c8a3bff87602abdaa2cc6463463053d37a842f67523da9547b8f409b56c8bda0852671a6ec60ae1e8f67cd7f55eb5c7acad4fa4245c58daecea11ac3e562bf77ebf5b2501d993ecb140c911b2dbd3d6dc996d647b642f5710ffb7cc53d9528a97ce71e4ca793707829c7cb18d82742d0ff8621429d559080bbbd143789e46f8d47c1f8563c142b1120f9da15f837ac08f95722de9fc220e8afff116982f13f599c6aa7a95f4babd9685ce1315fe8306747677f40d0fef9d0521daaf5946bf7ef901b3397a58effc5db9848e16d6076a2627ec42e761e86bc9c75975cf40c29863c49ccbd16508a20876c39d2a67b9b21eb11e3c7a4e7c5139bed96af39431b14d51f44c7981f85803cd823e12aa5af161c79afcc9e5fc6054613e74e5a484998189790b97e3b7c10568b3b1d0ff63869764660784b482fb6c318d1b50958e129ac904ad7a2f7cd246efff6dcbeb1ae70ee4112e93da9bbbb722a1b57d85d45db5afb3210ac0c2c22035291e2dbaffd7e2684451811aeb6ee55d39389a1f08d6c5ea9a80161b322b441ef89904eff063b80d95e631ada8387bb3c8b841ab002ce1b6334872a4a89536f8e232e78121dcb0ee325a4b4cf168d3917566c9421a472fdc2ffda2725121dafd5e53bb9108ccfb36246d9a5af7b9359e4d06e43f9a572559257467613bff71a381bd8bd7d0f2df1bed05e61634dca8d29d4ef9c55ec7d71b22f628e16690b6b465333424357a73912b8b628fb3f5b49cec3c59ed2939e4a189ce7d8c23b87a5b7e114414e874bca0857e14ca797185688038611836acb7a045910ddc235b890e45c6fbe5cbaaa5667e84512baae7bc1c27ee117ce724a626c4487aa00aa6fe14d1f05a04d7be411540b2bf98d71f3fadc1f119de762e3a67cdfe8600bc2b95f0ac91c46404a81de6245bf6c22e382fdf85952741a24e27638d0d3f668f9e37ba7bd45d6151e1daff47eb11abf99af9ed09f733b6359e9397c6189a2a69ed30d1ca32c6a72dee161b1815975f43cc05c33ed7052ebd1949915001e862455ac38eae57f9430e7d1a0c27f7022de0b4b46df3e207acb15ec0b36c03981cb95c6ac7233e4ed5589e290fd8c5958a25614d56013eeeb1ddda44940d7684a162bf6c1c800f99bfb8ae6595821fa6bc7043dd686f2a9fccf9a355ad41efc3119b520240c026d044aa148d1df9822111f0ec1b9c0e5629cd3edb71f099479307e28240c6ab5221e9bfc95d1e0b4860fb50146e36ef75345492e9b8cf5db9c22bd4b0d3f6dd9641213bbd8082016466e658039d6c5bbe02bfbe626ed06b9e754323a9e4d31f02a208372c247cac804587cc03f5766c5a1c10a55cdee2499c2c017e324ce62c16df336aba69b3eb79339080bf2dc352e81dd01ed4d4f4f082d9dcf8ca18ab3d88be2606245bdf23129dd543eede95314e71a91d4a75d3438fe24614b3ddd8e03bec33587659e796b29845c4a302a4217f6412566dcb63a80b5b4828d10b567d69b4c6d7f3f2023fd7e41dab6ca6d76ded7ca25b181e89bd9daa10e9039fdee28b02dc8a4b73cc36383160f1bdbda969019ce1ef824e37863ef6d4ae84260de941d5c71c0682cee7e779439a7ddf0915c18f450317d6644deed36fafed9a1c8f9523b8c967e535e118c04369b4a379df17d3522876dd17a325b87d48cd5088efbcf98055960ff47467a58bbcd84b1e606cd7eb2d8d7ff61e7e6e78c228c93a87ff9abc9332b3f1626ac652f2718726ed3fd8867373f2077ad9bd455a47af08033fe8bd5c0fc606b24f52c1b2648e70e962a799b6b3aab43d69ff76cca3a756204cbcd36900dde4693da30de5beeba33104c19ba6fb6e68dd785cbfe9fc98d71a50f3cf0a89bbfe12b6c1c5eeea85f200506bc00f0089f3ccb2c0771031e5e3d0f53dbe601a5c62988ac3c161659a5f0c0831dc5d2d2ffd0ef3075ed290f9022fb17ac6e0872e0c2ab898e18a2f1eeae8c64d39facb2b60c27ec18d9198b87ba6a11ddca1df0b11d776563487395e3f43cb81d1747c7c9357806c3bd2f1d77ff1e675ff914dc4381352ede54ff05b58ca40b2b6f2202cc6c8c96770e9ac40ea31e0a0f6eb7bc19dd1107cbbfd06155721d852115f14959b531d24719de1abf523619e1f541ad3dd89a80938b0333366505117fb21a6d04a4da4e816b53caca497455cad6e4f70ad946615101cacd6af43d9ea25636e420201cd254e7a354a09eaca64f654580c1b0ebfd415eeafe4790a96a904c9664124b32b256cc5029362ef5aff2b4fbfd2b2d47221c1e3e0adbb5b45f4323bc420c6a7ac67afe2b0c63e52a74ecbc1307e3117a890a0632f9d1b9c97e1d27dda46aa2f6e76de9decb6c6d6b0ae8468feaf6e187e64c0a4839af11db5d490b66850f74cc016f78644847ee9962af41e25a0a72d67cc1a6978a623bdeb31eca74856d20c7b109bd8bc93daedd19fc9d75e82772b548d948473ea18c4bf5dbd35a8f2a98d378540979a500d4215fdeaf264cdcfdd2b7b8f716c59744a000477b1f49841c847b94742437517595e76b2bc4815fc8c3f1867dd68dce33deda0630fd56e07c90d268fbd924c6692cf3a83794b2d6a97eb02df3ec335c2f097cc87ee46d32bc506bdfaeb6f6610e1c61ddbecfeff829e1a80bb6d3fdbe4dacab80fff43522f5447815b02ffdf54362d4c71721bda0232b835b7d97abf79c52ed5c5206fd1a878c7145088c878b773ce8605f2c6b14c48163517b5b95bf9a0e7d350c2439aff16fa564c76a6a4d71ad74205f03420ff86a023134d319f438f45b92fb0ddbf946d858b23536d6abfbfce2f6a7af7bd5cf5455defbca5c91a19d6c957fb270e7c8d477da4eb57db43135926c9eae0edd820dc7aad89e55d63ea79f5bc2bc17911815d68d2f7f0cd968a64b99906f48d5824a0061e0bf2d6cc53330a7880340135a72ffb1987fed9d3763c7e592d737177d2340d10233efaa1f66f0e4f0183e8ad265988a2d7561eb0fc440c9ccbfe7c8409544609b15835522e8a5deebbcad1ed18b4e7e9c74e5fa40e21af51273693d3fffc25a11110067e46876ac7167a44b272859597464e91afa1d95a02834af5bc3a5700f45ea48cb6c1564b775c2b3c80fa0892f0281d6c214988fe6c5b736160c309bf185f5fa1c2a26dade642dbcc126ffeb3f36da65b101bb5f678da5833d2d2c5146549bacc251544913893a8f460172f77b38d3460f32b1b4a06f196a37f9891a1e3fdb7b05d55bd4f70530621c05cc4e9407a315d0bae88bad962529653d1b419dc5c5ecfa137d0c5d4289105b5d0211ad37d7a1cbbd933560d94d55424a1aa9d44000185cd7febda67a742e804bafb91ba8cd8449592b69e8e009938617a339ad814529a463a9cc6ee853ebe19f9191d0d13a08f97bea6977a04821d45334d5a7fce12d10a94920f7633ec4abd70aaf4d22a55c8f004e3b947ae833bd5634e2731337335aad31a13ca47f06cf39a0864db1e91f86e19ce511743f1e2a3b69639e4a3cb5494db544bd4a1422914f7d0b7b81b733b42a5bcad920954d6a5f80a0dbfff95c735daf2943b831cca140ef5fdd6611e4ba8bd508fc8aa7137a27885ead830d0fc202fbc44640bbf1e2ede915d99fd05d811399f2b7302641472577a8674f294aa3d4fa1dbab0cd794c2181ffe827207538aafc8f6e5a3690feb9a427d66e92d5f0fb131b4f4c37d895cc708f70cdffbba0549334b175efdeabda4b0c041ba2bff10a2ede8a45fa6f8f1b95c9491ec1e88c28ba23ded6e039b7e10c86e8f146ac0cbc37313881cdc5551272c3f0508f8cda1f1045fd3e87b6083e6dccf92c8173051ea1020be79b82c88aadb46e6ae3195ba4f9e8cec7da1ddd5f53b7e8b6e80fe0f5e0f186cf0346efa7919766030d3b67c41b9d3e64c166957a8de7060725ddc247eeeb7a2ed74836418a59e538b178167de110acf799000b92497f193775267209c7d7a3f19dcfac5aad9bd7874730556cfb09bc22b2ed32d19d6a9ade29053ffaec5248ab116daf7a2d03478c9ada528adc68a348d2ad1957ff37aadc288f837de47ddf08a114a890e719c1df4aa2023f3b3d1efcb1f8817ecd8034606c55061eb7d814c6f0fccd2bd7be899c4a4aa84592ea84f77b48b023c7e1082c8132df11712fbbc453520ff4a1267b5d6264ab55a25cb1ff51342d939083ea4a0969b7afa59680b257b98eb2096cb5ecaed72a75ed4ba695c89a889b3033d8253a127f47e777752d39a15eba7bb07f351db70a2f3b922a27689c67b3950a0f355edcf13951aeb8a86c66642d84ccfc77f26776a3168fca822e6667440da989525219857bffb2da6a17458d6c5690952f4718118f4d9cf1af0acc9ef2cdfbfd0a071d99e893d9c9df941bbffd48943a9785854ef275920df062b3c8276d969b2805ec07f2b19c5920f3fc2e555499df38ee12aef7635dee998d4d61406a9b0c19e3396b3284fab6017593073b9879d27f1430d214b3a0d3b459224078a3347bbed0b7b36d38e15b37115389f9d5a8cfd75e0fae27456cdab486ea781bdd9b9db10db3eb557dab5312f27b0e1bf344f67c8aac437eba00bd7e493a8d8f3fb30af971033f310bade230f6ec9c427bb6fdc22523ac4cfba316e5cafb3880d62d59c0e449a22618cfe20131c6ef941be14e4415556412b3859d25a6b963da1248cba7e47eb387e33c675768db223b26e4e21b10e117d5d0b304896db90047471089db03d1ade72d20796497f669c3126b0907226964e4061f782c25e6e7862e3fab3f66d38a30bb6dff696bea7d87f88d780e2b294f0f4bafe427c58af16761a098a448fcaa30d6c12307c974a9cb174248205fdd66d15c687eb8bbdcde0ae20a782575739f4cca4cc69945f19ffc505abbd6c4f8cb02379e36d36756d9d895e9b888cf30ef196acfd25bacdb874dab17f881b72b99039f3914879ee192d6c153f4b8cd6af1a88aa788f27b22e3fad249126d673a66309c0ca0e327d250fa93b5a832f754172d84ec07f565493fa291ff08415923dfaaf836d7ab41b06bc99a0892916b2e3f84b49bb587d05bd7715cc7995ae295a926c29a55fa1cd30dc94c9ca03e5ba0ead6fcd4488a58af916ec97f0be2b0b81278cf60aa1f5bcb3a3d806ed09d8d6c722c412793fe4b7603b211b8527642403f17348d0870989d6948f2136301d96b9157ebec553e2c22644d94a031476bd5d7a7cda3c569e0cc3bbdc3303e16afdce01069b8b64e31969a71528b358cd3cb6bdaba5c589a33351bdbf68070738f01206eabd02420c529ce9772fcb360762804aadd68c9eb05b6c8bdf4701f758e0f6126a6bdc1d1067b25a9bf4a292f23f8a00d5d46c890c0ac257f09465f91d76c753cd4e49e1f48119d8f25d09a00fef59ab22525d699403fc2e9e4d6b7e3283f33bab13562fb90cf1f2d39aade36fe81233a65db385a5576fa211bd457c2514a6e203fe623ef19374b71af0aeb726fe75cc110eaa7274528acdaa5a038ceb91ebe10833f9752d3798929e1178faa72f3c4305d91e5ab47b3cf815ebab415d9976b7f067ad08adf5c6b324f5d37bb78dbebec47f5b5f308887d5086b2528fbeaa1f6d54aa398514eb6be419cc9f19cb73b1070d52e6a22a842a5142b6c6688b842dc180bd885e7df787f1874b959806b682feb2405639c01c253e1aa76c64f5f8915e9e85c8aed96f536c82d928a3979751677e0b3521d4589d917ac3bcc5c54002f748d6c6431a7f765c183bf6b0443041ab7b12e552e29bc13f31df7c1b5a08a4701ab1792be67fbc17fb3c602da3304013f83f37b75a40ba5af42bb08c24ce48ec36822dd019bf1e920384e484a0a80bf80a8df01421028e4fdc91872b2a91469bc16c8e90410e75faa8ede88dfb1d54dc0696cb4210393982e497d43785cf479d64a7dead2126b6448880a40cc7f2fe94093f688b74e381ce6cdcc028e3d4ac0fcce8eba22ed9f1b5e7ec05a4ed1bbe687068dbac8c8052dab91dcccb055958512ae495f615166f212a9db91fa727783d60b3d0a10fbba53572d252c5416e013fb3d5f76433ba97495b56b1a505943a7f3f56706ea3e34cfb24521f4500c064d5211f87ca1b349cc2a868cf076b75b06939017b7f673b1b05c08dc7cf4121ef3730f4b89414f6f8c7e46f9720116d57c2bcd24dc66a0e40cd1858d993146bbb932217019c99b847b105a406e082efecdefc5bc0a0319ed9c5653e17e020abfdb73ca53b33d78c0616a4b9f56f1ddbcd0870b50ec835dc0a47c4f047f61fa2358646f3d3e832bf3e3b8052a6a1975e42eb53a341f663f41d33b43a5aa45c92c28d0f7a3aeec0893519fbfd5da8772ff0bf24cac6ad9aa77d17998bdfc33c5eb23fe7d27b5acc9690229bc930ff9910ed5926f32a9330768fe03d0f1ffeb2f6e52824c40a85555398f98b615d3aaca82b05e16028156e9ab82d4a5643f9c819e5899d5d0f868c11826b17c1dc6eb4a83c6d9f337bb30fd317f2d08e64611d61fb743225cd815afbeb5c347307cd88429e63f0dd2d11c5fdbd55c54c711662a078544253f2c4d7d4f4cdf562720555ef136b26a94f23b4816fba81e23726dc932e5c331fe2dfd48e99bf173c65111986c062d9494739f589385e3e491220e5de85f2ed1b4cdf52a9428f765b8c0229a2fc886584335015105e559e99f6bd6b5ac48e5fa4a2780966ab50954fc88fab59235367e10b1f93b6757c2414eed470b35ee3f9574281e2ca8cc91be93c2f1d44cf444472738d6573ae92b9b934fd88596f6c31a75c4f7d74b6f550db01b0ccd04106a2a56fd3740a94e3297483685ebdaca3fe6ff7b3ac387f65a4ec16705eedee76ed2b75541b58d7c6a4ce46eb2fdf782c60aa9315f698b75b29b77d7d49fc665ed3eb7c64ac6be5c33d3ec4534d2de443d6244dd31505884b14f99a2b591b19abc806138aeb7157566efa6fc89db2bda1e650858078ede439407541e82ebaa7e383ed917c916f85279265f50c1a950ac1981f6bb9c2a58291aa630866bb80c98c7f84fb11cacbc5106a05b43b50e47e37fbedf1484917732bf671cf83c985f0ca7b58228cb7aa2361990bb3b7e1b7d15c9f8109199843d7a9e95392b372d53174fbe21439ed1796b0286e4dd9049e06041a1529eeed964ec5ede3ed04a647271cc5defc26f50002020390181e015a067284747f3fbc0a0ed990d3f861a9a6c56064ace4bfcaff90147979c772b6f2200efa295a952a18c9cc05ec51ff932561e4a8c6147e5ef5268cc0a00c84151153821eba95b7927ad676a1de857ace265aee2347fb8d147400086e42bce1a3ae682295f0289e04c23ab45fa52ea49c522ca1ae7acf1e47fac355014ab125fb13577a883c1d86c1b582bf5d736e7d8ffdd911e2856357c2fc8fae59ca363e492768d5241d86c5c08697c25d35a8820fdfed51a019d66047c1cf523806f871d00a11691a9e49f92dd2f63a45a297f7d8178daa8a3716bd0909e1e8c908b9b1640b8e8f1fe4cbefe473f88a9fdb2a8b798853dc87066b2b318bd4b916ce5952f1de9d276dc9cf43fa280cfe1a9ebf53d577f43443b771b15c3fd34691fbe557f232f6bf408a1ef3350219990702a6d6a689d9b0ac7843fb1b78f2b88c28b310fa8a4bae5b81c85c8fa6684bdccb0a88f5043a2c767cb52971cd805cc0b6cfdd2f6f069381f410fbb765c877b44cabf2a36a64dc980be55159d715b0ca3a44acc9eaff13fbf056dd8f1aca58ced2f480e45b34e7cdaa81653eb8ee8ea974e9d0985adfc918388d5eafdd4e760aaa71b3bfd0eef3144f87f7817761a542d8d4ae7c8bf374a83b13d5e187c53d6faca56a8f9efefd31a25a55f72b71addca59dceea28a18f812a0101a304c7d1a862bccc854e4b87295779b868f6cce8a592fcd26e7bc03dfb0c39cea874fa5081b4aa3923442485ca8e30e911ccc3ec2b0188052377846973f1ce9757206b276f29a6b2c233fa07214c0b872311f71be3c98e0c3d1ac597cb289a7a4a3aba7806b913140de960ab01f9ea4f4203d618a59353b97f1bc79ee892016dd171d654802b5083845d135183fafb2a06f42b132dbc2ac4c5e8daf333fb54c26d5523343d0af47bfa1ef4868055f8707d534b129cd03cba7cf56cb51985457855853a2b3d2075fa1806443d19636ae7a78a9f44f695ef56255961f08b284f2df4b5b061a88a475cd83c891f3095e128f9a5afeca2a13701d6bdd4e07f3e25e76882889a4659e0af0a95e0f7ec4a4bc0dbcfdbbaed81542bb89e9c176d1d74e0050f3d0808bf3b59d22eb30139b5e1112a9b5f428776b9e18ad6207fd2c5896dd07b8e397dd2c821286f0af646cb6248b61d63bf04b924ec1c7e6833b09eb7a705277aa75ddedc045646fce8054f81e1c5da24935c552782d51a74020c427ff792bf08500242104c7ec6e9c4aee1c1208f7d86b26b929fba2e1d3f336f59dcc59927dcd4eae126d9120a8a4d3c2836a4bf6386c272eae78b35af02e3668a3d95f226a7c7372866cd5a6afb19eeffc792f8f76fc59493bc680bd393e10373fe82a6edf8292f957a18f20555e2c8c206604edccc0a5a98194466d1f67659b674ba756606a9bcdf4edb489772d20f9ca8edc9e4608b3080930c10f881e50319e2b251595bc9eca576f5229bf450d1ec31e7e6e1baf9e587ec7d66a829cd9daddd29e15d9d0972ad0e8617c7aece5bc07490153b24972f6ae87dd9b5cfcd4a1106acff9dcafe23b434a311d0febfc050dae33c59f94b25b8a0177114b0e72db80e263a689e8f7ad0780dd180c5d6599ba8c932e1b499c3c1db8e45d74422f0497a68559205c01562ea2e6b20fcf04005b66545be37bc690e28f3047f3bf3400e94c0c6abccf03692a247c52820f5d30f3df5b5fc17b75c65be106f9855b84e697ac93800f2124464ee431d4def02371dc27a3674001b44666c9bdcaa4e584f684c60096a451147567a5a80d02df4d3443d8272426b06acb109f6b57621c8fd5dcc36cdf43f9e6f91213b542c7126d137fa9b8515414486a13907f78346c3158f09d8c94f3da78daa4249cf0629eca035e0c87f462772a4917b22b400c67ed7eee212791081e997f2bb167d7960d0741014f7e6c386c740b8cb2a91ef368089eb74a60ac5b1882957f116bea7f10dc44126da31cc956d758e56ba48a6050bf44ac4bce0d6b311ffd27d4b45a52538cc0e3fde4e78ee57c3c80049279c9bc7e5163838123d0e087d6e727e9aacfa1709bdfab11c167c348fdfc50190884261ae99b418e9fe42f727d0447899a58a2ebfd6e7e6f252c958a0272f3577d1dcd8a26a69456586c504295dcf81f1fb4b22767fd9741736f989a47a651de749cd339c8aa4d8b8c2782acfe9046da6c5269a79d87f8047ebf41ba96f9ff67d40a8ff79903ca3c81adf940efa3951903998ae5675f8a1345d982ba423ee44ebdd87dc99e563fdb12f661bfa912522da755e2660e0480c03a0e7a6ed91aa7e77d31de16212f722416f786c4e96fb4a83966629302ff562cb2b976dc17c05397959dc34bd1a98497b763e52e7bc4281e01bf7f120a724bf1ae77013bfe57b62e07dd0ad97ea285715443a68490d481518a15049cf7106626f0160b56c16320daec2079962d2769ff93a26b518f15120a7e23e4aa06168d6c19d2de1af3877c9780c9b1591132958c7f4c1dfa0d4f412dc1454f93ac6b1cff64c38c7557ff396e7e0b8931f7d448252e476204f572b505a8ac8bf0951b4e86d58b0cc8ede420139f5481a0918d6e01fc8fea4350c14b49baa38bbbc09058175dc221c6e54fe12c00c03efcfe39634e9ec75d68bca761f6838bb21c552cf5bd3b25d016dc175686227131ef7a29a5c1f4083d5298e736362e809afdcfd534f402df6b4e7509cd11700a5278ab75b2c6432808b0ee8fc327d47b62c7c708648e99ffcba9d71e9a06fdbf0ca950fb6bf0f6acae2dbfeff117ff1a4ac65d87922eebba61f335e30fb09a6a37faa21a3d5a945a913581f5c94861749fa18709055e3b9faabb035b03a1d63bcaf5cf9c842712e7176eedc0ba17f565a1c5ed39e4e5154c5a301a4debdf3ea382dc6532edddbbacef62cc221d8afddfe7dc2637552369264bd751aaf486af92bb005cca55a0ca1631743917aac2ee5a37b12bd812e816131cb42985875728c1663b917c27b60791f708a14473c6774fe9ce2397a9b970192fdff02dc8f92929ea489536585e550feb4fa7ecf759a78eeba08884523d13dffbb280fcd9d63e9fc2e9c5f3d7bdd0b179eb25c12a05b2b3ead214d908ffb7786d29eb81f4212acbcc6204cbfc54098237d741321f110c977e10bdbc262d7829218244bfca2e2d1a56bc6d573ee37f0ece726f35b7676974b6969f448a1984c05c5e3fadb75a81cedc744f2a6e5a2ec44a253d6ef341c3c40a423a15d3d6859ec5ae60c70dc4d570343d26cccd494e777dda8523713e662a7a5e312cdcbe20d5a799f8f15cb50e49daeae922bd9d12e60f67779e1cd8bb410dd98995ec498c92de060a3d747419cd2fd98efa32257468516372800f6181db554f7a7d8748ed1d4aae35242ef47fc60d7561a9ddad2faf07f01e95fb76c62940ef5e7d0f9bd0a4e19c3fb87d29ba4821d319173e72644a4d61563cbf0ba0a3fa64e5fba049184fa3e0147bbf00b27ddb676a6f418e9f7d56d535b1cfa0993866201a4e3f26cd3049680b11f4378256ffdf77cbb8ae54b657ac3443c4eb7f58d4fd22057bd213b69c588a4d29258c09239871481119b282a0f0d617631f280f4cec769cf9579a9e8de471e4f1435556f788266b158bb4b0df30271bd563efd1449aca03199b18070f94bea8d132b8d74de4292d7edae3a12b411f5c678f223934f8fe93fdcfa31c153096ac4d16ebbb84fab19b3d70c05bdd5d2fd1dd6ba65b18e44ef753bf8a75d1a5aed0314c28b54e9a02d087f26d77d9c7f8c8f8116012960e27741c344810d83f89a3bc8010116508b0a7d9f7d5c0cc376839282da687eaf4309093aea1a7c2f06e95e9499c3244ad96416af48f2dc59b9268dd3acfa0b25889db251eb6abeaf85a5b2190bc071727ce524abf3a9192c6d2c1a668a053d3a93679c4abcaac98ce08e82346d150c1667b6b5819a1bc9485e5daf774120b3ee8ca7395ec6d1247a617cc186e953b574161fd85e92080dba7ac547abf7cf4f535d8fc51be4df5e2db916562a3c0b4faf03314afde1cda2075274f38ea17e5c053637befde8f713e17c20167e38a9fba732845ebac1a60fdd9ccd6e6e8dc14a0109a219e3164a8bb8a24eb29e42a80cf9a6032d9521bf220fb3692396daefc6e90ed266c3a6ce371008b11443d40d18f1e9c960712416b74715863474fb0817fd41ed4ce946d0a546edaaf361fc38cf33880d35ee9f30dfd1e405795739ebff77df711a3b97469fb0757b9b3cdc007138951d73e29f9be45b660285e62edaaa12ed7a569dac00913a2a5630292823f15f246e8316e13ec50785eb24cb4a554599b11486478e9f7ab0760bebbcab022fc6ceb4e325597f18ce5038ec78513e555f37a197f7ba60f3c4294112c5c2b5d54231159489ac31860a78a7b967da4ffb71cd4ca64ca0b80db7f93ab2d3df64bf476d5d4dadb55c3904d40f5395d2e39dfa8792073626bd23e6fd32550307f9f56ba852929904ccf867ddc07aa3f709b638bcd0d44f2460d6b932423efb551aab7b8c0dee2c5a72cff4337320725eeeb8d687ed623ae679c903f6d8e82b757ec1d6c466f1162e9c6e0f16c98fa7ee8f5723470b5a76ada3e66790c7ceb12987c1c08273c1c72bf2b38bed5ebe32e3c68ee51f4d5f132ee23067a6d6b64ac8305306fc11494fb04bfdbec36b1b764b9b411776ab828b11207360c4ef0d50ba509705ea855fb74e5134d9c5f182738ceb57cf79d8e27973785339dcd50cb7fd23fab70a2576bc4eefbb95c57e3604dc629b099434397764519569c16dacada32d59d59d139fe82998a990ba7cfde1c8bfccc7fdd23f4b6a4694d1ab96aa8952659689e710fffc0d57f4ef3e12cf08ae6aab3c298e23cefde2411cb6922165c2b5f27eb253fc455fb2839b0e93a7c2425ce94e552c7449092d306c62ff73f243cbc930cb76bcee335e48f28e806b810d5445c7fe9344e46fff72ba145e8fc7343289938475c3a8d6bec8c5431afa84e95e45ea69106daa8334629d942cc79e8dc5fc1f31b8c504453bcb68567ed12d86ad4b131ca6210b8fa086248e8dcad1566d4a39ab30ffa9b35f08c9185ae6c6e9baff2363774e3f4fdc3b16118176a12d2d37b6d086a13107d00edf782a9183e8c24540cc386ce474dfd02212ad86b3c5828f0f00552bcc5b0ac33a8f0cb368af883e5cabb5be3c216b3190eab5668d6114cd56a274bafe7ef92416151e74da3d6d099797642ff271446278d426a6b95daf53a19d5aabb673574523e86d3d92e8d5423aeb0b36edef3e5af470ead0b3a696df05df8317aaf0df943d8c7cd0d2d5e2787b467681cd1c1702744353aeac3c21ffa2daf1047ce052f741a64c9eca817510ca0b3752d6ec1c651335b428b7b7a8b16602b75863e24644948003c0363b875489e27eb9ac49ae5cf411895ff4bfc785785093de23c7cb1f4489193383a7cfa99335002e1a8f814083082fa6e393e8d038c2a0f70017dd5456d21a3bd73cbb20c9b4e328220ec065142e2d71118323dac0d80fc2f18da41b7391f70ab7c7b1df38d1ccbc1d8cf07677fc0c74fe766d12b119f4cbc6978cf51ca2e41726e6ac1f2a45ee583cc0b1eef4ccc2713e6516b4e38c20a321d722099f01ccb77f8e1eb566892e2abdd8443fa961a6817cde97634b3f14d416a4ca2715514ded8d86275fae5e5ccc70f4e770585cfbca4882b3abdec4f629138a3fe74a584e83c0ea1d572746733d97a80041a4c709f95187e16ef2afa44125c5e778b2801bdac8a863daf508f044fa7e160c068fc6e9f2360fa8ce19ccc11ccaf6285f58180849068da8e13035c138729d0c0d020c59cb6ead149a2019ca08bf5fafc2110030b71ad7af158cff8c937490b82855628a701f0fa2f265726b7ffe97bce702b62e1050ee6193b0eaee30eadb47b620583321d017b8a98c453bc096296c0725a60277e9046be87305c5b72b98ad1fdf6a28ce1b3abb24bfe35334ca1587a297b58e687639c5127076de5704148e99737f8eb72bc65fd32cee53069007c0ba1cc39abd604d6244863035d137affb749fea92544488a80682d22e8d1256536398b7334f64abe56458b7f9cff88404098d09ec393cb7aa347857e00c31d7b4a39726cdb30fbe28c5cef3a2ff6ee65aef466aa439fa32715e473955e0fd813592c67ec635469683759218748b38a5a2ae3d84f204fe1299de2bd61a783ee1ca9f77ca35d6c1e1131594962a5fe52d2df4c5001f2fd9848160e5eaa7b9ee55624921001ae98b317cbd8721c65e7a722e2e9693bb8eb0fa28213c5f5033bd296ac6dee2569a8490d5e361c49cf8f1a4b4ae4c63aca0bda911e4772236498c717ad7ec721c75df69cda4df052de9b4c1ec7ee801c6bece58c737e417c9f7f2bf478b2dee9bd9a4bf29f7ff1c60c426ea7db14df501b2c47ee0be5513a808baa749aba6602c272fb12dad84352edc1a8f0552c4cf8b6616b194339ee5f49e630ebf8403c54043af51980275c4d6685a6bfe0679d8284e4b6242c518796dd09f4efaed12b5d10fa8ce37b29eec1e6b214e9c1afb43bc4b83f07d4021fedb98d42a1fbb43aa1c5672e64a330df3479da1b566e6c0fce9d8ef03842b9a27b0d59e8fbc502fcedaa4b7ae2acc96c3afeb7f399c8c9efb1975553d29a26777d31f4b26eca50e5b51a3f9aaf69c50854d5a52c5a88153305e538d34a145f39a0a4a5db7e1d23f8cdd6409333da2163f7cf027a338a7fff9fcaa06c7b605a9d775020c5d143cc41ede5a9129daf4078cc5400056b2a1a52fa20442b64fe27c870296c2856b85c3611c9d8c02863907609e66504421a0aeb8785c5596550560a5df1c9dc619bc06d71663c819cf473cdc47e08132db0565b5aef76a0c5ebece694767f07d0f917c63cfb26b59778d9133467f6e8d6a447b37265a3e5dacfa44f72fdac2452f11b60fff7b6b307098369f1fc6e98487bb858a54dfa34b66cdc316b50b4d36ae036b58d19014d9ee7e1e63556e36f629a4790c9217ff46167d50044d5780f849faf40e892f86ef52927fe2c87d8e26fee235965c31cae9d454734b1ad27ea83e2ad0c9b01a3558d9e656c4fe469d4dc61df8734e6413c8b328bab3ecff1d88265821e5b88f677655bfe027f2eb05f07296e7edcb89fc8d4316373dc36968812d7c2e1325c6bab26ad922e9697fd909fc1d8960a9cd3a6e4f8bfe2ce1be1e794247df7cab039b5100ec63f501ad220945e84ce54ba796e02706a929c8793071305d4472e7bbcf99e966adaf5ae45e989529fe8a061a6eb01654d467a91a467bc9ea0fab81a4ee4afca74f652060fc38712d541eb691345bd64ba0e61fe93b7b8d3a1517f020e3630f675dd2dc05e1add7056682dd8eeebe262334bc45d79db9760dd03e2feb6e399c0f2eb6c407e63538093199c4bbb418d987377276902e46d6c8e0331769a2dcfb1bdd77f3655796a11414bc5bf23653ed95587250f6652a6afd85112eb067d17a4bcadac6e875f5f8ee53c4e53034a59a9dc782c74eee6b1767270d1ad4f914bdb916c34cf2f584413997a3aa7903805d6db9525a2e26dff147cf1615be67742c732a0af59080fc3e0f6c8c34200fce647de716bdb9a0c97becab09047615336b1f3063f99131e4fcdac2aad9145381f8ea0f368b32a254473a8d6c13783a0105c0984f2dee1aaf04e56d6c9455e271b3ab7205d2e2a8cf63656dc2fb6b6342d84e952328d0bfa54cdaa5596cc9628caac154958663e0c2404ce36a4f55eea3f13c9e1afb9e99a029d8877df1718c50db82808d38eaadd1f084ae12741e9003d46fed7be5ec9492cd8d915e8c0cae9f7b37c11994c2256df6d92fb42932d0a5666003cfb8e591e31caa2a87a4f329db046c6ae68458935604d0ee27c1b189bc036c2220dfdce59ae41b189edf4df2521455cf2c0c40f5f15bd1144564b86c3d613dd25435e7d859bba1080ffb5f15047b8ccacf1b787e68b3c6f09111067b084e705d902b01272617c6094302242ea71640378808be169bcd3873842d5f8f5e7d3852a8dd1d830462ab1169e31eee0e4ea478216527d5e42d91b352cbe7d4a7dae005a915a0106317fb77cea4cb9cd64a1df1c558608cad91b36b530fc4488a1f6e0e6a6a2ae75334e277c7bcd991604a1b8d5bc2dc3ccc19f57aee4c513f50a68cc632ec6794978444b2274eeabb24a01737f0ba029cb94d69601dc25de29eb236cd6e116b98779637ab4a4b6e1b4f4379623649d597926298c47b12dfab7caa2d8772d584e8705f654aacc1c547ae0169821158eeccea6ff5f0e7e3c9eaa7f9759a4f3b026caedb2d5d11d0c6e8f1b1c6913242a70d6f7cd653b33ed7433d998271b9a6128153323e124009613d39ff818bc48427e0578c43fe6293362eae75a2bcf9bc35789628db7f48452897f1a792f2286bb101ac1bad2800886576072d8feef4bff314abed4b1b5d2330db044aad4ac4c5ecde52ae7daaf8eea8bb87dfb1be3e954f107d89a9413bbec70358169b9edafd1f19f75f85848f89ecf0850cd0a63eeb55973a78d6b42355539d9905ccfa7b18400ab99d7156dd7721dda7b55767642e69caae3c891663ebb74a606854ee807a803bb173dee860788d9d68426f836c406e3e6cf3236f6a208b4099ec0711977f93ea421812a21f59df21efdd42c2b423ca7bc7308aad77c27fc94672cd7c5d361d8a3e9f1e14563c47ac3449c6aea00c357ea1b3451ecedd55c28e49984b91d3f142d4fef870b89243567b5b47160e8bc33702b711f77e4aeb1d19b4ac204aae996c56d00f0b43ed4d8914abba8ed9bd2a0c83ec53d0c1fae75fbec3f6a046d73c17cc760fe09a0815877718a1ee72c2305589692126dd8be72286f043f3489d88865395cc0cfeb37be1a735c2cf81d26ede50112dd89edbef94f5ff2048839059af54ffc053c4752601ecc3bc119ac10668c64564ab64a47af7ad17f74f14d7f4bd838c229a6f0ffca2d3c19b5800cdb12732e90dc6b461561f5b15c4bd93e4638fb37a5e6b4c439f528ec3338a7eef9a45b2faf63666ff28f7f18573ccaee615367847af74a27d92f7918f0952482bcb5e8557976b59156dfc3ed66eb8d478427b992b9608754368479139e8a1ef7b8a02256b20bac0b158c0c4967443f370a95655a23c9cd3881d4ec2351a276962fc3730d816d9f8f33a69eaec273143d7337872a6254bba507dec741e6ede203d6bf425646f65f1f9832f7a8b12cd245baac2228f215382383755f925cd801ede29a7efaf265f25e2d8e16fcc04630cd72dccb5b62a96e2d0248fd66e2e0c3a11afb03b87f2ab23e44b6117c6d8c37eb55e5f1b55ac1a80e613f750c41f45600493e4a7d0fa90a7c10e237b759ec51d4928fb3538b8890e1c4ebc406df133e7c3620bbc687f3b4112bcb2225820bfc469f552fd43dbb2007800633813d0a0f23cd261b471ef3b8e2ba22c210f8110b5e8abcb70e49306c9c79741fbf7bc9be877b1191ce23d0602a850c173e56b99d2ad2c005adbe81caafa200444f361f9f5fc2dd5375c2e733ece0737b053f33ff08f3e40df13422d17d6be2bdbf233cfa29455304098ec3efc5a8f6903dbc16b2c3aba0ee6657098a4a55089fdf633f976d683346ceb7f88836f68c37fd1b2895db085a04573c45f88c877c612c5e397ae3760e4cbb59354b4a644c8bbf9418c8e446e61c90830a9aa3e44deb3cab08afe8b56b4f2c09c0b9f922a8dc0ff81698b0f386f03adb03e4697f2a001eb2cfb682a000b72d99af9ac24b63b483573977d3e4a1def46ad0e19e34f2f4b30ead52128c8c8232d95ce58c271904c27a59598c73772630684aa248c8a85049cb346835b31ba4438771d77085b5ee5e60664d65704a955f840c9fe3471eb0c8f2a42121989275f4b23e00e7f440b771fab09c48b6dafebf058c5b10163834584850093dc7d4605f466f2c1bfeb1031540e0d5ec5b0a85eb54f1b2dec7ebe77174a7b2d5005c55bd2fc93c5d0497adcde1efdfc303e6bad8f63ef4506f2e68b9261f2a163394374a5eee86ab6d96cfa89dfe951cb89011fb6b38547cb9e950f7bd4688d6558d777987979377d4f9b32e0fee9727ab32a9530dac913e3b93c0cdace24be50e087a11a0eb28d3e80d1118b2693f78b0fd8ae259dc8d3f8629e2d384c3398bfd1d208cc2362e5e1096a185b1787252ab1b1bab92e353887b2523888efe1893783273c49613e949c9fda18d3dfe42c9af89179e96de549bc2f44d079184490be116babdf065f2672be24e6509ccf7455cf06b10abe53cad623348fee3cb1cbec6777e274a2c04637f364522d39f3f74facd28ddc551d249fca356b8bea4424ec0898932ad2c137d9c9477d6da0fcb0fdb26dd6515a85ebddf1306da0995dfe17490e728942a7dbebf38e72df951b17e93f81ed7eb4f0eef1335fb7e0a71eff2e628ddc3c24194dc0328f18bc4585aaecfd6ca455556bd7e79bd2dba466eee048ddbf57a90ca4078617b36e2833f490612fc1dd18852159b52bed82b125f8570221bfd8e79339a038baf91b30c52442d52ec7ffb1efe6f3796fe861bc88ad06918e9fdfef09aa0d5c1e724d132a5744184bf836525aaf43e7024e8129433e82b17ce361fa4b3081c91ac3e401aad2f8b70a8488095218e132b07ed3ba8a2367a18b0485671ae8ce0e40a6f69dcb5e783593ccb43daeddd1339a2d1b2c2f967d9741ef28b14e516622676999dfddc87f8b68e7e92a7561898db5e221f54918ee6b9b834234f8cfda6d486d033494616cd5f92046d88b2a790aaf793eace6ec8562411606dbb52cfc70b9a8f9f1627e482978752a2a9e6231883fa12bc3d9970b495cf1e1f199127f7ff5248e7d33f9e7f6441a5dfcd6581117d85e2e983867a35b9bcf48b18491355ba47e2eb5d8ae147eddf4fb6d53fd872f83878c158be843a1d7624c46299fd7ac2767028265ffca2d891f3fc9b768e41ed2495359dc703ab2e5ce0764e0945b9d16279aa5285186fe38690bb1ef52b60db0a26d1f4594bf1a932cdaa702aade11b515ed1e9a8d9d19f6d4547eee9bbe41fee70ae867b19bcb8009715ac4831d4ca220a8c0f79faf5ff61851f737c36f7def3e55149c64db4a88e4a25773a27cf1f049ccd2a7fa1fa0ed1e7aca1471fe9e32257873321294969db3192787f0a0ba58269dd9b913a3937a86607799f001e11ee9f6f95261b8ebab5fcd8f2a39984d3861dfdb06e2daeb9e224e8f11ada45e3e30539d70bdfce2bf434e30c5ad9104ac3767c28364030a0a79b877f4ba64bdc570038e35d4cb95edb1dfe5925f26a314faf6aa511baf2e9e458318444f641ec275b949171a4bbdc56d620c86c9313c078f6d48f98c933400f5122019434ab908dff83f77466ffdd8300fe188463acac5f144aa07c06528322c2fc72b97b2c23da1fd0252d320cebc260192b197693c0eac6005f4c4c64f3140b53e5bec2e809a83f23e048070b4417090c4235f15fa43632c58cf29407150bb1a319107d7ab74a177f199d1dbbd0ea37c9c29a7d71eb64a0a04c084506f8fdebf01e2546989ebeb789b718ba16fa248e78d855b37f10aac11b6b4ef8788811912b47560fb4e89a35467ba3c269ccd5a1e841dcae055a7661d390b86b4ce6b947ea4985e3ac319e2517d79b068d018cb3627e72a6e413f6a2a0b21eb1ea725aa347d4ea33b3371126700507d864b29ff764a6c11c06a9a3e06d0e4c3d665ed68b810e52bdd216175506d307850b2264e91cd55fde5bffe7a4dfb88370f56d31345045011b652a29df6558f3eeff05687ae7a2ace965fa04eba4b69748bc1959fff73891a97120ef224b360ad97fca16848abd6473919edf9c2f10d2b1bc9c838f9face2b9bdf1a30904660674f677327d4bda0907cb58753887bf8112720edbd01c955cfc7d232e62cc7b856c1f77bec3ca6e5b680894d4414e08f604dfda8404ab0fbb23d6d24f7d5d11a2222b729542e23582a8f16cf4ae84312550ed2ae87d7548e28f9aaa01d43d8b9bd242749f02e62843e63b83af7db50ecb0889bfd8ec4e77ca7227d73614f8872d8fe9126c48a1c09ab02a4d6714b3b373746de0e0355182c48ef0de6a48f65936b7da6b2c80d774e0e26b9cdf195ce6733189f36c9d724af4a1170841a398f5d3129d2a16a8e2e826631e84044ec1a6af103ebf17659355c8fb89b52ca57ec58297b0b3a0a48db4798bc2e454d6f9e7e423fc020d4266a8d1e23db560f5538041621fe867d0555dbebab6cb4819e032ffb3c6a30631f48e39fbd08421acea93e0052cbf88b1b825a8dd8937d6916cf9976c9a20bd6d76c88e2d340e86dba2d06b132d00571331b8b0c49f46e249f962510370a3e928cb44958d1e82e61e270ca394a244460e52db8ddf9a64be28a9b65a99ac81832e43bd198112e0fa1a531a26b06c293fc226832d835595b6a64e29a27186ace0582e7f5dc405e4ead1194255e508b5edd717695b97efa8995ed21c6f33e486c8e685bdc814cbb975848728dec309ca711f6fe33a1050f9bd06d23b079761250032c8c998be3acb54b36e3fc36859d9f984c03f394b3a8ea9e506ea7ad7a4738cff453221e8771c2809971871ec309b8e735b36cf9c148b4141459f381e8b1561152b596062bb14ac757bd9bcbcd95bb6610fa691de32bde5c87d92570da39dc1bdcb56230669386442581f729da2f46f22251a1b0fac2d53259ae86f7949d4df3a6fa412f333b00ef7a362f666346135cb7a03046665a206af6bbcb1643e390d58490eeb840ca3812ddd120762f49fde7043a77d7d531e168b8b633d3c2950baf188a2626d24d1fcf71515e91639635ceea33cc3fb486bca66374e1b7811e5aa3b85dbff62f377798e717bab0a84530bc7ab40cbf1f532d32f67e074f69ce2088202db14ca8419eb5ab4da4136083fb5253f1247a72c3992dc6c20cacdb67376af4605f6d1fe87bc170c2f5bde76d3ea4caf085f95aa4304df07154e3bf3bf8061342e74330ccc62d05091af6eea6e7bce855f1a8844be05b1677bc8e9e2bcc4fb87ae8dcdcb036bc79c747c8da0aa81632c12356d0bc85b66d11374ace2225ec6fa90eebceeaab0598759b833a40b2224290adb91c0ec9cf1d741d37b0d8f8b7d76f9e462868cff5a803763a4c11dcc63506b9e89e4bfaabee82fa7bfced753f32a76b66e3b1304a8b23764491c12ad9e52694dfca5ea864470696bf1eb71d47c9a4e9eac38871191a2ec0bb72d107a9f1ce93fecbb999a87cf0f9b0a245a54520b87914d7ffa048e3de0d00f6e1d77699d24c8a8dc944386f8e047fb08200e4f64cb74a843682152e602c3581aa71f4682d2f2b5370e46b7abddb62996cc2821b5b44962d141d8d516124c1af32ffcdb18c466ce10acba8b2803e1174c98db20d0e842760a7a1165b3145d036c2271dd6be423f36c2a4ef738f4349c9e0a9bc70cec4ce940a59e60418b1d1d938fdf462d1f176d12bf05140b78c4d500eef184e17968280f01889fc66d6f66ec00bd8fae7044b8d7a1273ee2a5860bb3f27f727c169dcf439715e79fd75b7f30a1740101650d906c212102819224c94afaebadbac2b9c5037951b2338754fb18e841a70c5b4a0960e36743824bade286f997854f82d228077d4454887be3e71722e469c738d05d9536c012b95c9678791e27d0d27a3f58d4651b96425e1c5c11b5f18f6cfc0cfc673bcae3964c83c8b0932c564e8e240c33d8526f8a7f56d4a9101dab88499ac5de0a3838b704837f98c9685bd65e15058870e90ac648a803023a747c4341faad899ac1e43814f35ac0e29a372429417b5b732cfcce4ff8e8b0bdf123778f4be788b5231dde93196d1a1ef3999b77bac415e7e0e0be1caeae9f8805dd9e1ac40dd88af01dfeb00c035d87d16937529c9c6afea4c977f862be5b4f7425757256dec6d4be9d42f299ca0edc03c71356a4c5eecfcde85ce9eacc6584d4e4b46a8e44b23dcd5a0ef4e1842521e632624aab6f36265eb85b4a00b3cc2b60119808e2a6989b7c718accea32e668b75899147af5434beefe7d1e9ad1b3aa04f97c69c26128c3756f5878ab3eb778d10288dca80d1372c38f40b8839397fc9218b5395a59de0562a10a2d5420c582107e95a63976da3ce6c91bc95194aa7d3d5f1e453d7a3305b81f208604ddd1362e17ee9a407a0591c013bc044fcd987bfba1b1852d046b377adb321ff5422a96659aab42b05a4fe87c447dc17574f993f95961da1080b77f194279c0fdde1c436dc4cbdc46c5937065380377fdfff856c20e80f3ddb3fde742f5d4ef7f241dfe60e4b81b33ab5b35ba305df291055d75f8518771d0af80ffa0b6d91fe9e7446e4453f1de911c2e66a7a8791e3a9007374e70cc38d39925fcccb97f95173b4ea390cfc015ab7308e13f5ca4ac32e41da70d9b1c6276fd1ec06a4b4837e0751fd1ea2dee72296786b98048149c465365dabe4f57d419985fe1edfef912372cbb47079f1e9d83d88d0e9e96c89a0d945b44c0694a8be06c4d9e25ad951e7d102c8d0b7def233dd05e2a5bddaebe85b8fb1312eae03feecaf17aa08c365f3c7707202b8b2e53dcc1fb119eb6588e47b71fc90c1372c09e2d2bb526c6feb60f77513c88a6e2c5405ed8e10b30fe0b5131e09761830744564058d0c22103a048bb6baf36f6a9694a75d3bc9a8a5eca1bd3ebe5600ad34a353e772e25794f1f9b08633955f88b0c172bbd1fa3378461a58a97a8538b566b072e8d9674c47353017676130833601464dba946d0e61da5887246d05f6ea8c02eae05feed6be2be87691ffaebb41d2c0c218fc4dc919eaf0313c705363063823c6394b2ee1191ed5059515511978727280d0a50a48e250b60226dc091a20b08978a58e5ae88ed00f0203b337641b19f1d8d60c40427746c1e9b500e2adcb8c3353685b2f7f4aee8d2d2bc4459623d5ca416d27efc4581a229862397a2002461d0e3184a8ca9da22e4ce811fb5e51321cd5014ffead60a10d085acd736cdc0733478132b3037db9b6a87cbe9d9f107eaed19f13ce2fa94fe8f2a50df0fd1d5351fea5b05cf74de1478f7ed17eef2ad6e319f0dc7d54c3389b56606330358f349416404ab68906e8f7d0d4a466194a6b92b1c97a56773d700bf8f73f21e7271d83d75c85e42e9b0d2e42c3443e9b4aa1f3455207b3c7798461de8421e4dc09a59e6f1e369670bdc5553d0715db295ef7f6a2aebcaac7d40d0cd32c1d01514bc3746cf42e6e6cea82471f044d6a28fee19b3b43a0b0d11b8c0de897a1c4779144a743648fc053c96481c8ff1c5a34ae0071a58425588e530c33ee746372f27a8a3fcd61f362ecd9e3ea1e6f8ec69a8e59fa1b582a707fce5046fc0da6625bdadd41c43705351173d4678a527160dfbe848bfa03efaf7893752befec51d3bec0ddb14b0034752d115bf07aa2e87c3790f11546b0eb431f144b1fe58f8b321d58d52063ad90ebc5f4ab3cddcb677c0e88af862477a5a27233e1f8c12dbceceb8d026e77a11373ec10b054aa3c3300833265925e05e5333e9cfc65ab8a3e54918d1798d1d7cc6fcc49e97c5900b675e8c45e39e2caf07ea86770978ddc210dec0a815e229dc04e2403300b686c3e574490dee043bbacc6a81396b796bfb483cd5c3bf8fed77242c5fe23a7d7dcf321d143199a369ec13489c95dd2bb3aca58786740555de1620d148b6a5bac8017697eff871217f02df76bf4b08b31d4ea5a2557d8361cedf9d18c0114a67036e29e6d367ab46252e2fdc127211ef5b5e397832a57ea7a02ae8d3995c267b1789123766111b23a8c4b58d4551da144e2654e300d11999b845b1b3d716c2a7e5a75eba3811010e51aec8c3d5627832378a70b42828fef094b17574e27d3b9ee931195b27229494d5267a76fbcc379d807390dc15e51552e4afb43366e0a71999ea318960db2cd810da0232bfe1de66ef780132a949f0ddacbafe25230a8f11de29f52f61aeb7632efcc4e09ea9ad236f78e2deeca4061c337b3bd14ea77329af0f78aad1543003632a431f66dd33a418639abf0cc1560006ca20e8258cbf437d33bc348bd3ebc090a0199b0adb144e686b74e0bc0e8e08ea503c6b477c2b89b66e8058b78f1054793a45c4c69aa6e42e3270b8a30f924cbf855ad59d1243a74c77429ec872f633354f495b1b34c7cb341b919aeeb0eecfe596751331ba658f4cef0791708e4e6344dd8121bf9a1175dc963f32caa1dd5cd3533619625b00f4e9f468468c4342652991af3985518b3a5f666c9ac8ac39ec499cbb3ed190ccb3e4071ce74d95dfebd9eea088e3bf134b87ffd6a4b5c2c3ad5ec0e36350905f77e4b3aa080276f7c47cf6547336a6e5a0549f9221ab923aad3372c9781cadd2352a9502008955573e5f847441434378ebf6939fc9cf05f57f6fd37a97429b475fd5ccaf13a21daaf312625948f15b0225c402c084cc24fda5aa55311ca642b634961632e78c68045eb0ecf92589b3e7e1897cfb615e8f4397f6f2911cffc77f604950b12da2c71227318c8fbf898b3cc73d8e00bd8709546ca01bfe9e6d1a6dd7db795ecaa2cd3f351e6e59049af5536af532f81eaea4d06037ee095039339980b7599f6e6204967ae7a85effb224672539142b2935ece20d62aa660317c72f98683da76c1097201d8e6f29e13033ac7aab7c6818f18ccd652acd62a0e65e907cccec63d227f9d14c28be654679fc13245ff70459144f2586ccc27084a1f44fc3eb5dddc23731bde81ea3967234f987c75374ea9bc575b417dd39f3b2c252cc24054122893c4d173d00436644687b670d25aad67284bdd89c07099800d98dbe7bce47a7f75a101f78d4d1dd526412058b73318a763a6b298d5322cfcda8edb59a027df177fb966743a29d340e8b0a90dc3100ccced594ae44190fb2896b4cda15be2f7f89c401af57dfec29e094c2cb9598964d0ba69483eec7fae8dd6365ba0646b71b06ac1e386a0d6688cd81fe6502152aabeb467010da1a191144304a3e3b34603c9bdbfe16d118e1dc2f977fd7d8521b8c337befa9991f625691b5bc1cc46d22eed1a2bb444e3940876e3d4626635a4676593ca3923d0299ed6b1a74ab09e02773d3da1928b4c3f599663328b2ba38138d18ec76b8cd7688cc0e9b2d3fe3f2f23ca9380f80925a62906179ebca2887023e2df87306400cc42f5bd20e14b33efde50c0cad0716d17ad52f41de7e85d5b0b71b40012710e4fa8c7ba3ed8ba8805c9561bd74be6c6168ca8c1e643f9e3947ebf2da4f220a25984148322cbf2cd5dc3772dd5c863f6ed8f2b99e2345b9b1cba50de8bfc04c7d782154d183a3b9918164c361f7d8968cdff346d871e514ff3205151735bfb67e2722a626124cd04a8b795a85cc3f0a97c516780fd35af877f99c082824bc7d3f7818f9e4202b52cfccb7eaa743c11eb9a0ff8e9957da6150283f061e8469f3fc4754df56282dbb2bf2c9396502cac856f170b1491695d8a1a358a54455ca8437a268feb7924ec3aa40cc77b6ebcdc704a907c4135e724be5b3b3fb2e5f76c5bbbbbed1a4e0bdaefbe074d9b430e36cc743e010435c0754cf0be7849cca91c68106e33b003a0b4fcf43236ee6e47cb59b11317c36594a7134fd0d7d88cb2c92ef2ce8aeebae0561478fcd69ebb0199ff4c1fda3cc6e37fb6ff821c96f660e8d461d90eaa7e743deb52efb389f3c88a46bf3f74fcd4e24c1d6f0d16a98e8a0b66114d36ce82de78ba05ac49c3819042deda3788e836133df2d35e92b0daa838561c16a3d6b26a42bf32a85b11e54adb198e09abb8adda092523dddf3a7b6535f74fc3745690ad33bb5a87956ef20c1b6c33b8b8775e92d869143d584d169fdf01044d5b7261b18172a91e338ae766ecf271d4930568c86ab436a697e01131f81083ae4d65af2ae62ed27513464ddd81e8ad73a995c9ad53c3149577601e08c8250314f6fb144e123dfc561339aaab39320975652f99eaed93692da12f914dae232e9f9b165d21a06ebf3fcc2e053b33d5f3359d2f8a523ae964ee8e208fb3f5533a04902cf161789830c5dca437f68a7b4c18d9c4c333a5ccfffa21fd062c46e19281edc7975a8c14febcaf4c10665cf2107c4f72073aed05616883017c94a75c10f4bf048342943e9cbd4f8f45bffdad9e0e569027200ca65144e65fa020513ac1098a15afff3bf781c73406384c6341839108cbcda90e80c145e3b9bf8d705142d575bf9066c92675292200855c3ceadc7ae6a1919f3eab5b5993a9f968ab5fa74366dc1351d032e7f11aff68a426034b1133e75ec7de022b7c423b4cf25b84f3368ddbe926fa8a617f2b3bf23548916ee5f5442c13fea44e9690716e6672c32147203b28b13f1d0202471d037ebbb9faca93b401809475be471abe7ab97285bd6b4413375335e20700c0c524ef7b4655b31a054f5f319a1fd94f8c690c3e150b104f8d0f0333526f2ff3852944ed24c70b855d92040001a9c2f4b65b673624c0530967ca9a32e99e3694649f84aae73c816bb24e11cdd7177bde0eb582d2474c7ab8717e5d38a82326ac9d12350545974bd65052d9030fb480edd806a9e674b1ca4c116fc9fa967dbdc11d205d80988b6221574d9950e264cee74dcafbbe89ec613ec28a11483afcccd308584d9e5d143e313bb879b14ad52ed01aeba4a6c5edb94d9039d85823ab46c634f04e8bb7710488420a984585a81f92de21bf9e4d21e7939d7b05d9604ed0a2ac46a78aecb947250940901698be365eaf1effd6a887ce0ca057d2f0086fe8952e34998ee7f9b47c00b2406eca883bdf968367f1445ed60b61996301b477f0e1ae2e4e0fb5e5563ae93ac207246e66840eaf9139ef57d3d93ee08d1900e1ad19d13bd10708194efd5daece8ff83f7879895655bf5e653411aafd9c600e6e14d473b385fbb7e720ac479f0f7657be9b89ebf0d8b3a90daf2ba8a338d602796bb321d2c3093cbe048867760f06992c43160df1547fcd56ee1a5463c0cf874f200aa01b85603412c3aed0d8c61bd9f5737f3bd013c5293e06b45afbb88c489fd79f648d4ad2e3e21b4d6a5e008d776132d7b7e878f201c1ad2b7df6241578f28f1919a1a6eeadc9c75d71980c8e9457ff7d34201805fe7481df129ae2554b3ce74f1e1f15c09e9a7b73f46780ed99359a525b6228ebacb0d02e822469187a75d56e05c10a3de3948fb657a4f7ffb0b2787349ebda40fb43b733cc5b7f04c3486e1215a4668d4f232cf1d111167a49d4a62c0f626134bfc085c3006cc55367cf7bb4c88baf80d72c1e3fefa7404497f2009131c3ac093053ba3bc5152c40ab5d04a74df4c6f416f5b4b80f28c98811cdf969281b2f0e6b2a0c47d42cb37f575f0d8807f5fb77ca8c4b3ae60386daf1ae5e9de0102accef704bf47a6609513a442d930cf3a72455d25f3cfa931057b8a3cbe43a4054f420d5172f1a78e7b5a47cbe75a623bf8f3d02598a299e2315a8bbc1b68b64dfc3c7a1afe8a9854079ea85c552d6defea6acd75f88212cd4044860918d1b5329a49066590530e0a188e506dc73ccc1a386f03ceaa2e5b19ea4e4b88a4ca8c5a1608a302a714e145cf973fcb2bc70a1d1a1fc8bdc744e8482930b1f2660e0f4ae96ab522a95b3484fcbf5f5fd56dd4c1714e65e622b889fa5150fa3165aea029bf24abe1ac4a6ec741cd061d934e01fb89567f0e0b091bd3a1688a07836de35d463a3eb5c1c80b68009ece9fdf77d65a5575e0ca532bdbca30ed2628e2b48020ab25752bfca2ff232e938e052846b8a51ff4d902ea734560a808ad9bce1a9e5f0c3e9713090b41f36e923bbb104931fc715da7b3b157324e959145dba35f610b777416a9aeaa5e1a15425a2ec7094acad805091f47dc7f93ebd1883213317e373242d246947ac3b7df5340a6ab4425226071b86dd76d58666090103dac969331cbbdb097d340621da134f62e76c233daa7750c36ab21551cc047daa09dd3d6cc504614f8c9a0453648ef7cf34d9f1e754bf22b65ef052f9fc4000627fae577541ab36cb34aa8d8f7d69b1fbfbf71a2e44266eb050112bd2c58e4041ca556f6e5d90b55e404b9a0f41fb85ede306432ef8ef3059ff5296c66174bab36ee6b3394d77a1eedd8bb955088c82f9a38ac1fcdcd10cad500d5890ec1c2cadeb2443d60d2b9cebd0c38c4c4bdc38bcd7c81e26680dc68e21a28d3e962519ee812eb48ac81bae6ad9dd47b5f5813c27a6249a01747ecae746b4260c51024858ef438edf80e4c5cdd67f34e68b78a79d8d1fa7d3c299a55862246686f370455390fbce6997492ffc669832d9b8909720e7d76cc0d32a89796cc465030997df40997b6565eb07212b042f328a1fa5ad8bf2be448e739bcdaa162d8d68caf3a8015a01b61fa53b3436e331b9a5899b8ee17de83c38b3b26b7566a22041f9447989bde61c32f33ce28714636287cd4263a879c698502e04817b2c7faf2497daeb83778b628c44900d92a9745988050f58560c5b4e33cbbbd0f83831a46db39ade089203494fc9ee79d6e0d9e09df28e20cef6eb89085f0d40d36f6aba3fc923f6604d310577447b4d7f9ad1ce47a4366feb5b0a2797df525a3b1ecd7fde671119da83fcc05578ee968797b40acfbfc47f1f3d523ce70ccfccdc3304d9531f64f2ab03ae16deb7e5c47345a015d400198c1d3e9d8db4bc1de99e41ea7e66eb7271ec901198bd66852c9a182d7e2dcdcb5897818416b1319627f55f1c7ae09699499481e54947725084fbb6fd6b16f637a809b1f5859728479fbf5e18ad25d10c7193254b242e0d6d69ab759a16ad00f1c498814fbc0d85bf90bbe0cf4a1af0d9a4f56c5f930f388edcc8ea3a4ec18fd1fbfc47f25357c889dc83446c5a59981de1167edff9cde060c10696d5f2f2c8e61d7f6d7113d8bb1b89217dd937bbc5e9ebe72dc12abba2aa5fc4e10b7a20cf7eff04d89a7c66f9fe412c3c90112cee28c9428cf3ee92713fe3b5fe4e6e25ac89fb2d9759406e3b22d27a0f026cd23809474ff60f8245b992dc1c5ce8ece8434d6d7f6811c6e29e96af6354fca085032074f760a01692bcefc090be6626bc2a2637a1e470a80141776e8d225158943083226ccdc34f713c546657374fe20879b03daa67e035e5e25c484f901714dbf68a6ab91966c74fb27f8a4d7ed7f84ff63cea7c06a4efe67c8a9da1f2dea72a2ecc256b9ff48818a7084d5f70ddd87735e8aa33237f0ed2ebf26d9efa42b9fe512073415275950ff130b290b445016b7c026e9d7228fce52153df91d2684684b43d38f5502a627ce2c02296c9698b0691b82bca8425d7a885dfbe9a9c0f1cf09b779a0c1a785624b368e9fcfd439e58c5165aa722b5f389686ef6daf408612544bb85bfd6985095ae06b7e31496f92edd907aac12485554bf9c1c3859200cacfec327ec6dc31f40f1b71f43995d6cf5a6328a9716a839718625e4ab98b267f7eacd741b51d10963bec81bc621875ee4617e8f3934558cfa7b87a824ac7894fc00c4a395351a45c4b870863207f8477f4bc714539bd1729d1920e8f59a5304196803ae862b31813c88c6a0614fb43763ee87a580ba8cc14cf815c4d84c3901495453d632c56a5c11b67d8ec8ce1c4e650ccd533755de85c307c8847c6bbc25aee39d1e3b4098ed1fcad0d890c2ff42013e4f33d1ac0a7ad743fa11a7b50be165b64bcbf1e22f3ad7f270ea40657cdc17a13edfa90e99959bd1a379e565aff8af3ae1eabeff2ec202c27393f43f887afd3015f34a595e7c52ae156ecc8ebd0165e7a1ca94505ef464c8fe88253a3b8fd8a1fd91a84a7d09315a2cc8c88eb8e8bcde8a7e749c45b6211ae0e203aae51f50d97dde6da946cbf02862ff7ffdcc6d65b3eb5c02d907dff8055a9c5ee759448a5e1d82599b53843222e3b1c0b4aea3e84941802a3a95bc508927161c23c1ab7a5bb12caaba6cacb09b362203d7e6726310bc4ad5e11c8f104b26954285c9178109d10c9bbcd0ec41f7adb779f5fe178c0608dbab216c0b4b2722c090d3f9dd57a3b657f0ba35a5ab2e8b7ab1b7979f0850829dec80f5dab895f524a6227d6752367ac7937270674417bd9ac2a6236f154b05b84ff88d17037591b2bb53c7b2ebec24184109057a668722e710feb0bf7b65d99dfb158bd4dd04863cec6009cfa4ac948c422c0558b2c7805a9ca3e71ad829267f89fbd095f21856fb0cba8a65332189683d9a873021fabc8be1a922df303f74d519688bd39ace340c1634a32d008f7c750e6ab565e76073394afd56c3dc546eda7840c17b6a4fcdee1a4d3f75f5061c80b6a0e10d27afbac62d1f4b4db934140d5347ae6c8a6a5b05d3c68e9e9dd54bda56e85f7372cdbcb861107e59b17500b179f7d2fde20eab1bb2d9bb9e9147a6d83d7ad7961ebcd13a5acdfd969388364e37b2f67416f08bda90824f7f371d4c54038e9fa8657df498dafd570839192c2eb4b3ec904be0e553e5ac35ded047af2ab5df4a8f5931bdc735bac186fbfd65e16a7fc800c8a8e1798b3b2a6dd9c9b9bc131fe867a6cd86da1e888d3d7c2f0ebaacb652e95a6b89a71d32b671b5d4536fde59a5fd241b0a8e09f1082bba825d4f368bfb6497d6da16dd80ca44b07dd52c30b374563de382bf1a54e5a4781f91fcd33f3cfddeb684682a3574d44aa461e0ebf90826197767f1a8d561d1f5ec6861166955353cae70208e101c22dae380fc38f0005aa6fad5c84c8e862cf16ed9e016e09b010d1dc92751c841c575aa94f50f56608f691d1e90815be30602cdb0204c99124a7d856249a56fa96536cfe3c5d6088698202b39b6d5f36e023efe6b8d6ed1e36bd1f34eb65dce7f40c44113680dcaa6fb349e954cab2055972f535ed56c4268b68035e98137a610a835add6600a7f7474fe9d5689883fbd911cd89a6c7052cfef1fe9d424b22848823d288632c7fb0172d5120b47f86cd4909c49c409f26e27cf1bb147fb771f5fc8a94c0fe7bcaa2395f539eb83fafab0877f950527de057a8243136fc45689bf083250698cd0a3853630e3dee10415f73e0422fa85c9ceea2a23cfed25c8ad65e495f84cb24f0470df6ec2e92693b380d15e553aa635564e5a2eb53a260ae9bf900b8572eefc99bace057f1ebc4741ad308f179652e5fc779bba4822b3c7b25557fee6005a2c33c177d66ae4319c93679d8748c41341dd93eac9b6cb1b51a14c171de42a13f02542ff410b2fb821196330a36473b9cb483d38a5eb6894646b38c0b8fb2fb954e0f0c2ebdd41a6515f8b566cd134d7a6cfa6e946d128b4862a81d8722918f2ad932ecd4b03bef6ed51234eb38cb1432704155695001af9ea5f11fa3bdf8ae6f24ccb6411b902c6f1d59babf437f49a796c214b6d6f6b43ec7deb52a2d012c42a0cea7e55ce5cd811a2bbd50fba59b95654b7897ce5df5515f8bc237069f93ce9ac22449d7b1c12cd764c874df1e645c8708045ec415857b91b83419a09a604dbb1c496ac486cfe0c182452b62f5724fd8783a7dbdb0f561c33dc977e6016546245ae8528eaae4b56aa0919b97b34022e8f0b4caa8785c32bb5ada372819669430183da09413d4a8955aa9421cfafce91f089c5120b13f0c6835ef5a18b5805614f2e8edfe59871324f8acd5ecebc3a62eb0a16150ae497977a02b43cffeced88758550e1e98adc6aaea1a519b2293ef03fff37b1b654527ad07a18f619e66a509e8d02d2b731c748ab1e501b5d0995c899929671cdbf23770dbdf1d2d421c90071623cb23eb415e02b99d583d8ca3ee8698294c5a86aee4997281f485d4fdb930afac842d02bccd2c0dd8f50b0c6fc00236d032c758dfa7a161117a9234675c0690b24575d5997717358ae32eadfd7cd09deaf2dd06c7507366a65e6d8adf116a02c03f8f9e5426c0712b7b5901399770beed2275c19ed997fcf249cf547ac2d5ad6cc55d3414212922400b60c7752094c2e9de612b1d75256f98770a4e486dd905a1a8b01b02b2389863527c732d5f45eb5b60f61ed0b21520e54c229ae658e75043a2fdfb0372fab0dd62acd7b38a75b94b45c546046aa7d2664a722ffb293eb87b0b8d0c7c6876395b676706c582d40ada5a56602a6c338c4253b3569da98209fb2afa58cd8bf8b467e0b1ee153e853e08abbaf99327ac1fc183d0cebce1d1635abb0fd1395bcac18273be52b0c527ccad512881250aba58853ffb79583032ec4ba3e5bfdc65521c784ff6886aad1b5daf13de6184d2d9ffaffa4ce0a73280ca03066cee05986bc9eb6a035a719a02259ba370121d9879ce9dbb0e54d1b155fba162f6169e02cd5bab5abb4c487bc6116017e0195883cf8f0b7e33e5d7bb86112a4dfaf24125cd982ce38399de8f01fde09fcca0c529f2008fa6ac2e699342867cb0e57f3493bcb7b2082ded073e614740dc5294c133f32d91d5fa037feba09f5ad862111037749cb1eca32f7234aded6b89eeb420cde466c2e9233363779b9fe1ff71fdf6241051c1092e85f38016bc0ab5bb07160fa7ab34eaeffce5595e0a22e7814f5ace2c7ed9120085bb36de8dffa057be598e6eb4133a4e4be6e3a4b0797dcdb82cbd969bd12585b3addc26315afd94d73207230bea769e10d1ab5fa324dd4beb5cd8c5ddb56dd0dfbc069910b460f5bfcc7210e6f070800dff69fae08f6ded4a86589af39f48dbbabac756ce82f76944a43f86c97c4fa44732420b7b36f0ee36268559ef4d9466730b428a07150dfa384abcecb34e5ddbb5ec93122814499608752cf2bdbc3fa13fb2f88eded8057964ffb3cb719846cfbf78e04968f383d8babbf1e4ac7c718faac3701ff400d32c4739312054c4dcfbafb20686af8c371b5b3f2348627cb4293592d1d5c3b37e3bf41b85ed604023b5237b148d3148f6a383851dd51b60c54f65133466fdcb3aa55031209a2aef9de40ce743b2e4afbe9b3b4e65d4ffd9ab73df4b09bcf50280ee67b2cfac9c39c231258241bc55898e3852fe0a740edd70ef2f7d0d640cbb523a5daf8d37c7ee8383ca57eb49780aeeea31a10187c85fc27b315023f1bf36aac8888483ae6e0eff4d172c282feefb888406a6dc70840954499b578ae0d87cafeeeea56abefba560222b6367c38d8e327e640906bc4b9ffaf0885aaa66bff4fc1488b3de813f9d75e9d72a3ee3534941df21b38c734d8f1a5f171d374796fe6744f137ded06b6c0299f3dd66b32c6df48991ec8aae680f088b68e726668a16fcf92347b7b343f73c2efac20c6ef101099e0f4c4847cd0d96832cfcb0df56c3ee666e1fe2e577ae4361deed71ca419266033cb5882cea15bd999646496d9949d287d34b429aae01e0203aed3f8ec8f53758102d13fbe1f7a1830bb8829860b482ab203ab4a7792be1a1b49f17859917e270f9c2109a00e0e850ac2c762d15bef3dda61d8f603dc765172f6948e5d9ec1ff60d35faa5b7442655c3274d49216b7df964293711ee38cff59401f4ac07eea53edff45d0a64815fd0401269c5992b9ef7e0a19c5cd1a580531a77c37d7c3fea87da3b0f8fae0dcf459023707b1fe82bc2315093be688c918debb98ca204fb2e5697d24f328debe0f680d4847a5c892ddbc21e86484dfe163d558e4db0fc4f154179646f1ab7b36322499bb367a92dddd84269db4b0ea3473c3e1d6341a14597865795f271e4395b846e3382dc68c4db61042f5a4add86d74379b662c5c64bdaaf241e0fc70507e1dcb2d29e462d33d182c612072b271cac4e63c91536364a49262aad8ccca8568f04d853d6be68cff8704b73262146a9bcf63dada31f2cdc864101580362c48201cc3cfe82c96ef5847ad54a39d2098c63c621fc411cf2d99f6d65db0fd3ea0447d3e98e20206fbff0f4604d09728fd466fe2a559ddef45b8a0d854677bf0e4c9c8c829a45ad5a9219e4e0b7707833fa853dedda60a95517cf170f4469eb96775c27a10ee693c9be00af19e3f6f83698c7e51973c982534702fbabf157bb6f465c3580544735bb8cff64ca42e36aa7f2a9bb6c0b119de23d3ab503a3e6def5a8990a89e6c1a7e2578840dce31508fc6ca2954551dd4a4b116e53d154dc7759b2381bbdbf1c8c4e3d622d4479776495f4b8ef2c79eea942762793580761336d04cf566c8a322e6e3db2742e796f053379d6ed56fd49a121810e98d090fe4a1556f1ca501158afaeeeb3aab49c5eb34274761129f65897a9f2e299f9a11349344cc0b18da1eb583b65f8fba5ca0646681e2eb9e127a1ebe4a010671699b570bd4f624693a5951229104ef948f089f43419bd1af5d40f27ecd7d55e32b6f7cafcc56ccbe1ea4b8abe599944151c02efd7fb5b143cb69497bb2cff9b97373ba24d2a56fc2bfa5b5910a54d66390f18fd5d305f6f2f1a8a15df9d4b336de32b4ade876dcf00a4058ce6b5ea08fa7a64f28c474115fdf384e41259fc52aad59c7ab47aae62e0b22dac51d52bfc28051c4fd8a810edb5156247e97f764aa723acde53bf95edc473a93d09fc2faccc145562df3bc6d27f0c2378d32d6fdcbb34c6bb6967059dfed3afe5e75b2ffb082395850404c545bd043a71829eb441dcaa8f383e9edc361cb1acde9681b1b4866cb6f07b46a0a55e82079888d191ab8d68ed52397a54c3bc4960d20c06ac4b2ecfb8d5472e4c94b33a7879eaef63a214dde9d0b979f98416aab5d0565fef75f7eac770b3d9bdc5c71a97df38d4dc55a77a08771a037caf8d11e30f20bad61b5fd26de9abc050b2e67fce266c59c89d4577d9cbe559c575e91f520a8aa3f57a2a49707b0d2ab5aff6cf838d400972d24794365f4f25131c813fb59ec28f2a2a7928d0777c1ac1cc6edfb269fc9a31eaf8e642ae8f3b038121340075a0ff41d1eb77d8779c8394c2b30e5b6e8c15b54b04477c475216206941397db5e1dec1ac6b2401f5e36173d87649ca17f950fe34341b6fc0b77e800993517a5931b72a0e9e2913d0ad5209a347c8aabddd6405e62237f7d345c16c3548c9bf1e2f393739b0790a0e803c2b3d4bf1a11c4b6260bf63fd452f9bf92d28d6f42c32f154ef2dcdd415e56ca7e1e5f733e0349052ae373f8dafa49be59a678eaef546e6c19beb4950e7d78aa6aa11618ec3368336c66a2c95d06c2993bfd7c7fcaee60c95c368043411cfc02b074bd059c72681c3e3e789c4b4973fe5a84dff5bdca6a59f4c2094b8b2c835c0dd0a225cc58a836a6af3c76359b95b4afcd89cbf7082bffb2ee2d4a80b8bf1553c963937c06a46be0dd5360d1273e2485966a326db4d1b1da51193818bd7f7fe78279c8b3ee453fdadc60437a4f804dfc5d283654d760bba54c694530922ba043aaeec0f8b3d41eb793a77cae179a22874b20c4a89f4ba1bf91b98807e378847546d638d91410a84d6fd8278f2b0e846e65ebd4a99325bfc98068aac13654399ed65dbb67e84e79a7e4af72636ccd784d17e4e359d6531fd8509c91e808434975c094bdb82ab0a383c313d31412d29e93086636ff7bf2e9f40ad38be671c0ddc20e43686419b128d45898a3b15bce3a0443c231a6ee5fd69a5487878b5f5673e37f69050960ba044161977debcfd0bf38a921249e6220683b0ab4c18d5d75654ae4443fc8547628a4cb42a3990d00221abdcf71fa5a0b64bba940b503ee4c75fcccee7fc544026b8d4b5def342cd0c969b6a70d113ef5d6f28ca032ea7126d5138af5c9aad04cefbdbb820b5cac53873ae99d80fef9463b2ef42e45a1a104303e798553245933afab502a2151f88977747c9f8920b935c7afe011f36c95899b3ca4fd06657b30df029bccd47a74cd675270428c9aa68cb12abfadf3f45d656065af09cda19dada5215ad71de21fc3e5e5f4ff99d4c23a4747e2f32fc42ccf0b21189b2d918b49319a1e5979b6c46dd49010f3def8c7160023cd153fce0ccb4a6ec35b66f322a691b3bc8e08c8b846118a9655a0761e8b5dcf4d6719e52a3ea65edc738e07108fd6e88ebc670f2e7e6e5f6d35c880e11b42cf6779ea41ae13559602bdc0139a8be16788fd6c2513c50970251c2f8ff3f89c9b0eb842fe20f4ffd1c8bc8ee17f2d069217f289c78342d16edfa271dc220eb921b014e89c6851805b8300303ce4ae0d81d8783e59c996dae3822c25e85d433b418f872c804ec95a9766b4fa764ab1727d17dbe560eb0d0aa94440acf4f7dedf50ee7100729820f37af58fb3e5a22fb28623172b05d0636a707d3ec08afd3b5396092bee1eb1583e0dc0712c6267f55ece70329c99ecbb7f3dd06903f2984616d0e141cb93e0a105ee71447ac8c72c3d3c8bbd270b86aed2e5a07e93bd86703baed63d33c6142ca3cf3007c47be6d50bead8f3ca74818dea96da52a23f9a0fed5611e8ce103a1880f64a134ede2439caa82c215a8cc442f04f06388f0c52581adfbe6702ac230c81bc35f9eddc7fa6377628371831266a3d63b119d9827c665cd71abbdb3a042bfd0edc8e8460fc13040e70bcc5e7ee882dfb2392863fec0521be4957023f7674d1ef3c0975dc236da857ab7fea53e984a190676f4ea73c2d6d8903f9f7a314cca0f4593b51c690a525f358b860bf02bdc7759d62e05fa65c151f0ef03131a32b57c675541c178be0f9b2707f939a7b65e54ba5d32ad5e3521edf876f711ac7c194a4a2b796cab43f731dd6c975f5a709108a131360b525ca141902ad48724fffbbdea0c6fe63c62a28ec9a4ef206405500ff7764ca75172f8f906bd8e9b20dd5fb5b0dba93655b22c590110378a940876948f49a33caf383b8ffca3634353ea4d514e0e0f0dbce6f90aaaf1652da5b308b5a2242c231a9e1b790c9e972bb80a34c973ae108d02744fc92c098ad3cf91e05fc4a8db3670e4904660d2e68e860ba228586d421d7151860d1e7318a95c69a4f49ec8646f126b63815641a0fc66e154514e458b483fd5e7aa7739b564fd8519c32903a829577a7c824bd29f53fe7f4d08de8f178f014dfab867fbea4f959994efaf2d23ddf6a8b0992f4192ca4ec77e7cfac5240399040f694e6a434c4cee3abb879e6f69ec5c5614e6de99b294f66ddca2b6b6cffdda5a72d06a69007e28edfd8f86b8f642b3f12be7bfdeb66e53a3205f15201e9f35e455f507998bcc76aef9aca110209b3b0fb39a0bb51d774f01f19176b4e1eab09b86dae18a4002d9dc89235f178288b1df58d911f525d2a538334965121cc2cb1f34db39c5a97b8c11e2623d8f7a0ae2cc57a1718ebbca2d85297e27f4da56ebe562a9e7cf4651b6a711326a3f814a9549434b5b4a81b8340d818dbae145dbfcbc2e22cdc63f6b05f2ee9d061ed6c6b0bbae5236ee186a17f67a1075aa52d63ad02883ed1c8f7af48918a5c3f9952306c1b289d13b46fbdb75baf27fc7553404169e345cbaabaea9293af9e6996b67005ee8ff5266929c83db9d410dfbc909bd7b4ee8b86abb61fc6f96350702c4be8c0951f87b1886cbdf0f135dd98a5a8e4462b564235cb05da9f4d86a4d6cf07aa1781cfebdb4cd40b63ef0c6a902ee692fc92ff65c8e0e5e19a5e4b2ed140a744045b0d88d218628d98b4272623ca95119d258071829c383dc4b4ba176a4428f764a638e5c042f90f5b9f673046f888bc04d9a2df3d4603c0ff3b2fa885604ff5bfda26dcedcee26d678f42c6bc0e93ecbb63e47d116cf22c1e50d91a81d822bc9b66a08f42b14936f5ff42023e4f0f143032fa8e3ce1d3e5663c3cc565656d76dd8fe236c6c9bde55a6e84ea233c5aeb431a4abd84c0172c7733af8deac93f71df061cb8d817b4aed03e3acf7dd555ad08fb6ddd6fa054f198bedfb1bd6f6aa03125f312368f04d293ae29f99f09e463b29eb48003cbc537f3a54addd0b00633363d009b0c916c87ec482ca88a2d71441925a157f415c802365dc58fa14cee370c145002b04a0976d8c2af61c2c26a1c5af569646feb5fd7a312f7d3b7ce41527a9be233b322e1bc5b17348f7329bfcd67b579fde62eb0cc9a98db45d558e27dcb452cfe72ec5c8bba3cd26cfb1e65db9f0eee9f8dec852cff8342698e58f9e3cfd9978d5bd1c5491dd1b1473012d505b4161b92682dcda329044f50482d71bf7381feb672becd5161ab8f0c03688df77a0c075624dd19033b2bf991d4a877519ee93312f1ab093718533e2654c48e017ab8eceb146a208ff7978c9025a201e64aae7ae4f3436f9ff70023ac154cdce73929bae40bab3be18bb42810d58645e80dfa0cdb9144ddab3befb9e0149f02beb8b39144eea1e957dd07411d77f53d0778349695dc7892f0bd6f0c0005f98bd38a184a3534e54d1748691d43f3ef2a6a09d7b700994bb3fdc2733f5ea5828ab07c6eef88bcd5ab99ec4059125e220ef5a6048580b8c5c777c0ef06d61c5d1322c8fb3f0c9e49eecfee6065f70c10309dc34b09ebdc413cc09dcb2c87e45abf562e772ba9e0ff25d2486e0361deb59ccdaf0fbe52713de02d31de0fe5dbaa2c0b89425c59480402201342cc36f055a1dbc07116a1217768d5fbc81f1560f37bc6a410aae89b255a716426ab9f3eae518b4237f500bbf9ece16567639d807cd0267610a68711c4a7162b545f18e5d004a464153419bfc9697dfdeaf362ab11d63dfc27112ecb8889e9a1a1fed9fb31d59496001f1734ab3e497aac7ff8693438fd610163cd8c0a40cd42e36d904b4423290399f8887bdcaf66872fc63c0a1329b330ad455316ff34bf581be860512b124d1e9272083ab2660f96d520494c77dd96a564177ccb5b6e77933680404d9e08e276ff36b599b7ecbd3a42f5af6f8a0e657c876b7593b31c993ebe989a541530a33ec5617f715534e6d681440b3add780cc14888501a331173fe9a6847a3befa193a0704421b0a113e91991ddbd09b5c6a7a9ee844ded7425034bd7ad2a48799a20a55a3ad02ebbb95de4527b48036011740f0bac2b2e14a586c42301170ba8380047fb70237804f401d531bbecb112e420db16055a309e7ff76793c09af4df50203732c9c89094c0c052a46825901b29fe288f25560bc7a0910f925272d0a6db9ec3ce34903e416cceddf0b0aa26add6309f3ba6d9f718b1535d7c9c485162eb8d94a317bba15766e2e595f4d6eed0450093eb6e8193d43c38df1d6ffac4ebfec0d615f3954fd18a716d24cf1875900cfa3aa1ad812e1f16087214fe04c0a6c82393e28f2b9c10b2745716e56328aaf2166ffcb048ffd862c06290dcf309ac456b1cc3d12ce6545655c5c79f74679729ba446e4b1a8721824ce1e3b5c39d2197e91e1c46f29531df75726b1170f7daeeef094352fa9af62660524d955926a03d8fbf6d5842bc6cf78045570c1342d4b895da6bf3bd273432372363b2e25e0ddb3e402fc79e7c4fc055c29a31465dc380120d9393801793b1c560587b281c147851f88ba00b8312742c9cc4ee57dede590648c8e29969bf1b409c93a11b702a2a89d4304fe831938f1928304aa608cc7a364325d8b4856af93b83fa430d70797371164c204fe94fd1ff28a6fbac47bd772127578f2d3d3e4d1e11b67551e9390ed650e51a06873a4dc94d7cedda2ba13b1eba4657fa66ba1bc7d63fc18f9a83e1cc20d026fa96c437f8fff722914bdfc47c549e020fb191d25eb782eb062c911b001f2990cb5c75182fe38402fbed51724a21557a60967314106c99c815593c344dd8bcecc9d392aebda7c327c26366c11e7a96da2363cdd1b0b643b0c4804ce492faacf45cf408e42c38bd666879d0aa87d97771d6d59c1952519d31df00af1360938bcaa4067221dbbc8cd2d00678148b8c202180c78a5f5d0067d59c611afd08a5f2af6a7a5ed33239c4d663a29662809dc4bdd11acf10869f42b0e047bcf7cad21a1e4f798dd0c6c57a0bade82d36edeeec5256f3f35d48e33205d15c5dda675e71ebfb25a40334560a3f87200e46035209709ac16a3d3423a74b0b933a8343ced92f5db934b004bc048707b413b9eb6961bc348a990cbd6a091814c67c20a020d608992d197f3741414a2f9a37c6d6d2e614184bbf72819b1b96ff04003a1ff58e3479e399e9bc63da13da2c6d2186c0aa98a7252f5f15b3e7da696cf07a7e22be6d4797c420e05231d706423685aef4eedee8cce4e46391047941cc7abb86bd02a85a34947f17249ca821cddf1d8d24e550a26b4e76f37c4d1f0f92f93225ea6e3083f9ad412d5087cc98b9c21b4683fc0976b6c4a83aeacac133993a7b6f3b63be651ddf69141a51ee1ef01322106164213e034259091ddbb9ab3e392b046ffcec324701e977ba72d3ed3c16017d78aa02c7686cb61fd49b560cdca42aa63532d8393d49e9cdd623b0bf1fc6d0ccb5cd609b878b1cfae8fa6c327cc6ba0f5a05b2b56c62f4714b66d5746c41ec1acda9159a408f29dc2cf6e3b07e90407be8453a38ca76dce2bbafe94aa03bb1db765b6352bb6cced60be40406509c8ed4a9a803c17bc59f5b141ba67893e931e391ad9d5749e213e36e52581e875b5349da9420d8652d792eebf84c397a376d01a0bc7115c14f7fbdbee776a3d1b91d0b0da701ba9482eaa746ed05a9efd395d5585fcb3c6bbf8a292b366fc8824601f1963d1560b518b138e4d295db3f03628b0a02d9cc557f36defa976f1efb7ef4c23b6a263e2fd7db443eaac857dc4c9fa54fbc0b1a6ce59975f50ff346c454dc7301c13353ddd7a4804ab8b795ab5d4400dde4fa66262c129a31eaca96ae54cc33bbaca777be159b92bc28c7682673c553a2f298810bf6460a190fd3febb904d1b77e5ecc57e8d707edcb885183a069deda8ad124494ca19738e6c55c1d225180401dddd10e7212c4608becc4acdcca3a2f8e7d3ba4217acdeb45ac8fb2d55015c83e46006d811016ba303b0f48af4ffdcee5987fb91916b28f0aad9961aae64c38501053cd2c3956e41089e1b41ded7941f5406b4fc23d48592699c353db3931b7e5a2c3529fd9350bbd029ec54e1e626bffc9e1a153df15f0af9384762734fe017dd893e628a077cff661e6f4f85e71638f38a2ea6b8de465e09378eb00081690858568c58221382a14797a4ef3af298081c8899507fe113603e3a61055a60570f9eb33a59d56c64004b066252e16a0b07469a6ca118c70ae1ba518db294599107e44f0bd251a0cab4c7f82f1211b59729c3c1938f4b4b7634d4abf7d6ae20068f83d2a3f68ae86ee04f33eee734d35f6e318c945c09a8493671cab96c2bb042bbc765bfc6c35cf2f0c2fc55429bc6d461308e51ce782ea954db5915c54d349060203155ae637b35c73e8644b437da07f2fcb362df95893c05886a4866b72a60b1deef9c51022dd505ce752e4062aed6e9f53451a9129705d3a51b7e80ba286c5fbb28943bac40cb75c8bb7c5855f2127b646473a61a8f99656b0b6fd5fb360a2ddc1bad771d2dfa8c9440d55353ef55e2580e51056aa007c0b8c6eda98b739aac90065dd4d652a4f2f19cf7f83ec57aa818fcd6fe3aa0e7dd9a6a042c6073ab03f467c8ccf78df049e23777b6112cce072361427dd465605ed116bda8abc5172d1e01a3e296b89be6410cd16ae5cd44263ea676fafc033111bb5fead5536ed58cd105c37948352b846a1847642fd6f3080581378667e3de4fccea589f799b9c8e889060572679dac382c2c57252aa782a7ddcfc2b78ae440796c1adcff4b7938e4f506b2aca52d15381a6b2e129a27da6c0c68372b12a5c3b3f12c1d4a78e87c776fba62282be038dd6a90c5120df174ba2c2a1995e9c6430d96baf2898eb1fc7009091f20cef307df2d712763c2115db1b82a484d04ae5b9ae5f38d71c171bed64850d5f6c6e108de0897fb38ff720255dd5678f3ffba7a3740f3fa4288cd8204a31de05c30c6d771b5bea8508cb5913eff56481b96bb222c59a35c3a1e955c3607b998a98b58faa282a6d853381e9a0c344162ee2ceb3221b8f54e4402f978ab567fc0ef3b740ab1d02c37465392d483c71b10ae478362083969e9e077ce0991e30ccba20a31794395be862632a41d5eb72fee0011dc4c758be01be905e48331f737539aa28b6ce9db1564dbafaff494352ba524d91cb1d2b2c85f29f665a37850db44e0149e791b3e485354a706128d2a14d66e907cc7d0858ff769da52e8b89d7085fd54683dc87225f0807a2de6e8ae0aa2034d12ce63306a368a03007dc3037b2568ba2816d3bbe55b618d2714f092db774bc9352facb46b8ac90aaeb8cf9bc1644d82447fd2742842fccbd3386ad762117de46fe32b62eb4236482db063f15913314dc0d1c989420f782059918335beca831efbf0be643b4d7f5e92866b878219c39e486b90fcdc31eefda252d2954fbda51ed42ae734af6113aefa6ff1504d30db5eca7d1663e381d0d7edc3db978f48902f5a911d23f25a8ddf609874351a15c2599b09be68c226e2a8636e880db0f2dcbe7ff605d4adb1ea5c8160b193e1c1cebf32c48c48f696d66dc8f3ce7e0ecbb3de8ac793be8056eed6cea2c9bb4e74e5a371ed1155e98aa2a6365e7da476b8ebc4651e69ad44645fbe76f21f71aa8d957f7b55797422fd6bd8ebfa70ee345dec4a7f7cfb41a56488cc7506e56e95956aaf1e156ba0c391cf0e2512bdfff87aa0703bd1536d0621797bbcc24ade92e983f1ccba056558ffe2540b0e392d8800fbd7f3ad91f9ba39128a63918ce362e3adeb23ee7665f452f53bb0e2ae03402bec4488c9b3018bfdce7cf0fd80102d8006a505cc74d7a1298f591ac96abf686627237f358a758a3b9719aac434ee7ad2ae0b417d200320399ec5619f3dd557d2c501b6e620973f09a0ebfcc7237a02c9296ccac9a8c7f1c1089fdc7f1b6a629da411a273637f19b787c2ef9d00a54014d0fe83aa5915bcc41e865b6160d99f9859a9202a359a366d6631a8a79fd3e3343057961ba472f414802076d26c327cb2bbc2d7deba277b2a820effe4865d06fd48f558b10f79dfa21796e721bdb61a41487b26181eb9f71fb2f524f6d70f390e4cb8cab1f556df96bd15fb85d55c88462195a576a3b89fa242c656fdc5aa5d1efd639ab45b4c1218eb4e08155e50be18fcf8377d88df4f2d5823a2528d63510dd21ebf0b423041d8abf6ffa0916e74171b2eab1abfbf242d65f3b93bef626715aff7a07640eb1dbfa24708c8b2cbc35958e3e796473b219243e2da198844b3a0795cd1e159fbd14e887d29cc922d997d5a6bb2c59b0a255ee8debfbff29a238dc3a2533586f2dc9b025a0a04568ef702483cfba0583933ac749ba6e18f686ce97f8275738ff642f217a3bcf6899d0addb06fbfac15a608ea9c1064d1f4a7c174099637aa85400b82c6a19974513c0923f14379c62386023efcf888f878c3616bc2e045793b9d0ead7ca12e3fe303e80a039a2042bfdf5453ef5396c2151df0693c44802ef3f015d046743912d8bcdb97917bbb761b0f9106ffe241e9d08a9f71260913bba2ae48a188f0b41fcde5aba6208e7e1c90437cacb8aeca5435d22c57365d1f03fb60eb7d016df550b031d5414aa37d1e1b0136ed0e815d02f332a926e2cef349ad246e41ac48d5dea3077c67642becc15b9fae6d036b89a29ada144605d1d4bc3f2dcb7fc463f112c7012fab5dbded4426c96dc1663fc4e18d70c9607c3e555e203f353ddca3f5435a6f795d4293f656d1ffa85ef113ff8ca98cb4655ad23ed48b34167d7671f95ea4d24523a70abc0c6914aa8ed5c44a04e33a622298ab0ada4b795d420c3aec8fb86110272926b53105b085942f6f8cad0e7bd74407d0e95947911edbbaef11dbba87e94d4f0b2b199bcf218466f9415949c33ccee0a75d6e5c917f911b868fb98a62481a5b85d950c5418c83a58956cb0a7c58407e7d60ce02a2f71bee77070bcb46ad2603811fd72528a170120807dabb82d90fac021e66c383dd66503f46ee16381462a492bfb6397c01254ca8d6d4c6a671258bca3b1ea805469137e0fe52d3743778326d302aaf8a59d2075a93257d2cc07726cdb113658d3237bf5f975675e8a10f136d7da89171f7305a566a5dd94c0c08b9e732c222950f63ad4505be45b691c71a10b77de9a4b17a56970c2019b4cfcb1f5925e37e2a83c03a1fba1ea2b6aa1f1677f3d0be6b1d6c325ce39e6604114d1a462b03906f8beac7fe9f887cf178b3a3eade39ca6115c60ba954964d508e0bcc373b10761aedeea9f825c36227c373a852dc477ff78f09c1c417ff83a2b7e86798486248f2f160a1a55c6fa64aadc751beb59c87f0c5670cb899e5a27679280e387c0000577795e0a222a15701abbf6cdedc7c37e3a82035d57119c83dd7d0accdfc2453ba4ce2be1261c1a32642c4d48b3bf7febda9c9382ee6a6bdda5572216e47415520e2fa571858cbd2489d6adf3d94065d3ccf364319804c302836a3f217180515a346c45f5efb368d2aee58f693724e64741c00a9b3b05eb2717fb7b628e22960f1763a64915e224d42a06403cfab55734e47dd5d5a85cbc4f8d97fa4e6d8044bcbfb4090e2318cdd3b5b54ee576f605c04bf510d316c30e4713919daf987211887d878c3864eb19273d5ef2e36e74bdba70263919f0834a48457179dedfb66a28fc9757c3753df767e76476198680de299475380d01796374dfe9336b17a897ae0d2eca3b2504effa1c95ddd5134b7f613709aef3265a75fdf22ed141accca7e8c1eb6c03786d3fc58c09c8e57c72498a0519757face1ba2bccf3b8c61124bd2382bbc7d0c2e7f64d0c4c0aa44bbb060a04bb3b89d5a68d05edfcde5a1326cbf9f4a32b4fa59773c6d9c2373d5369668c72df2bf57591f60b2862c6e5acd093f0d70ac5dcedfa171461a91de6deedf3b9b6e28aea79cfccf98af79d8f1c68794e44ef2ead955ef9aa4a10fe593b40692f9b15132d1ba935964232e9d2d60f9ac1ac9a77d711f59b78778462a6f9aa1d7909fc13c5b63d27b608582f1bcdee2b5b9ad246c6e89e4c321ea6bee51a0c9846409290e90c3c87e481d0d94acc557ae5fc71548aef63c9f5839e5802af6122d4d62a96a00fb69edcc715ef2f29d778bbc3ac159911cb710486302eb12cc3d30758470e6fe9f362611b3a61ae64e8be7163508237d4fcff0bfb17f2cc0b200f5dec796b62154039031f5ad83c5064519c7ea7f065bdf7461c01db72e158c8ea1da68394ec3684db4b63aa9f42eb3e4febee53ba01884b4bc0182ab86910bba57913e0b558593cf7c234fb6bda60943bffaa323b725da93606c9d5739dcc6d8147ec1d531c9b248166b710801142005411386edeef8ee2fc7a1e5eaf83fd6c777f261a47836a2a68678ce0f0445957a1eb10d198c89dd706a7329debc2805b4d9902e9409381f07d9a5cb255f6949e86a126c82acd67ae4ca67030a2aaa928758cfca81927ebd40de4421886994a641b4886069505f129291381c29fbfa811fac7cb50f737aebe5e41ac5ac6e3e79924af9660571aa32d92c553d707df37aaae135dfbeb7e406f477dea43811c4589bfc5e36012bc089ad7861332640933ad362ee9013fe3881a027bd3023bfe49f6227448f77b4aed1c1fab38065497c93980070a200993314f69bd9b5d04145f5bb2c4e5a4b6f06cd73fbd4df717d74fef413d500133a5a0634b5c5882d44ed630823b016f5e95b538b4c03964b69e5bab7280e8517b9737f214a0b001420c903b1d6b6981f0ac86d1edfb4956a4c5e7f31115d1a355109edc628a63e876328321e043b18aeed6a832c620c71cb1010ba96b3db34b5cfec763f5a88ce8c45405a2d07328f0548e195c203671cb8db5161395cf397f4bdaf83944e150bafb1a84d671b2168b322d38dba16d380f6041c54fce1e56c3685294dd581a65b72a8dc2c79aea17ccbf1699767cf92de468d904aa27444a953e8f484dc5f083e4772bddfb134df6397ddd18ec1819cff5ba06d56da7e78c580f4f10876a8fa20c3890e1e86651e1171fcba7ce1cb1a3ab7499af32ba03dc3a3b7c2a7d659b30e2a31f8075dc433c2e2f8db22e1dfec421247a1a824652384f49f4ff597335927935e874e27e0083e66496c986fb025d040ce3137690e0c70e165422faa388aeb31092aec82c219635db0aaf6ac1b890bb42f9c5c8c69b06b72c68b31ee32d7568ee9376d6d9dfa051e3458151f5016d5097a5622cd781c1fa421dc7a691f44cab56d64c8c6e4cf1f2b203957d18f78d551a67cbe1e692e8c75452d414696cac6aef94fc726a7d24cf5e99786f1259360f25f4a041c201bf5a14b399611b6d677b98bf8fec627d09abc04bfc17bdbfd27572ea464c87dea124175009ce9d5c70d278342588371aca76dc98377c77780bdd3a18e7e4048f79bc75c43c98d4673e8d197752833d9db0f9498b67307e29de56328b43fbc99711a22ac0b3e83b6a2918007bfd9dc2be72cbc45b3fb0e4904ba335ae3948493db12e4c12328bf619287bd9dae238693ed23deacd7faed4e1e9c19e18eaabe0a8f7ac5559937891172d7ed81b7d3a2131e44ac0b033f369683f76b994abd2ede367ee83941238815e787b0a6b32564dc41b5de591bc5fb3684d4dc2d527a54a65198a1cb323d2acb89d3d04c5dac6a9a2f76d6648915e291a23a453b03f0827bb4f7bb834717dcfc6733554bd5c99c5656ab31e4bab0c8af14638afc610ed69f0536745b9f37bf5e7f3975b51fb82370373d2b16a08be8dc46bb069a91b727f5415ea3681dddd2b2473b8006a521d1b8e9c6dfb32e9c747654b7207b5d6066493843d428e8d3c0aa27ccfe32b1496fc293d4d136df750ff29374f963b340efad0c263d70ec59c071fc0799ddf4c4bdf9f571477a30f6a7fe092c5dbc2614ce40b6acd7d6581341e597ad8b7502c21c42e0085bbc22018f3ef0745f186c6a0409812811cb1e0f50e73c09b633d3e12863b98fd9e3b94dd88237574dfede873ac98d2ccd2677dbe8e124af8ac244a0c9bffb0748b97f4258a0012edff0fbd57bf64d74b4405caaa486fb056ddc944c747cb24caa7201e2d0ee235ad4f7e5e3e052091a0af90e86791262a14553e891a5700031e58a51c71f43aac4623ce2f648065d1d812e8e957be16e6c57c3c78fd3fd9beed55e82327fbd533124d2dc2a8571b839cc5c2571d2ea27a49faa9e825a2c4de4787330252c1512616b70e2229762de0ce2e3de8654d10aa24abd3dfc253d73a4b999f58e03238c042bb3a549e5c2678d2f9c078f7a918876071757ac18357f53c65df589e84c9c7ed875fba186523ed132e9b4109d798553445806b974c57656005153a7051cd276f4fd60270886222438713cf6ad05d9c69dd546a7c65a7351e4c6b94b3bb8ef3b23f670be4d26286e33feba31e06bd2c40abf76d3140a2276838ed51b4d3b6af6fead6eb570e437eaefd08d71ef23b1828f8bda79558a4622680bd678a0ceeeaa0daec1d4bdad8a2c10ddd8e4f976282181051b4f6d6633ceffd1f30c9e4ed90c857271ff342e327998f903101e32e226a40ac5a8381a544be494e46708bfbf20c60aae632c9a926afd0e38b14eeafbcaaf72c73561dba6529ec167abd933e6aeba6e4a3bf989e1e7f0b0e7b40edc840f0096f74602653b63bba4bc477af2c7857e963a1cc06cab6b675f423a075601ac809d8ca75d3bb9f0504e2ac86277938cfec318d9ced9519a8fdfc1a7a9c9eac71a1ca2052383d8e146b978af7abcaca577f9a4f121d28428b6baa0cf4f112d852f71b9ea0bb6e85e03e97464c6942761ff4a3f93f7d5ce1150f65c248c37aa5ae91a3644ce3448f051ca702e3632517f80946e29928c091fa5853afd9b0fb63e6e6ee17ba7f4f0ec9fa5c041a8f22743d5205d7e8c9ee5bcd4cac0ea2d3b99a13c236620024de838e8d93ddc6f61dc94e9f9596b614d67222fba3a1beecd10383dbed41df837cf53e868c571f3f257f40eed5519094564c71cd836cf8a9bcb9553302186b32e55b77896b8e83360b23becc966d1cd1e0a2b0e5708de2261f09777eba3e9ef28c185227e7de64eb0bc31b305475dba0e58ea2d10474a5067969fa148983f1e2d6b884c0bfb2b4eee78ec85b7d6f7433fa126cfee4ee640ee4b9c13589f5eed4c437e99312002d6419c0336d695cd11710e3cd2abbc088fb18fe6c81c3d0539e049ba519c5eb684a11393c7ba2a0d048f18c87963f450cc1557ea16c35a72a14e1212f24c2927159e7c0d622edf87dc0811006aa59e7bf01d8ba1c899c18d4dbe4a1c3aca7a6096d5a8de98b522e22af19c57cc1b24471e1b87de3e74a3a0998c2cbf53924f484a761fde355e06545e0d3177d1cb366870259e665fa3149eb48b0c0fee7b285bfdd89c91ef0fc1f91e419b820b3f478ec0d092dc5ce28dfb6d9ece7c65938c78e9a28d3fb5432cb4500079af5a9a0d789a3c1e7ba16b6919b6716b5453ca82bf750f94d17a10469961bdc02b66c40518cd926c9f66e9683b93c1738b1e81187b65fa0887aeafb1f4c59b3754db42ecc84b449f23c31c2e118dcd86df678be01e48037577c2f634d8c308f93aed62479251d84b893ae5c5ced05eda446bd16e533cab17e573c6373cf03ef77dce96dc7cf8edf8f316ca17d58f08b8a4b019db12589e635fba1b4ab0ff22746d1faf37f7b624a2f3174675ea5a19ffeba5d745d547727fb588419e9bcc5a87828abc264a75fc3c1f40dd13037c0aa069fc144dcdf603c5b8c99d8a0db5f99c2509f33d1d2d62287fcb7389999018207de3ac9734524644cf42b7071eb29ff3c48add530b458ac0c798745397e61619957a194da879c43f56465d35deca72203bf88d03471aeed1638f1452cd5d7db97d14059d95ee51acd45f4dd4d8c4ec8985cd3a2c4f6abe1b4fcc674afccf2641ff25aa36b69463b5fd634ecc223411ebcac3e872b38ee0d2c09f0c626d413487ccf9cdcf9c95543b838e7ba5fb4c42d6b14554bbe8bc9820a6a5e7a71a5c0f74c183cba151796a4eb897650ffe18952c07a84bb22dbc57a0ec82cb8867f7948771edb4e7fe05c4b603ecc25ce0f137a6b46010477fc3bee59c11ee5b69ae065106b60b65adb87e6d226fa301c563384f07b6222c9d71d0f1c405bcd7bfc680f76d7ff0de10e946233a757f191bd9b70746a0105a1e1172be3255f398fde957b3d9e2f49d64ea36b9bbdb04e20eb2680198d77d8a959ad4b9feebeb9fa073f0538ce6eaed57bb61c6156c383d8aa9ff453b48a0bdc5261941e6779dcf9a5557b9f8ee26a10a17a61537f25c32c5a91f2f0b67d9b5e6c2882d765440716ad522593045aa8d19f96e25e1b0146a667228ee5620ea79f9d8034305e25eb1c2a3e7bea18c950abbe6d6a8ca1d5db333b23a06085b7cd52c9ee1c27ed1cb1ec410dd0527c21376eb8e3b04920f58805276b6ce184a62d7513954d9fc2e392ec55e2160649ea7b2a993afd57fc75066181173f35a06f95460938a2d33351bb8aa756d94767566a2438ee06919810ea16bdaf1f38d2a91610b043557a9331978d078ed7bf1ed34b1b2a0f7428371518db36bd7a3c56b9dda654cc664c5118e8e810162832e298b4a0826b398b3604a2c593909aa2068acde9691c653a63881ff0000a42eeb369f60ea5378d57446f8a7d7abe703cf25e0509e49d29aceed0f332285239037ee15e71fdf631738959ac55c1a3f7f68fcaa58294c6ba29e68767571f6ffd4b48583582df570b1b71cd275f948b44193e216e0767996469f2cc776ec6ee6b723851631011da6aa69ae9d4a624ca340fa59c1184ca95c589c72e6cb4a4ef90b43eac073b0d0d73cc2ed1afa3ff5a1c8937303f3f8d3a91d8faa8e6ad638a236fe9a142243e2f85646f6d6df0f2519f937a672a86ca7b1cc5860fa31d40956b0eb7ed1c990fd201b6ec1857694aeb247877104315afaf4124d0d4f5f7c7a00891bdad4f32cd9696157762ae2d5a3abf82007a4cf6ccf3f908ca00b19837f03e035c3b2b363e88333480d1669b23375a92974f6848cda7b5c2fc88ce6309f746de9cec31d46a06b5001f1a4f6fe7870ca21055f9732baff7c3f4530340bc288f8a94adacac6a1f8a9e0874f9637ddfb1d0cde1214ce56176e83d4bfd10c40eddfbea02358e65a18e6d3ce74fbbff07a3be4003049381adea3187693726e2c2394a1bff3e8eff065e21a7041fcb3da7b0f653c594b39f127ffdd1d4a2fff21d01dac9e599fc585792bdaff381348c29f581fbd4d6f1c49225bae160f050d125e93173a65253d2ab9837d4832ddb9d3752be03a50d1547b8a036ba22153ddd731a34aa0f8298c0fae61ead00a9ed0c20a7067fcf6d03159d339d6a0bb5db592361078ead6dac4c9e0718206f1baedec88606e2b2b90a3c957b03d4783cd72087a2f8d3a656f62b50710f6b4e73dad7ce70dfe32b3760fabc411f5326b2db4921c6970183ba1e5d1e2d1e6b51054163151da1522a1b0346b3879a060436d9158e5bc8fc8302c81a378fa590260d25baa9dffa14c63ab200733f94ed24463e4d6613bbf751352d6ed8707fb8f93ac2c5ef206c2a4029b3365551565ee904621f9958967488857c159ea262ea4af7d949562515f864ff6343b1b4657e1f433bf44e86204c5f006365854baf286ca036c16309213ef5af977f22e78348c40a930d92313f30b542a2cd456d995bfa5a0595a20505f5c9f22103cb38c00ca270c6c4620fd90d469169c85c7ce34456eda6175346930caa9b41547e2d7de15f425047cdd16aa3de51e8bf7bc970f4d00db070ef48ab88be836facaaea20cdc43a0d052a86737cd061c28931b67aba036f1fd0866b993f882f4ec57a7a8abffb38c26732a8f6d394eecf7414926c5a5200f66cac3cc3e28c27c4f9f389b5c5f97dfa0991e3e1bd5ddf01179fdec1382754ba22eeeefdd35a0233aa68f9ea962cff0787cc0bb57b9c28c6cbf4b89db043e5eb59cad2253c6887e1014c9cc40da6ffb6ed3f55f8106deab3901f7014f407a2733d4d0b9a4ca32e9250fcfb0febc17a142449c188cd69c385342d408c3fc7a28c15850dab3b8c003725943cbc88bcf65bb355ee79fba05a6b781838ab06fc207279db60243b40130559f17123866cd2f41cbee6207dee36916e00d2687e0820fef4f2f814c62c3166d2a026d131ab6cd048b93336e28017b6c5b08ad4f253dac966bcaf9a860a120796ad1399ab7d16db87070966ff971d8b0b1b2a81e2194f34eecb1e3059ca4fe4e06bdc79e743125d86e21df7457ee02913c2ad056ae25174d4d4b0a2cfe5123df6637db70e8f36c82a0caf2e3ab157d3dd83094bb9e1cf5aa2f800d962e7d4649ee6e2d98eaab9ee0adeec99d6643544627389653bb3a89d7ccb3e1565f51e2004da2eb5e9e6d7b1f2509f2cc224c9287c5592e1bee4c7797163d4d506087b501a7a7362c752ca8f7c9200e042714ca253ca5e686ff568395a6976db972ad44147b8aa0ae31e42cc61d4454ac5a71909ac8b4fcd591471983dfdf30f596ba56227ef0b4d6357ef534a2280bc40a34fed048cae33444b0c6c8e198dab84ee4759bdc096ea1a583517398f313ed17833052decdc2af27d1bcc6352d83820dd370e9932dbfb2287475fe3e895dcb67f1c8811a68f3f4ef4152a54d15955fd1dedcf4d5e60bc05408faf9cf9d6eb3b3d36aeac51985e789a682b5401151039ef28eb9a824aaca0abe5a18c229e1a80e67419a94a3bb0102286a61626d4b2dd41222eb33716a2a51cc77130ed1e2acbc49798f59488b87e4ddccac96dbea5f9f4778a96e920b68740315b57a43b35c35a1c3348b74208981ef8d458605e9e6f3d931451fbac2e7782d5b6f319a2ae06b09d4ed2a1e59b5bef73924149ed92dd76636145f77b763273a57851d3d3128b612e5b361c047fc60217ebcb6629d66ec49325614f086363ee177c3d7e8457deda435c2632eb301f884acbd962fec7f7f4513291485c275916c10c82004f85da21782526d99955fd58c3c639503d26b881b8d8ddee76edc230bdadb2b9270ad7d309469ea0d9eb8b98646559c932fb66000977b26d582858265715a3cb5e5312982b3b31b5caf9a21160919d9d6366b256deb06981b6c76ed2ad6aa57e3266359d72b71cad860315ced6711c6c7f04dffdc8a938e528cf916f6edc70da51589d26ecf72dda27ac8d50e660f2370b75ecb6d361aada8b6c88337fd589af4262a83ce7326e1227d1e40dbb0723f38bbb1dd31f77c8a7eb407d6ca335ae019e5c90ddd71b3d4491b87825c13da375c8095d83d97ebe315c20e8d7b1e55022e6df75bc22527ceacbbdd9b900264095c5e09d2222658077a1005040534299947f1ab14c971b147fbf747e0f2137f9c350084419276fb67ea444bb9084c64cbae36f90f1c3af127761ec83c257f4a293ae609705e9fe98e7cf5b5280d232bc75e170822c4e950d7fdb39f6a66a686975ae39364e77db6d95e3198c2d6d7b160880067d7bbea72f405ec813c758815bcd89fc586492d2ab921f89dc9536e44d7bf74a8fc4730ae793d33966f9c4c0b966515f47150940562b1be7621e350df02fe88510b05af9a6502a94aaae5bd4eedd6167cb245d8d62136ed4f58f5c9576ca36518d642e1ad38c89ef692ddc8c9f4c8c5686730e4ddc9e5601f75e68914123ac9720e5942e7d9701a4d28e6ff29d2b21a6a0200e800e2eceabd54762356dbc887747d98d48cb437589fc5cf19312d2f5f1ba069d7d095facdd19f74910ffc2902fe095e9cf7203745ef7c546f95287cb599b2f4e89d8da64389a30cae4ecbfbb63ae69523706ac005d2b01c81726680906af8d0169c25dcf924979cbb34ce71b06f7eaef51a04b76f2218276f49faf437b2f994a017b5b28f52182bdb7ef981111a039425f5bedf8c8395c981916aae45ba85f189b9d79e1957e85c969e6ba88e5da8cec8e0129d1cb38c03a0d823612bc8eabb19dceab4a10934445a6221c2f3c2f44675c1cce110ac9f85da73ffa7ab43e26d5aef30c9a29df081f52d674d9b46b984cca8b33b1199fda5e2bfbf855e2d6b5f9ced4c968f3d17f91cc308b5fab68c76ec12f7b7f896ad738df1a31430e50c8cf1df07b74a75c9c2e89981291463e4f0fa32780b54477f3e56767599df403caab172922cad6ca82954c9aff10319241da14f40d9e7328b34dae4b0b97ff2dcb8f899b45e05f2e831c07fcb0a74743f028d2fb62929fc6c295d7dac26f83e19ec23469dff9154ee9a710ba5652a7dc629ef956425c461a136443adea986040a1057284401070f76560e736b0d7a957725fcce63ee1611b2a79970a3203911e90cfa868fd610429e28259e7d0aceb8b0626e4cb803b24819908b002ca073c9c7353822b08ec7fe5083a40d08e2220a5a4a015238f2df4a077cea156bac6c1e04707f2865f53f61deb6602471298ab22325f7b1ca3617a073e8af782f27b1d4f50e033b1ac7fa7acf4091ffd5c2aa0f319d9599101e8a292492726dc55ea11a8e743b821134720d3c5954df19fe3c328bffa46dcc1d99039436f225c46def6ee32462d20b6293142dde281f4a088eacf490f0b6979c5b38e41491c1e8ef23d7e3fff7df20583f21d66371be58c901b1f862cc1eba5c1b424058d5dd20ea1966a7c13d84b9c4c983af7ff9786f8e8b804f178d7b33c7d75d6098e9b626a680121dbfbc11de597a46bb5b6a6f5da53629f2e80aed4a24c3ad13c819d8502c5bdd0b12beb84a1010f87e3ed55fe15b9f94772323093567c42bfc3c5a5c77f88448a20cd3fbfb7d3a467cfb2e6ed2ab78e694ea3718a392f66be6f6bfbe1081241d0eda4d3425911d68322fcd7c693b062bdbd4ac614de4272f67f0053be8293d82d6a6c526c36cd258f2c0ee142d3fa3550c666a88cae559c5a80d08be0f462063f28894a73cd8e5e10f6432df41393d8fa1e660f74539444092314c66efdef725d5a45b4fc3afd87539b91cac2188d5d45c6ffe5de778d175b21c759494103cb2ebb97b3f66079eda0040950cfb3402e6ff40ba3cfdc7a0d298fccf8ba2ed40c743569ef6083f20a4de7f697a3b786b8382a98b813cc583fc85fdc7a28607a5a2576e2c35b019bb7e3aafc81006f950fd61c97134a76501459ec36f03d2e9e4f637fbd7f1d606c04942a6c5d498e20212e484eb33ecdfd4eb076cfefbda11c9318e8e105ebd30f3019a1ada8de288671c67ce7f0152386b319f994edf072ef21231818ffae38e0d7c2ef8632ecb7b77921940369e7e2d69392a1c5d7565ba5392457ecfbb24303befb42a1eccf096c1d3b2ac1c77ae3e24bd36f4ccfb6a62b41626b28f3e1c2c9c4fa152b128dbd1e74707e8c4612c44268e0bd163b60dea31fc89d1520cd52fc6789daf24cae7521dcd9189c3a81c9bb0e857a98b0f86c2b96aa782e0efe6654128cb0163a3708dae764cd43128ed3532968abad24868d9917c496fc3597c8e0ac85a42bd4cef01779b72402b7afcfad4dffa0d6bbbf2a2ceb4146ccbd4ef77120941db68cc1e8d6a34065d53334abbf1504d9eaba8b3dadec573ae8a8af3acbd4d1575faee5d110d8b32e04e2f42b39649be5a8d9ae8f700a8e6ffd9d7741964e46289f12fd4abf837e6c645edf4f708613c912f5439ddc7ffad3a51f1ad9bd311f7a62d321b955b713e5565fc4e6b3b987cbb5352b0dd9bd40c21d39717f3cbebf5bc71c86e4375a99da25f7821d25b5c7246dfdb12cb37979acd01dd2c438103c0d0e58da5621e7d871de8950d6f4759d5ae5da2fa632c785f90c2f7326c1de0f239970e74bc3ef29b1c3a3fa515111c9f387cf252225d0eb509c22877d03d490edff57fbc3953064c2392c052017f85aa9a8d741b1d814d25b9efaeb4b86f6188f45409edbbf08c21fab084d9db8d0cdf32067d926aa5ccc103fdcb946eba905a6f6d9caeab385eb41b5ae18147e9d091ba03ed2832a75cadeae3d87a50163f51b3da5d66c067d5a640d9f341f1767d06fec0a2e167bceed1475c6a0e96ef5ea8049d9d4bb24a9f4306798d75a7cafa39cd207afefceacafc6b67853539a6c7beeb9a5f143fc1d896c40e30f731aff1603729dd1c2c8f16db66c8e3707b1060ea0819747a27447d1caa6a1aacb0a51dc42586e42369d8aa90168548cccbf518b7cd45201b925b255e1ce864d5155f6fe4950408a6674b3be35969e4d46592b71a30af56738e25151a2ee42db8e942ff1006155f989c3ecc336584ca47de177067b3cb7ffa48d29acdb440d66ee5e9cfd8225071819a5d79c047a8746af62a50071271d29ddf29bdbc28e14df51aa3600267ee9681a9288ce823ee573f7b63794a6a5e3c1eac033ad1033070c250f30ff32a4fe9d60672e26c7a2f8908aace2bd20fb1da1248ced4eac49a7080ef0f4eb09373c3be49e3a4e62d8c71ca8f6ca0620a6e0292840af57a161057b660629fe4ec82d2d685774dee90e792c01db2b325dafed8c20dcf9485d8888c74050b6f923c45b65fe5fefdace6675cf6380860a850ad7cd978467f61f77a7756fc5cb15e4aaa3103a09a26d23af80e76df4b081d2d2bbd3f092cdfd10963286bc65b569de743ebff64437c275bc9b1bf994b92acb10c64ec8f521d53aa4616d77a448c8900c7bddc89463f3d0acc167ad34e51e096480d2b661f0fb56026eb40a104d253e290980591c2d1e83739ef8f628bc06e99bb8f56ab0c755cba8c605bfbc0d9ef2c1cf0f3e52a9e8f290bf02a78b00df55da6f5479b27c9c5e0072c1e7e4c3bcc1a8dbec4fec64d780181063f735d73678d2c057c09915d2397a8fb7f7bb5f6dd2329145e6d314f9c019f9c9790a76342dcd65831cb00cb30098282c385b8c6a984da3786ccdcacb33baaed98409682c625100801213eaf953e3095a9e5484335ddae578edf8a33751db06a27ffa670c9e3a9dfb3fea7bb71574f561a950cd409ef58c972add2e8446dfe2e67e64a65e512572a5666af13ecefff721790de72b37cadda92daa1d1cad8a88445d9649911023109cce95afbdf29666f09f64d3e657029326206b5082c2c0479fe0ecf81eda3335c68ccc2d500e52d03bae51cdcadba68bfb57c2225e0a52f8110fdd6d948b7bd3a482c95730f9f01868d36c1ab65cdad3b8e1cd1f05ca0862e5019b97db28314fc741195b1fa4e136ccd31e7c840325e3a82f0fc6e12f82ef30db22f1518f6e0cf0e3f1667054f99d21537ecddae7f85c92997b43271298ef67f26df8dc0a0ade6012983a44b134b1c38c763c6f2d951a22d445b05f51fd0f9e11de6652b816a2a087fd94141fbe69d24c994019c4193e9dbd42add6d14634cfdf754c69e75ec86f95329bd0306ac5aadfcf7f4cb2b4280642dac2301a67bd00a96ae6d6efb6cc2ac061b699992e2d03377bdfef043f7e31ed6414c9d8e900f21c76e6a452605e6eab766eb779e51d693b64758e1c1719f7b9a9cbad9f0db5ea0177ea5fcbf37363747590bec25c684dd4eacc87f72134b83bd46f97c42dc134f7fc13cc82767bb6d8655d03c639336b0773827546c611d053219402344d7c8176e8badc5d65c5a7a9251f0a3aa0b9348c6e7adc16c870bd726c54abf4b51b19b0323cf3467c47caa05558e758a09ab9331ee1bc505d279c9214e7fef6498fe0ad2489de59aad495b06ff4811a86a25d91533a86417347c30d68c64616f3954f568a9d2a4240c6e060b0199768b8e7bce4dd0c33b8bc4a624dea968bd70884c04096c53b1e73975ef8aa07f962cdb45259f94accceb3c6edd7fa7f7d4c41b0dd24da86784f771b1f3b83785bc9e3a98e6a881cf813c7918c7b2d8e10cb44feb4d4feac576e153b43ed4b207a9a7986e2d3a4ffb886aff3c36aa64123e3dc22061b8b46d0cb16457e59014d9366967291278e862f2c973d5da45c695868dd78fc594f951f1ae8b54a998f12da6851a1ad981ea6fbd551fde54ea4ff0c4b535984024a3d3f011956e672af4dbe64fac1fb6522865bdd471868c6ea2c4b7aa48fbb3d50e0c72b8b229f26c5be87baa1a53b77829d17f987bf1affc36cafb81bfd0b6a88ae74288f6135b207666d230e4c3a8d8a8d09e467586f8ffd813622b0c8ba898dc0fbc41d99decf62ba625289044031f1221290037d1997a26a6589be70b7060bda6876ab882560575b24a7e274738ae0b030a46c0f27b69145e5f78cbc858da5ad0947351c1ce67e01ffdcdb6c9ecd2e9ec9a2cb2a1a32d6b27c3e278ffbf7bd20d02654b2429700f2b87f9cf52f5c6370dc9cbd2ba694d458319db6ffa83f0bb67082aac5a276f3727df6d05fe2494349d785c8d61e441935e3621796567a69e68310e61a08371a13c9489749841385203101f6eb0236f9738962221b2da2c76e7611cb9f677172b7724f436cdae83b7c554faa9f923d86a9976b4358cdda935a283d7eaecc303c82496ebee61a20cbf4c04bd9497979977d816e3495eea4840a8d7338bf8723dfc836eaf4283f3387875d20f284deab8fe32db1433e61d75ee943bcf185ee54560c25f395921d7d024f4b7708436205f92324c1d90d90ce40c9aeb2572a9dd92244b4865615c1bc5bb86ba036a4972299b632ca13e9af9f25c5a8be85cb6949288655d830b01b4d366f798a0c165cad3d55b1e5ae0fa5b9c11947fa7ff48165e62239b100d15aa7d05ef044811f68023cf10955d5e13ed57549be2be673668c49b9fd3fa9f73c051aa9754160a20fea26e7e08886225a98bc3a49dcd031d3e67b28cf766ff795fe71c0fd6dcfd842071679da892db9079d0ecdf700929ad3b3837137269edc3990555a92ec661928b21c8a908277ed87831d761f4708f8107c45100b2812ff85b5a040370cd50623f6d6fb97c7940b602b979dfdb33d387b05e39443af30ea519d60ce4cc5a072c1c3d91a22390dd3f0213423807a9f5f82773bdc0d347d82c893803f2170c6944336a3532d761551b30c890d029d1d8e1d1edbe67ba5f5cc2409bb432768a7dae076ab7b27c6569c3329a39bc12b4c43b6ef8b2a9df75717546cf9d5eef51ec3b20ff45c26fff22dcd2d406b9dfdde794dc0322349c676f4fe5327203c67a843d8df0657901784b97b5fc32f9a4e0cf487c6a2d006b60b37f593d9876c1e7b7c343bd6de145cbf2284f20efbd5306f1482bcc04b187c32098255eab97d3b71c6f703a0abba9afeb4bff33e1c1e94d267525a36c428ace99d922f6f862c47cedd0230eba940aa64dd6894ea1733366298446773f075af988909e200454cd83885bfa0152314bc3f25d5d4f3b00ab971a7c7083ebf711895a8116bb03984e70efe2a13b783e9e28fbec5974b02d3e76fdba1897f150b0240165669a6110088447ad5e83295b01cd4bc609a9750505c33c8f3d7c6781ab7b717578243d2580335c13f2e2356ea24f073a7ee5f24e331d216046307769c637230acb95e96782101cb18411be4c29081c8a42c705dace005d6690ccdee8bd8494445d126c05e0a718267653bdb01f6fb9de16ab7c99f41884f4a9116dbda971c7ccd4410009dcaa066634af7d7ff145d04a65f29e8db61a8f4f7e268cb12a51bf9dee9e946eed9f41997c9a1e603f9b9ae0a2cba37edc345817f442b21ac00606e28cd24a05c161478da78d13a27de510d60ec9378711a0213fc0abeea7dee02ae0f36afa21a5e63ab5e33ca47319f372b981cdfb6e48020e966f9b6180a2383af70700ae21c92560ba7e916d689e5cc4b53e5b04d56027a6dfe872c2b136914dab9e5d24bcfa72a49ca0dfdd4f967a4ce7f81c5a2a5708567ce9d1d8ee4b5afbc1349d4f26cb5412631a3fb85b1d9e16c07e957a7c8939eea13e06049e38cae26e9b30d60446ac5e5c2a6096966470707cbd3ed50fdb9f2124bd96b4379c7d22906ebf942ea4005eef57af62aa9cea6ea70e054fcac0bb09cd398df1f49fabe1fa6561163fa4e48bfbc81ddbf6024d67af01f8c16d80f0a2450489d2cd2ec7c9d7d32f20f972b685c9b26d16f80ad9768a133e77a22ced4b8ea69106c8be56850fe26028794d6e8c8bc874fafcddd7fa6a06d5b2d127c44807eb174f9585a547c7601fda3747cb076ec960c22daba31684cb6c26d0374d42096b91018232b245ed371a92dcddf0013890ac7fe281ff32de30b77e587425c50d28a187b3876837d5cd56e5df4473a77b461e9449b006783bc5dad4559affc067f1cddfb66cc4dbe4f42918fe92e3cb569dd9385ee061570ca45339fe8dfe098c985d195f54f67250325ba7793b2864c32d4bf72e6d3d04741b7133bd1583d7e9350adbf30f0240e76f5ec02d7d510c7862743174aab1f45c5ecb39ee172d465300ede0af699a0ff06a40c570b12481588de99a7618eb649db4803967e5cfd6f57828748f202dad15725acec6a44c24760aa6b845be65d12bdbc513d35ac0ece2ed91fa6e1f2c69634422256463f87da092b4693e24fc806386591cdce232502895d8667909a29a512373c87f23b5b5851f8c5ae1a85ff75838b79c26a356ae78c26ec4e6750918e2697dea75377b1a6303ec90f929cd823edd79501b5e7a1564f9c7af0a752fc1cc3a2272ba31399e4516f4e3123c76afd8df3ae881d3ce7b20de54dab238a3f5bb17e17a9ba4a359da1a307e406a24d202a85e8aeba94936e246048bd5f9d51b9973a5d1d327b303cfbc4f613338118243b5ab4ded2ac384c4eff61b9075971ae991b27aed8d057bc2ca4edfa0e0ca1a71c73dbf2a5ca80442938177759b0ce03845a42b74734a42c58de320f909433966c76260303f823b3d698fa22c4797df995efbcff791fe006a6023a06debd142c66133251e422211994ef5315f1f678b2ee0f404df80fcfe65e76fae479c0dddd18545f6212b7b5161174ccdf7f394ddc6447b9e09ced4abc18de9ee8e87d2744b3aab30c2080d2c83cd76b5b035bbd38c6ed922439872189926d6d8ff3dbfb76b7a8c23f8f051b615de0f39f260f7eec00e774c85154d7b6cd6a4375220b318e60c0f941a99c86832779640017621b286984e890aaf49b000a67aaacf9b9450fd86eb3d9810119d5d985bc37dd8cf29c110fd7d309d7db3cf607d263af3c0d8a7ed024982280f157f97dc4f31306f61ff4c447f2a2d312709a1647202ce0c71c4a684d8e6512d746a1c6f872d254fe978739818841ae2d2222b33a9ce072a9d5eacb881fbfca9c4f7d4e0a03a1106581aced98873a6851ef9fa5cbc507a9c2303344ecb17c350efab63f3e49500f7c6a142ac41fc56f69a7fa408ee6cbb248f243c23b27fe51f46f14a6b30009c1651cb01f66ae1874c7801ea8c56b009adaf8fb1d529feea21fddfd6be751cd6449a8d3fef5d6837dc0cf2a9c56017e15117f01e13924f6632ac9647503a1e76b2456606d529ebb8b5534d865d4c24f2149e641888475c2f7ccf22be8418c8d9d27db677c1103b37d26988e7b3ae457526df23d5841543993e47f9adf73dba884b6d2f0b640ad3308812123d99ec8c1476b930a9e8bc336fced8fed480ea10732cc84e9f5441da577f32639db5c3b4e569d01e51aa7019c2d6a920fcc0f552f57c41eb6c8493f67f1e559f2710c3772e2f09e7415f5b15d194c77c027048b0fbd214d12fe014ef835928c5c8d7ce36ed1512cc6e7819c99629a11af5b46090de3a7c6e168f891e4dd8fd6e46409def5fcdd74e37873a71c5228d333567ce70d22f3a904a5aedb566ab4c23b1003b8303250e336fc1c3cf3b0c420bef35ba1305c6152e25b1f0cec6252c35a0c2884cc52aad93b227ade63c325c5863595bfec17054475f6e734eb7fede34d99bb706611c6e0778256582f4bbdc3cc59f8047380046cfd1fa0d97089243f0447ed00325a752d5923e4a9412eda5ceae5d0ab6bb8bbaa996aa890bf8ef600354642fb3965e1b391762335d87ac00306e086f2f9bfe87b79c3966617471e1f834e20378b127d0b7d2c6e667aa651938ad063976e6e606f3001d55c3a9b094fa7daed0cfb9933d84cac9a25356f87fab35928538bdf6929cbef6bfc3ff18ea112ca7edc8deca70f37123966ec3b3a804d45b856e01620f7788d93d1ff3bc79f819d207c923b43d018bec67202b476bcc9a00fe0887e36114dd321e4f75c37c7850da9ca34c7dbbe4e257ae247e2a84820846cbef1abef031a821e4ebbb89515dd2958100c682c5b45cba7895b7401316d2805850fb7e2a2503b8c7dfa989faf3c5315d12c4dc7b1cfc76c4c5965c1dee5380273c9ba00158e62630693c309d1b1ebd2b9d1184be1fe2a809ecc9df0a82ebd64f97cb6d69d5dd1a3feda51204e624ecbc4c4916b7ef6df1ad9cd3e32582bb5df856a3aedce512a1469a005c1ec88919335b73be50cff3a48f14353b1f909c60e00fc1b9d6bb3cc44cdac2e07c5ad5bb878741acb907bcac7e53a7208d873cf5d9d4bf7d58304ab18c955f00d5a964c0045c63d1e5ec3bd9bf066d996556ae9e4865f9f7698fd69bf344bc6082174e7fcac4ac510af2ba0b8b4307678565ec84a8abef569074bbcaa5d4669a02cad1454df6083025ced5928e11d7e32c910f36058cf7b75cb7512d7856b51f78ad37c7d3a3734774b091516ac5754d7fb13c1d8202ff08dc67339a7784390428043d1ef985728cfa84f1cc322bcece05b38a99d391881827db9060601332cdfd86736b2f31c9e8fe483f08ef8040132c3f63e5c3013647d6c76a2527d5e31e06163487373c61c08c6f7b2c1e415b4bc2f41fcdb06d7c9f8572431e71ab04eef5f806b0aab6a053f255a45330bdf2527c30e6890aa35b5155ea189b1a4aa2d79fafa39873efa8c044c8c7740988a117726503f4d7cf32b16d55ba79afc92e657e52eaba518b375ada95105a82b0fb75a1054ca632810ce2744dca8cf5099d4d3283785f59eb2368cd3542bee8cbfe8268fa3e9ac8f1fb44194c003960e4b637fbecbb5b72e3b22a7d7b542b7ad0889e0badef3a59a865a08c277b8b9c2e171d4155a422d8c03a270d5b21f8eebfdfba349ff8ea3d664db3029f36d4088d71300d3c57669aa0d159e5bd56cfec2194fc6d93f87fdc65039234f7f55f361512158d0c87331c1600e530671e7e924e4835a6455c4435e9cabae0a635456dc0be4894dd4f219621b23d5ce82d65ee8826c3b5b376a65818544a4713f65727ae64dc17c2bbea9276e09e9ca530cb87e90f0ed1f529daec2f2319ae5c714093363bfbb1a950897212acc569eabd7bd518ce3217a7a258aaff3ed4dd3a0e6114cd464b20d149b2e722e693fd0d4d92c4d9d16b06dee757dfb8c054a3e921b0c0b265e5c5c25e6a7b3c3e3cd6a7d90c3664486e02b5410aee0c1f934f2553eae178507e28e7393e09e1900839d429c67e8f9141d462b8b3cc9b4e943837b08a7853385d72a500903f5610a0ad88e8c3e5cc600e28e32d6a82b211983712cac5b53bede7f2138f5423cc41f07cf8055928f212020d2fd4a2af5911a1287f665d80e2b5df9938498d8bd3088135091079f8d302aa7905028604b499286d599b82f47243e57dd9a3c5a3243b092dd774bd79b0e619c343a860c6ac13716adb6424de246b7d5addc6dd6bb5768ef949e109c36980c727cee9704ff695730afb08f9429f8f9b0c3ceb7537203438f5b82d7164902a45460898b98c5827d639d6d948d72ae4a9167f56602d5a4f43c09f8af8dd3d0ceca3dd957e804f8cb1672f290186b4f3221ed2159acab98030be62df1a20bffd15275a638ac0f9b92e0bfa7edeee9f15136ea3da6c5e4d805d95238d7a190f68be9dd25363f656482d1ef7648de369ba4375d49a868b81cdb5b0a507042221a940379d30255fd67d359c615979eb1aba65447ad605ca17550dbf42f6df8cfbc671075a4aca4c35cef107abbaa1c0890e4b122ea5b3e66c6f0435efcef71da27b8287cb3a4da5ade64ce347be34d06da323b9edece67e0ff109c94e1d7c8dd0e3ed123fd9326174a11a3b8cc4e0c04126bc368c64d687a6472df5d8e7cf08855a38688523ba0f887ae31b989bc4e5f5fff02cb24371c827ddf77a0de578af5a4b692e7b1d2650cf5f03135483a7c972e2e2626ec2305d5e3b72dab8723816b405fa6b21e9535a87f6cdbb7d04aaaceb93fd4056cab635c1a9cedd48813dc8b5da6a0d4699157702b0d329c4ecf00001e28c56367b7894993ff00d31e573be1010cf62bc809884b647b47cf401e4ecb2e66ed65265259179a42a395fc051ec9c71679f250074729b842455363e7acb23096e15389bf69902f923f6bff527b5c38bd25d4914dcc9d957ee5958a7b5aa2a64f00e1bfd92eafba1b719d3d8e671bad87016fe26d920c9b2bbb02c13e1cdb57cf5dec95fc019b9ef153e508b7cee8c6a5aaca1e454a63eae6d89f56a5a2afd71ce492c71ab8dd0db2144dbba6eecf7dbaae32118429c2bdd9a94e13fb4ec84119834547c2a0122573bcfc8b015a01392f7e294cf23d98be4908698c2c1f1ef7841291e6f01062de239c5f2e5ea32ce987bb7614bbe45a4b2de99d76fb04da22fd5d661c3d77eb76958d29c8cb13f36a4e3d0f879ca5e91effd091e04abc533e2cd8eb22d4323c4a138e468c2db579500ac821c021bb625812b986988a155b2297bc6af3304fed858f1af5b5c73805a7bb185b91dc072183ce00a038abaabb408de99e50b54f0957d5029f17ef9b1d48eed754fc75dcd831ca4aa5acfa1a93c08f0453d69ed2c9bd0160f41742458bd5a5553e9fb1593b39f27c4f41b61e7691efbacfd134cbbbf7cf85f33721fd592337a03a6131fa64319a82966d5af2efcc883d45b7279f7970f9bdcc442f4fcc2d2d9ccd0725c7d8e1da5ae609baefea5054a679b629e104c8c9e8e5b001397a571c3db891516a06b5bd6accf24d4092f121134bf087d8abe92fc3f2b5880e2465956bffeb7392d3e7f228204f468b6661e31acc971e7bd02d5adfea9040f0d02b165ee329d665653f9100ef3b34f9fe65971668d2e4254235593a014722dbeeb95fc3f583663e190c1d1387e500013a5074c9b0d4824ce3825163a03bf3c8c0cc5d46fb72c82a773727f68d41b8c94578189aa6e2f3000d0a3cda80d8b1a856b62519c2890ea155e55b10bdc92395aa5541295768b5392a06ec10a804583a9e8a676cd1b2cadae363acc11b092c6cf29e4f3839d05b67d938589411fee366ce77ee66731450f865634c2722c11a8114736d63cae78c1931958a476fd795638c96321b9624179c8c7961aa48aaaae74c8db8cb0f622d8f7d1b6d7945e4e44e937fa2972bafc0532d4c92c270e964ffd5418007008be7fa080e356c9ac836e4f403f31259dccc594e3d371f9c0d371e7b1b66633099e02db5439b5097de2cdaf3c4e20cca1736655aaafb12240a50722d532ac53933acac67f27628ba3cc90438f09ac42fa8717eea7c99a2a98a3eccfc723ea4d26c300d61a78884a58845db52c301ec86b0e316cf53e039e310479c5580f0f4527a159525e1db0856b83d3a8f6a92681d74b58f263b7ac8fcddefa109f11288071093bd527ff6e3f8ad4772fe614d7f5520f9d35ebf4e4129286ce6c4c0246e776ffe4d0d082cac092a0045406dd6192d0f64ffe6772c6c126481fcecf01a28af06b9c0555c7a812f96128933e695526587020acaaab2d38e11e72dc4798781b1f29530dc9cfd5843b701d5ced26d366cf7967afd5e8b0990133fc7045d00d6f9b26c0d42016ae2b67d52694527eb16b5261620e0ca70909ff326f195ef76f01b7065d174640755cac9feb14caf98e301c0887e26ff86730e4d955998eeed96f6cd748ed7ef1bc340321de29d6299c15ff6909d9707519bdbb3453686d3409fbdfc8a13b24f20410e0ce5812fa0f495f6d6b20b46a99c6e00c79972f2d13770b7003823ea827eb15f8b3de86641a12b5113096c993a44ace677ea54e6f9c27a955ff6d06ed16d7a915a6f92b6afa5fa5f77bb6813b5dd341022d1458e65cac03d5ffacf5eaea09f8fcb18420d0dfcc1d44e1bbdb584a87f563734f92c8f10becfd93b643a7396170606fcd74e6e228f27dbc72e768416a2c23957c7712ea7a59fc3e9a7d7a01e845e52edbac72a7465c6641e74c9d61992bb1e48d36999d35e6f8a90802a79886ee373457416a82f078611a9ac45ad640bb363a4232ede3554f890170853f89bfa6306e051f7dac65c56e5ce59a07fbdea980b944810cd532f7b0606f8e5d936cec075f7928704b66a7b1d1f1332aba8146fcb8e16ee5fdb364716bbff9a74fdc039b26b7af475c24e889dcda82d741ace7c3dbcdf8a8b204c0ea7a9075292c8672c0e0bcfe41c866323c75659171677c66bbb04c50ed683f5a0cfd480577bdc786acc984d0a12ecb962a04fdffb554789fbc689c5fa043fc23d5e98083269ec3cb65daecd7482d5a089d1a7f7d1c5826f4d375ec3eca3a2932247011189556879749cf1437223291e0194cbd5085f231ca8c254dbb1f893540193c12c6fd06137fa0ca2f095d1f9c5a4eaa82d2377c6a0a4e88f6db6449652d05f9a7dedfcdbae042057bd9764cb81787a558f130fb58a1c637fd529f6d1579eaf0998ab5249f574d3f072916aea19ab8533dafc10484006ace0b0d197ad3cc8debbe44faaca649e5e246ecb42e7acc1760fba07dc4b99cfc2100a932ae6f6e16af735d8127795c0d0bdf0b1b82d2794225da6f7982e16f30935bf905859bcf3ec477771c9078fc89e00de4a8fd5ad1f7b3ea59d942e77551044e83d8b739011560bf0b92979ad7d5fe14c6713f4ae860c84cb0af584d300249c3035e2c40394b9503e4e3f5516126d37a7626aeb635e9ef64592b06d125082143e8ca78ef51182f1ca2e54e6c518027a2accf1bf717a761234f1684585d2810a4d2b6e72e49a8d7783923a3e622dc4b2b9758159e4b544b291ea40a12e513a5891acf250fc21c6c616f17873f935a8fce55e9a8ca77d694d06890b5444e6401674a5903d1811da0ff61b29abb2889fbe37c11ad58a3575cd8648d5dd52da8b446a085f350f2f85296fe2ffb65a6b6839894c2d2f1bbeeacbe5a02c1ea9c7f3ce990913363a35438b58b3ea9539b571ae59b1a0c200ed8319e281a966db7c8bbe33fbac37fc46db6a91d751dd6d37a5eb87ffe1149d23de8bcc021605a20dcba4809ff9b18aee91be8221a85d6e44dd367a78412ea9d9011d7a73622fc7cd9020dfbb3c61759368faad9396188a4b1d3696b2fd281ed6fe89778454930f403e6ca59e44a4ee47a598dd50c82ffaddeb38d97f7c956a222da9f5f5867c5b52e87be1a85101010186f827eae98fae019c363ec260cae7f9f46ee9f359adf7039f2d5677728d4c5b857108f23d960df781f4b9f02fa16ac2509830c4eb70edb9c1778205b22748df2d77299b0668691b68da7d945142737654b9292fb966b2d07fd3556315a901476fedd7f808448a117c715356c70608e3765b3ac0f19a3a608b5f1d2ce67b2aecf76d6f89ac477005fef1044cc9c0406e8ca09edb1bc4e05d0ef268d1e1a02d1b7434c112bafd35fd224600c82fd44580c4c857b8ee55b10f736e93634a8c496f342e7a8159c10b64e5ccf8529ec981eed24fd8e54288c105f26dcaf2e02693cc51cbff2dfb8fdc06cfb2a1b6924015b7ea417f684384a521d34e290ea6b97a5ec808af5e574e67cb5e2312f4c47a7f0e768a693905128a777d97ea384cb95d837d32b0fe1cdb7aee6d935966d7f97289e745dda46355ee4f02ef166ac55557da3a9636dbe36475b47906e18f28f492d0a9e915794198663654ddc8cc681a9640bbe5b95c51256510ca397f252d84d90c21bf925911fa9813706eb7cdf6f7bfe6a35afdd0eff919dcc5b4e55f816bac5532f3f78df2d651e8cc43cd16d2a48e2ad5e8ff14964df120619c8e4a3f4e0a498309a135a48972097a991d8e46ddb4a104ba651d40ab1c9b192e63aaba276eaa27b79d212bf7ef0f7351c1517e003930fe262d01fab0c9be86c6362021684b63303cf", - "0x3b5944461642e26b9ea483970adaca91078dc8826c991ad82f55a4db6fc67958e37e5095016886dde8b5e898744a1d31950b3508ec98272558882c04a12d799872c0cd152cde8d36ba9bbf177cfe74de246c6c52246cb34859d5bb381e7d774bad777a4e4578252e30db080c039b75a5ac4b24670c6fa97dd0441d882224511bdd45ff3f2f152b46867627373d655062e602dbfb1208cf40bc8a462b796339b44f00b7768051c8019fed1dee31793022d2dae1fa97024787028f942c066c2332d4acd72f020ac15081ce3aa13a5087273351c9166db935efe985e0c93ecdc06d428e0e9735ae1f84d7d596e98997f3ef116d61705f11d09f9e98ef3daa336f68f140e1f1cc62294c6e9f06cfa056bcd63623c0e7aa435f2db11dffeb6977afe73ee765262cf4c392b8e661ebd8651a94b2caff8c7c78031d96616fbeabfaff090b66246bf098b7804c57420adc34cae658a1a7c97c313f9326ffcee7375834403db132166784769f63ed9be62ce19f2a3e60f0474184954f1bc8c81be661a49f3d28de71144954d45de6d0b19b9c177c41e05651d924c556973db6c3256fccf2780274352e0b57e53a412322d2631c4e7ece2e5a44406a949e98d8a877fb3847dfcbfac01e830c5322cfa5e0cb047f04da7027b4f726114c97627a0cf194d7f868f1ee8203d00c96ded30e515675d73a7c7af4ed5ac70369cf9d7b2cbd1a07648b5d54ff311a153a5c360ed89779cadc4df08c3a4af85b9bf57d0ac1974b0179c524afc7ad05e4e32e4109f7869aed197f1db256989324b89fb08a1653a477641842b553b3f4410a0bf35dce6d6cc38c0398b0c787609301c435ed04558e7d628963cb12311adf0d4ddf8fd96e258aa111558dfa52cea5d341c8fa2b17de199aa1c69e3e495cf166729e0003fb83d666a4cd10bfa47eaab054d6528aa7a11991ed246eefd306bcd29cce157f5c053577fb3262d61ea081d0240a21bb263ffe607eb0d3b2d84d6bc611a4ee382d6c4b981ab324d1d4189e243662cbe44ae320576a20226e18a0edefbb090bfddc96340147cd4845d080113af9d91aa9dc3f651349cda6a585c9b438a85186f82fa610f78db603788eec2c823ca2a0353b9a158ab8dd625ac6e588918b75c795bd7303943cce9ef47e878051b2e959e9d829c286da820d90b522d7c939e7c83970581b1a6722112d86fe43547535d4e2b8c63d6fd2447135e15226d82ef159e70943da286404952d7dba714e80495430f5e7110f4a5f76990d509b4406a7fb579c1e5b8950adaba0b1ea1ebd34a1c1493df20325ef2d6ccfaf2db7d904663bac0f894d0a89f240dd625fda55d47758f251d68545f2a86365723bb95a8ee5ac6fa0d94112320ae17c45341c700961649e88ff2b6688f1ac97b4d74ec2aed7e54e5c77ff23b25f3204014b101c5e7185ff4992820f6beec9bc09127b7793191d88a97fdf8d3578257cb5954509c4f6416894fadbc358f867fdb61de0f90e744fd1bdbc65e229e78c7670310c6710c1e64d7acaf2a28f96328c3694d4c116ec0b2924ec91a5c3520fddb8508c9778faf74da1e878b3564b9179b987ee7344a349c93e55a3cf7888a51504f6dfe51a90b4c2c10eee847670a2efa065ee125748e93dbeaf486384a1167c427eb96e762a6e202998570dbc23051b03a9338eb217c4722c5a765a663724ec52fb6012eeb5b70b3aa0b3bf7a4344452b5d25e6d96b6a6764e05c9a62a8581c885e12964d7bac56eb7041d9037bf4ad18703823f8ddf31815d23974fe6ba9d52e7306123dc8d1d94441f9cc325fd77ffbd47d875cf6821c43e9fc7585775fa800a9b068cf27556be343cd85e49a95d7bd76e2b5f76c5e57c680fbf287b2f79aaeaefbcaf82aa90d7a058ef1792be23177fa8a05e0f9c13bc95a2cba577bed735ddc8e3c0ec1c2cc4e1b8ce757ad18bff851f5dad1b5c8f34bb7a5e48c3ba1ab266e66a24f2079a7ba21510767b8a80a431ecaaba70a13eea1ba5784800ccdc7968e48255a3eca50aa0a0a9d964ce01c0de28745e6330a52f2233d40d46b39de2d31e198b3d8058b21bc7cf2554ee7bd4304720a334fe3fbba537c98eaffa340dfbc8e27440d215db3fea2b41a13fec882a26d1bd8fcc3c248a232a26a6c0ae0968629bad782b4680bbc443fddfd508f9d33b6d04390aaf53f06e487bd36cbc9ad1b50729d216735668d2bb19c2bf3211bfa937686d0fb2d190bc90095ba4e3d765c0215aa5bc75c179ac9a4d72fd2fab7467efed948d8d09f17753e632e2a3db66695727605d51f01c66d4a14e1b4e4de8a533814299bda72048f85e0b7ee2a24504ab05dbebd1520305f6a5c359732343b913236b624da860296c3859016f1edb011d720e98d5100017a86bc2ef54e0f82b593e14ad253a1d1ecf8123ac27a33fe45326234d892448927ecc6d7d66bfbc1341adae7451ee146a53b0bc337bd89b466d82cb395d321b0cb080d50afa434e8acf38e545fad3323c66c361b566379521e2f6a85c0cbe0b101bcace860332f8f404788cf08aadc3905771e786e3ffe221d5b6c106bd15f2e42b4c050f87111f14322fc4eb9a7f4e62f1393c935040ad8925366c755d29187cc1be9d1f04236aaa5f14051ce42e6d5bb9b283ec06901f550c0f0834502f98fedfccd9c1322ac4bbdd54d3d1f75f2173a6eb4d7b8cf3c09e0e0866e9c3f3389fdcd96254490b5bd696942536c78ac8aa6b6d10f47a288ba117a0877f71d6c72af866f90cbc44972a908e12c8b207a8ecd45608c5443a49fd92e3c5dd2fce5a68b790a2ef58e9167163e11ce7231190d424103e424fec6ea76b84749748e73ccafed1d080e1b6dfaefc15a4589d718d09d10a272719717e4382938f4a6b902122e1ee07d19deef889c606cdcd4125b2706e9e73d7b60323468c47e60face0ebea5d590f0cc43dee4cc3198d8f29799df2ebdc2e9c44fa9fe1efd6c2297a0280def55f07fa4da3496abc455cbf563df885525e5bc668680cc19ddbc38e1297760f49cc3d622a2a1edc148bd728d020c0def0748b52ed5b046cb6b09756d1a34bc126153078ecf05e77c460d2a1a5053960169a6acfe48a5c03cad53740bc62d969b50ed77bbd5f9db7b2d28ea8d53bcc564cbff8c1a82c19285777f27e6b143f4513963dfbcbc470613da15da3ca6a23962fc1595589ed744e3acd5ebfc34ecdafcbd885180de6ed2ab367d6fffc1366470f7efac65cab088bc5f80337768b10b6929b85bb5ed1f02ed482e1f4878fbb101cd58ca8f228a04190511be94f2b11374bf4d3189c4c29ed44d9a9d0ec74800a21c6065c86793ed5721fd603c0d1caafaa52be80bd9f2bb2d0358afb23ac963dcbfdafcd03172722fca1a606b87ba5ec42035c5505dfd090ce0a289380efe3ea3b8ddaac3b3bff53ef0265607676298c664d5989b01653837c9d6c28045bba8345344c969dc8618bea140add43e83e358b8782b2d076128b0c1f6d25d0fa622b5b02ea61298c994125f6da793429e9034ef10ba80d25cefb9322724a9d7bd9ffa9c7e48dc3dfbe7562914f785a1c9a2599290820c7475a910aa35b3b993cdda41a75b44a0be85cd1b2a83e7deb52e0e332d7360d7aefe1b38580e40defdfcfcc7f2b19ff6ff4489eeb26815e0258a46944a367975dcfe270c2c430ebda63f948219afc600ea09026eda58b7a429ae1d6aa5daf83daeaf0cfd238a35bbfde4d96fc84890ee1218acf82b4b76ddc602f238f263decb33007aa1503e82cdb30a493972b2526f9faa433db61ed6fef9fd2cc26f5bd81bbf1373880fdde5542624852292e90781e36b573844102ea472ae116b1e4f31d936c00075388977e8d17b907e7e2d824f8bfca268b3453129bf36db3e1751ccab2711b46d5288042f27a95225b77fb01598723944294607bc814b2ba6fc7531996dc4f84969c5302a7e1e6fa3f128f33249d57f38617ff4698fb7c82de854aaca9ecc37a7c74c27954e9e6b1ee23380e61da2f7be6bc092e8a7d7adf6898b6e4a79d1c96facd1da3846c02627e5eed762784ad950febd991dcb1d67a9673de08975abb95d0e2ad65750719ef43b6d492b0fb17e43e38e37a70f8fc1064a8dd8c247ea5623a3d09c93236f06c1810787ed45af9dd72f2ff9e64345c96ff12504868a519823ab639bed75cf2e435ed74a49bb925205a601435191ee17ff1ab34f0805574ee47523a8c588783a7e3e3c0b38d59aface0e475903d04c730db966640100a525a42e7713f858a6a5610a59bf06da7cde2469aaf8e136b3bb3b0375342de49a79be436a487d61679cf36296b31330e0aca380b67ec29a8e2a5cab9897c2edcaf6c6a629fd2c8921a1fff11d6ec2b2e3c4b7de5ba2607ddbe0d4158df770187a0d873686f386860a8383b95ea357a79650ef0f6cbc22c3aac555ed940cb787263447df7849a3beafa4541e8b3aafc07d2f85e623a0ee04a1b4fbcaf4a9a497fcf90d619959ac666e92b79e1d62fecc10b16eba9b0391197bfc3af5749a59725fe9be36a72b8479fbe8e3c24db4131d3d1f76a6557879a0842d2b69e28cd37e643a43cc037577e5df8baeda9b6bc770e4182c69b4f3cc0d1f380e7b9010dd3d5855260d35292030f42bd4884ba78d385abcb9df81e710f640db2c5cd2a12f3238255058669ad9b0ea1d9ef1e3bf5c48e7a1dd0767eaf01df36262bd81c61a41373ac710e429686cc680db9fafab59ac48a69d1578f0e020d3b6f1967b3a42913cbca456a188b68e5eb468d83d73fc2b2d857770a6139e5793b87dd7202c454d612da4eeb587ca79de1fbf21e1b59e2cbc431a5b4beccf0579f23e5dd78860d875fcf801d87853634548473a323ea6100c6bf29fa2794cbcde95d02587595ef37be7532be91e7484e93667958ba6bde4ab1213dc4b9ac04bafc92e121e2f6408292e4239692eaaf1ef2f87bc8b0dcd2819bb45e9cb14bf99ef48c0f50e9f72546a945029005ef54ad4b8869e96daa4c64bf84a7709307828335185e8cbcfa7eddc349c22f706a096e7ee5bed51928cd428ec951bd3db9b2724e1c5fd4e1597b55323bba1bd554bc3cc2dd72629bb27960f910cfe576928730147d991959d471f54df269b630a2180fa05c5cacdd1624a50d496d0aad327fb1affe05a202dac509b9692141fc94c543e83e88db04d5ab9ae8086ee44989dad56e2904976eba58f9537d798cdfa1e2136c3870e88b56d21e3f82158821f2722aea45e109e3fe41ae7535ff5693d7b84d7db84acd2c3f0161cc2dbbd23b510f5afa3b2d8a6bc3387e5d23ce3966474e3e8113dac4b9b2c2679de00f6cc10e1f5fc04f446201e0e7a1b7452991a0a383f855fa8b8780ba38b8ff7e706846e9d0bca248a6fa0547d22c8213bc860aeab589c4a732e1fe5c522e558eb66383748f763542dc83fa2a3215e13d92bf4685d11730b0d57cc5e94bf7abab73238395ceba3e820721bc77aac04c0b6b10b29aa6a2003e4df4d89d0833ddbb9e1b87046457e983073d53d9c9014a1a32ef8853bfe0f824a6736d4be6494ebb46f50602094782b8b941188246e9dc436c1b81f3f4d81debded5ea2a102d6c7cb097df92c7e2b78d4a7f98a03e9e22f214a678ff9cd07db895760ee6227d102870c1e15da04c5b208e4bbfb4bccd8117ad6c9e796310b5d02f01cff1dbe44c0a187f77b481d870b0be506dc531aacc56e73c2c55af64773cd0c332c7bd9665fa78b317f9c91479df9999106b8cc3ba500a6def091fc4bacf7848eb20f6b5ef5ab3cd82099a5efaba01795cb949799abbaacca8be77efbbcccacd79833a2c897a9045ccc4707fc32a7d54e9d855b8be788c4d8d4ff800f77aeecfea3ff8436774c47119fae2affc34e567dcc3d120cbe1be580767a81920f64d6b55b653aadc937a8af40d74d1fb5e099e954cce9ccfc80ccd967befe64b59bd1f51e93a824850d4cd24ba808969f2208d9b9dcf2acbcdcbc245d3128b87798157748167f09eac27b7b46537a302e6cab9bf2863e43146c0242c8bce7967c46ab3c636d80bbdd0bb98d2a59dccf3b38b2016780885c9a3b333969c2495a872fa0066ffce21015e7a1517e6781e9414b2ddffbbf9276b83b1db36ee22b33cf0968dc3495edd10e95f8f2fc8f0c0817c9e98e2fe72743cd9c827e263505a525dba78aab05883ade83c402cf9cfb49fdd3176c460252291f9d170d3eda8c086dea4a1e6c5e547bc5f9c7f3528375de008f275a8771b3c49e32b0f592fffd065ff9ce6d36e30939a088759f7537ac8ca69de73c880d1862793a2f7193c1ba21a3802a8c85d7360d1e9427c19a61072eea7a797d8ad844857b1894df2e87192a35e88c5734994204cd749b7a15b9bb308d412188e7cec46543f0764156bb3acb1bf67ded31bb851df40199cd30056914b5bf4f508da8f16b43e25ebd0c06dfff6eed8e31461292ce3d8c3e22c806d7f0d9ef86c98a9dbfb794f6a01d08675894ae17e54be6e550803293a0c76fccefee0431e26cac7fe39c999fe150e1d3dab068b2622519b7710e83bf4b6a2d33a4cc5ed81cb728bbc21e70fee5e7d43ba349307b18ef6b4fc04a23ecbea0c0957d5a0ef6eaab5c103d7b34531d991313a6abe353f79cf6095d78344822627bec3dfe6667594c9818c89f13432ebfa2c7b8f642b96e6370887ed1bfdb578baadf15336790b325fbe784ae2ddeded8ccc1739ed605c23e5446580241f9005b8e8ea60587561ec1605ef07e488fe10722d25ce925bb636e15bcfdaa00663bf7e20c3b97d636b1335df98e1a525f6c90082f44753d4b93f563049edf3e43a8e6f8d8639d6c91578f49397d40b336035edd6320af851ccf1a813540aa2d02c6955dcc64e224842d6f60eb1de471068980a4d31f5ce9c169e9a3e03c86bbecc5d41349b4c4aeee5c6feaea37ab23037fa849b118144ca1b6b06d690de52d0dcd575ca520b3eb79b8b07a2bb571b05b54ab708ffac25082cf846e27aeab8b7eff370e180fb59dbc73593b1eddc3e415cc03d6e9662670bfe9bab983bcb1d76fcc6e7fa7ade565d760114ac45466d4fac9503f4a20535f138f6e8a84ae431b64b05efddcdfef12220d87240067ce0cbd4ad2fbea9131c63cd3fff778fda340790a4bd53e24963fc4efea24f817a897c2e6f1f4f84bfc45f2cc91bab6e184bdbfb0dcecd2ae2f41539e720e9c4e50cd3c02b64ed7f2c3cfe02d89ff9b498f0deda69eea2b7b2366cfa4d5a99fef5bf777b84f9806caea4aaed2154e470ae1fcf2b981eec22671849a2fd2717c1d0fc38ca38f8c91b5a88b3a89a27bb7e48bc245ca59da9ffa29d8412faa4f4cf1e683dddc6b6221478a99981864b045de11ebda3d5e2a92a9f23135ffa1678fe80f4609f67ead1b2eee457acbd17bab97ec122b6aac5d7cee8575c5e182f52ef4edb03e3586ce6b2c8912085b26f906c20871c0c5c4ee73642f6804d518394f8d96b91813e4f3d983e2675f1de1038f8d46311c5809dcb7a853cf5f5db672c27455256fd320aa11aad41b31c8457ce9f1fb9efc2d44c0bf14e6de11e4b0ef2eca28dcc41563fcb9f310483aedd99b55fc6f1f4e8f217be524f1f7c030e0263af8420b3e65fc2441d198709193e9591ce460fc5053b0696bee13b7e85622e54a8b8a174823dccd3d2a45fa01f604e0f8817cfebdf6e52ee1ddfd52e628d889b4681b605da319e1875e43f1ef7b7c0e6bebead7f306170c2955f34ac2e366535d04c2d08ef16fe96a76901f7e77e910265fc25e770c85c99fa651651150b7bb31238344bd88ba0294158073f010a6020d649097505ecc2e295904fe8605b6e4f67015b8ac307d5a9dd63cb6840c52a36ff8d58f0e2f720b23970da512d8d6144eb0614c5b575e4059cf3e923bc5511b79f386087cb35fcf8028020da3b68b43fb930719467ca7c63e20d6e94bcc49d002478bf04e7a5fb3915e8d49e86ccfb5c060d8ce85afc7f2999ba87d3a1aa5493da441af5ceba17133e6eb5dafca9eaa614f5d2aedb9cf7d61c32ce3cfa31525e121f93e1d6c3b8e738b6d1bb1ac7a179e464f36933ef624d4962dcb66720c1bdae6771787d6a32fe197fa4fee43e77439ed1d5e7309168a3feb4b984c1441139e8873e88dc63787119b8c44f3c1f09a5e4469500dca2ca4925579452e572f49a2c01b24cec1b6c9181bae8b9c41a0adf2f28bb12eacdbbf9dfc7f9ccbc39480a0588f592f64c5b7f9e00520375d8a19a92a0c9e509cea0f220679bc6a3ff6499eb1645f878b6cb64d53c4d2156442a0ea972cfd3288c1cd12b356f0c58c1957e815224e88f39677a797360af68ae274e11d5ea39f97f0e9a9a959d56d238adf731ac64a83d29432720138d0cdc386683c38c7447f2ca94c325e9cd1d9f9181558b04121cace9b6c4fabbb30dd22d0267bbcc237aa7441a6a90588daa06b55a8512bf6aeb913415135f647e81cd86582d55c5e071f7bb3cf9bff5f8b945a195d826e401db764049bcf0f736264cd6dd10fbfded588f11cbbd92eb28bb806ffaedea4eaeba9cbc30be58f6e08e1ea175ace31388f3f5e40f4ed4aeb4550dee785fb66b5cbbce74375c3b279dfb8dc13cf390b97eb2bf604b170cec5ed7cb472fa2df4d5486ff0ab51f4b46b77f2f516dc0684da41392df2719dc8ced62562892a2f317445423808ecbb5a2c29fc7559c23272122a3ff3b832cc82ec1a25d2d71db6722e448959ca1825bc3aa56c071b05079f07f08ca6a92ab09cd590f2de4abab9b694822a3f457e9b0427d2d6c71a263694b4d580003a1d7efc9512473596c84cd3a9e357ea316d56f318fbca891e7223a4c725d5a3863a5cb20c1472a4b208464e68aa8a2d2b491690484e5f4b6fdda7a122168d7fcb8fbdb86e895be2585626053c044c943281f6b583abd90c59f0e5335d7aabeceb078b4dc11d6cce89ddee88856394562a1856ccabc2544cafc3fef241461d48ecfb3b7a62b9d5ab075fd9151aaacb855cbd184437827a6c460dba824ee081cd785340438058f6a0da84245bc223b498f474c235afd5ad35753c73b03644bba25e355ee1a031cf06cc4666dbba50ca1a82d02fcb31b43d729ba6dd3160f56f570f48e559172ccab8ed5e168a3280e38a7ab7ecf5ef7e7b5f99c3710266c1e8962282d9c493a647a20fce426fd03a1564b4bf6b51afafce4e547a95859752f46dd08a7792938dae3c4a70a0862b7d2fe3ec15a0f649d71f5b357dd1837692ba5178aab42beac1f88d7aa169c351c2535a43f6bb742dec3488a8b6a5d139dca9071c4b9c5b40cff57cbcf1bc1e41de72cbbcb41519dae8d8faa0c5ceee4affe7a9e308ad0991f2bdfdf5e96dda72a8bb11c055752e68bae839118a0e35ca11f146955297efac700fae4be079ae4b7644199bb64ce2ff7e9f9175c1c93d09134e06229aff1eb43c90f6a9dc926ba8657983fc459c0c5e14bcc348b30cfe0f5e8d4f1b15265ea3ee2dab408d72b95c77199e794c862d55f659288cedc8b817ece62b370d969cc2091672004be7fba979ccc109c5d2914b6d974446115e4f0e6736fed0c03e03339300ee232ecd15c9c29b0488c5e5b5b90c63a5ffaebfa8180c35b864d3976d67f8c6c5a33265b569bbd7ab3677eaacac810a7e4333cadf0c5e1de7144eb36ba8458128bf6cbc11293934f0389f348847b1642325e398a9f3a7224d28305f4791aca35a7a4d15851da9257616b71a793f27b1fdbae8eeec8c4f6640888fb8e8905ad62d1cf7843c4f0ac9533b95caad5dd486f4996f05d4cbf754b34449d2e06b9f8102afa136493ab8e09c79a5dab7001d6ca962d150ba5673959d0e425153b8634817830f07bc5005b03482b199a19837f20e34d20e380787f377bb3620f263efea77aa116a1876a5bf1470395fb52cdb6d0b34edaf356d7cf0a0c61c9dc02c004cd27c1ccc5783e8d44e3d5c5e60d53da7df0db16d614a84e3cacd12e2dd263092d515ece1eac4c4a5cf4c50bd8841631bfb6b2ca09916e9d30712e10b283aac9f84d23898a27bb1134fde6bf32431bba8c07f6aa4de7b1177a2fb09a024a23a5a12d87b952e5b51f7e99ebdafe0fa0fc17f7eff6527d752fcb6926a743c6e917d738dc37feb0eedc6981635ca2108c57a8f22a5dd03bd939069056bba0302fdf1ce1d98780fafbda115375b01ce57651b0a27e7165174bec3ab27bf0ff99656e15af6ef80df50078acbd59a86c8f881f520cfdf8e6276033abdeeb6b6e0b54a04024c34b77c7603d81729199b3c92e0b3615f04feecf2eb52f7f31e58a3beefe1619655a104989b2f7e4d1ebf70def96b8fa001dfae4b3cb0fef5b0f06166acfb4f0ff0f433a3f3826ec30d360c2ac811e35ea8444b98a6c6285614cc42346e1f1936808f7779c4ebe3efeee06ed7e526a0ece9d00b224de2be9c6eaf5504305a1b4ea7905b6d8665729885b1f9dcfbbeeb9fe4de46e81449cbf18395cf85d04430df7fc894c86bb2506bfa37ec408033bd4f0032f71ec86448a4679ec8bb38a59a3302ec74482bf58a78130dcaa3c42091d91abcaaa9081fdda6bc14997dd29dcfda7841769c9ac9d1b3fc1a11b65486409fc44c4ea512d9cc0e86aaecf84ee5802605b7afe918e64874a00668bea424ff068d3b84df4ea96495874fa52abdc0c2b033956e48c98f4ff88f3b521510720df75e5f245cb5c8fa1b3643b168f0b905d1a03fe7459d6b7cdd7b5ce5a927c11b5eede43bd0ff59882038440ad8364ce12aadb5ea612f76edaa3c1af27c5072b5ae2fd66328acf4004c76ff2df85f015a1c42c5d2926d2c9c456bb39022b24a4c5cee648d01cf9dcee7bb20821513b96251a62163be132f9d7b899e0746754aa5605f4a732eb6184461e7a640d57514d63162d03143f6ea117bba209125529c79b7d2cdb7355bb96accaa5dbbae22400a6e349e45721c101a5b0bd39a116f7817e36e4457d3a5224c5ddc9eff6c6f966e79168efd0fabe141924b58b8ef8e14b069fd1b7f747e9abbee56bd25af03d4570a517400d39ea8c17c75c5a8b98ffbc852c7c209bbb0ddad7f6abbe16abfd83c9ca02fe8ea56d361daa67e7cfe3866f8aa81fed7009e9d61b1dc2e3d66f12e2bee69329876ef8bfe4fa98a1f5d5a6fad054a8400cdbf11f6497974bcbcddd22783fe4db30a6ccf74e40a9f4a0d3a3d46f5b2e35661fa4690b33295935f94f48523f1f1702fb24f3a84c6662358537a1f95e0161b2dbf16db7dd27ef99017aefa587a5126e253aa00817cc5a21c65b79901b549bb59ffef80d739721a68d2e759f2ec2034142abe8b0d3b192e49d0904adb0caa7140eedb391a32c9a4515fe575833addad29bf2952c2d6bcae6546b1b0c80b5330ef93682b3ecd756d8d9e3490e487cffb7a893c961bd34cd90bc80bcbf178a5bdaa2f63a36256dd33ae1cf927aed13eedd3f5e464f8da704467f3e6e4832b9f1b686a8ca947d2f01e1cc9cdb6c2a0812cf541654f1ec92b65bca293c12f8703555259b07fd50fa52793fcb61ac2cee1ca1527d9c3b540f484dedae813aa0867934850ccea96482c47a2cfde00955a84a65b54d69fc8d5174cb809dfd62ee26ff15c842c0b365193e92cc115a2e74cc7c064b9b1f7c8c52a89c894e4adf8a7ddd4547eab9c095aa4d7ebac5187a6acc737c1a033140a4086749eb7c5ecc80c2abc2c5d7db3853f3185e811a5d98683017a769ac457443e17cd61b2414864aed45392e8306dc36a277304a968f67ef23b777c365d1314edffd14948a77915f2ab576973d09802420f1f734b919d495633063f8fc25279a4cd4a268e125128f1a43c468e4c0c2529d9cb3c296c15e67bb37220a4d94e58704586d80826cb6e7514e0f6899a3830121a4aba4f3b0c19e5132cf4831b84f8280976d41b59c0bc055b81f6c93091ce9d19c81e806cd5e81c48d883477462861b97d3a6c735ad6c02561bf9e6ca86cecb539cea5564dd1e1cf6a30adc162b9242668969b81fe4dad127cb66a19f28a13b50ef77bd17d4cbbd4484b99d0fd8900e217151a47e32e91c2952d8a372fadefcf02370baed1bc143544336c29fd859be40d621fa65a47c1b15e99c152c31bfef9adf557ea37f19eb929c1993ab04a7478b12b5df139bfcf0c28a36fddf52a1b5d99df9ce2c0fad905f626e78c5689ccf35087a80e1dbd818fe94dc726f2a883161cd05bdca489b65aca236d39f733cb3ad33f1ab7e9601e34f1ef9a22d1a98988e8e73aad260c8b39304e5a7ef9cee1f4a8d30c492a26a00cca7aca0321c9d12cb4d13a1df8f5ab21025e5180ad5ff3b687960fc1c5e8e1f32e24608462a84ea719e09f967cc3d6a4e8d12b1c6eb1edfb39340e72da7e998f615d0718b5ae04e1ced4d4ff139a0aa3ef93d7e70a4e1da9e38a055d4647e2b045e83b35ab966a274da29cc5ce5750d6b09a9fbcf5b3fd3484ee78457f450c9484d4df4edcb715cf351bfb9051bf40b322791cc97fc15a759f48e9d1899e9735fc7538ecfb27f7927ba816aacc01823e256a046be7da1689070a381b5bed628e9cc69c13116b17b6ceeb76f67a2200865ef77cc8d4d0ae608e447eb467628de66bc9c359a614ffb2f0cfc1f158f10ed6cb32af875dbd6c1791330dbd318f48ea610a94d8b5742c72880c16d07762ed55594c395e7e9711bedd228575462d45715f6834915b19995212ac90de47485f92c7bbe6dc570adc7b5738c78dda965a38828d1387950044439e0abedf2e9653d90d08cdf33a03c19a3e9cdcad014c766506b6dbd47895ca8ba1454fa542ce1b25720104cd1023839b53af3f928c4206ea495e69da52319f1ede38236dd804f2f3b222550a1d59c0fd43215d13d0419b9be9dff27a00bda9585594a21863fccefe5a415cf346bc9bb4a43e28899785fe734452e63bb10e1d7884f926cbde2b64d2a5523cdd8d6029d09ec1b66b91623b562091d1324b594d2dde2980300e73083471f031ad62bf0eaf00a16a8d3839b87f7c22d8a2fcffe1bd51b696180f6a17f73f45dcb0b541edea5f33dc7fc127f09f9efee4b13ab4bf558136b37d1d935cc1a134d876ee07d68b4408d0346328cd7ccfba87ffe6d4f0ba28fd463ce0b5a80888ce69e363de27dffe41843af8e043d1bf9d632ffb600c793cd30383e9c4546e8a6aaf8c66284ecc122834006982c009d29a66b5dddf82d22eedf0fe5416b6fd4e613cb96737ec5a1b388090d57cd3e26607a0b56b7f45263499436051a0819e2ff0e27a8b65355989b75cd81b9b55a80c169815f8757562ebbd71e85989b60dda77b52550564acb0d6acfbe19033f6e4a8be4c6289a8f669d784e8359968a8a74caef27e127cb7de4f21b202829d002c7d784f8f9c0fb16cd64a37f457f4ebe9f8f10665e845e87055df7bd80ccdd2fe13c9f0bdb1dc7110f42d599d528d240d4800b3b499d06dde0d835f08a5a05ba2d8980f7cf901d55bee05bd12bdc0bd2a268bd9946912fdf345bf3d1c8d765c2a4cffc4394893f6e24afdb8c53ce119e73cb786bb9d6129a14fd97f74c7b5b5279dcdf63aca4cd3410c14a185168626685a9e2ad6c4fb4ce88e291651f946c3230504ba38ec0c39528b9f2d8ad60c01dad3caa3ce7a7f01b3c09ec68b8d16c97a525c85dc505447ff39c531f4bf11ab33e1be19b79b71288dfd3acbddbd976f6785bf337fc6c97fd82f02a23efef8ce2d346da76665bb8388f891c0ca6a3c5051afcbd6d573b766c682a4ea59a868949d98c1d5103f27deca004fe48ad30092b053535976dd8bb3829851966b22cd84a54015e661bde6c388daafa38e50e0951862def15df95811b6cf7f446c50d67f6e7bbbece9000ccb6e9a047a752c5cb33b812765e340536fdd19fbef1d59f5bf5af978bbe0bb116cbb4c341f9298fa04faca9b18bf9457e158616385146ee2eee46c039c8c03516a8b5737065683c344cd49def412d2621046e1acc9ca2a194a9d4b89dad5908dcc1a369d2b3ec8c5b8238046189c2a163b08e79c90fda157d7f6ae2c20d13a6865fc6005341bca8f2f7c025e5978ad39c69fcf5043c71d20a9d6d87f9d4a45ebeb89f5a7dace09b80c55b5659929f1355d3375b8b635ec3c3317ac0c059c9402a40ef5cbafd8b458654090cce6c07a99a26cab1652544f36af717dce5557f7fd966ec250274c788c2cafdfbc6f5b5196708adb8385e4ccdc907ffdb775907d105c4cdb26f7091ef76f45df8e445b2882a94a4b6a903ea7c20ac6a02b9ccf44d370efa7d4c5569b3ca4c92e50ea05ae59980d1c0b98143ebfa2fdd6838cf636c2216da3f32829418501011447bd72d737e1d499f90d12736dd607978ca0a2f9be02d60bb0f3c8afffef0ab46723f375df0100049d6691f3adcf444105a3d5b28af769c04ed599085b840d6fdd8e5efd3762e893b11fe27521293b9e257aef564ecc14f2a2472b9a4f0c45518650ec25caf4be27c516d98db1c2c3cb12a242eb185eb5d095cde7eb13abc81c753a85ff44674da2bd2fa5b907a62b3f6a77cd875753f16da681fd9fe50596c77112b71ffae27cc2504b22ad12fbfd9ebe51e2e95e6497a465f4591b8500b66974d4b3ce5f5dc4ef8b8d24af870c681b126ec818e733d2f1f95ad344b9898c7f2dc5067b9078e03a4df475115b5c69185fa99ff48db7add39b0dd9f5ca77a5147f54e946caf3769cc93d80694ac84171120b11875030b16e195645e8067590e300d06135eeab874d061fdd3454becf588a1f969415b38ff2548e2a063cfc9db77be59558032ccdef62ca46023a81d0de9d22613c24413174c28eb50143658f656ef5fa65b4cd6e1e0bc6a20c057d346e13fa2f6ca87096b9ed6c7f0473018c808d354e40ce69a6e98512217380abf92c2a00948c33a558c2f189e8162948ed883da37e5ac4983bb227a1a9deb9083e02119e781137d8c8ad02f156994813b4b21ac6f2512b83f6d39d1a127284177c2b6db6f2898df1eefde40572cab3ebb8897142d4c13e426c9067f5d557f87523b6d6dfa261f2afeb1c7183b5ded465e9a9c63b4b1c5bd0c6b559dc4a1def81ffe65b6f34c585217f7817caf59e188e247a5087d34983896a8f19658b508a0d1e95fa52c3024bacb57d577eafdc7165885878ae15eade3b9bc8a5169283ac2328c229bc324eaba6fd3c47773c4cd36d42ef5ec1ed5435c8820bcabf3eec7adc5e46509abe08f37f278511fd477eb746357954f989e576523b51ad1d4056e141ae3a4ce5518ad17812d272571cc39a590596602bcbac3402bf0946d21b01104488e7eeb76bf2daeb07823caca3bb7f965eedbe0a51632553df7ce0aace51f9bd79637a3a9db506260763d42d363d694085495c3bdc56a2f5a5079b36bff4d589abb8295dea7332cf83cfb4998ff6e2c983015d34f0192a3fc123eaacfc299da5c15ca76297239de25840ea0686f578c1878397f2deefdf2ade781624047b9167ca02953a2dc36b37f26f98045cc63ace3f90157ba84a07ea1192b8178606b5c22f5308acbe942599eee87208c8663fbc4c40952d58338c04b533433fe6e34f9bdce10850b85b3770bc20432d0bb01f99173d0ae7c9207d0952b878b46fdd6b2b08cf125cc53571faf20c6841b12c78e09eae1bf400948bcf894def6977e5577759db973c936e87136a785325d4876ab80c40a3d37acc8d450f6495f1e63727d2545c7bff7d1d918d523b097afce41b3e28452fc9270afcd21c282893e90e3272456a968e6f738b19d1dfa59ff97835e2f37a833ff31d77480562bdb2b52e931515d536ba7b5ed6bfdd2ad3034bad63d50a1615fb057d4160c2b03b87ac51a9b6c3e99c130390b5e8697755c0f4f0d7848fd1901c2ca6becea2aade204aab975071d67a976601d1f40ed08289d91ac726bd7953f3086cf9e27912b4bf9f4dc5b32b877b2896cab665c739c2ecf3eaeb566efdf09f02a221b7f2f9237a523a5d6af229bbd9037d549674b013df08c901c32a6aad860fbd08f1e85b6133584dceaca2244b09c63c7e0333ec879bad584ae78d560692cf34a312e973fbe1ecfc4d66bd2baf4af2806e24bf24c4d1badb0adb182729459bd48d5a3ada4f267a1f4f4939555f217bbb3fd36cae822487a50b2dc149789e72b78334143611316b8511f4d276498b1a287245737a11f882121bd0cefc0bcaf55ce939e129caf9263a10258ddb4d5b13a96a948f3ad0900f918917db752ba4d12043c2b1f754c5aaac91ff5260c063d3e7f0d53f26b781ab49acc30246d80b4a61bd03403e5ffc7ed51a60ee53ea70c0e6afc41826ce6acedd5c1e9da84405cccca4c32719f05100adf740c40a8813bad0b9f8f777b3070e8677270a0256acb1a1eb9633e1dd3937eab62e09f34a3b1a1a00b014b5198e66915ed1693429f7a126a7b2e1c352afb70fabcc5f1a56a13ef6de263181435cc0e8754d4e988a483645934c826b4e4b43416d691382c302e5bb8e806e108df70cc3a668bb577dd2cffb8d8b91fb27ab273a57a360531f2be411b03f851fe45a9527def704a2b8c8c64e17082fb702279301081d7e01eace0ba5c94e64f0489553358f545eaf26635d7cd7dcd4c15cd859a7e34157ab193635b7c098785b51b871672de92cd365b0207a1747b08c84f0e468901a2b9b94b6ce0e63311f0ca7164838b1cd6fec4938a12d45d73dad1e5f461b79318196b7fe6bade00d048d89d274951974ce5b576ead6574310b0ca1c8aa9c4e8726cdd52f253b4c4d6fdf47bce690e66420474af47754da5dc094dacca35c8297249ab0c8ce431f11c3f6c880f59b9c0b805a92ac532e50a9d2d2679d04c0cb6ce4f50360543ab6e5ab970528e16245933a67d09cef9bd51583303de44a42a5c5ef6cf82ec0b5d0bac5c88ce35210efd22e395ca03ce20b5b0ae023facdbbd04802abb26bb982126cb738fe2cb959294a6dbbbfea66fdb97614d1346d2e21cec95c2ec57383f383d906bc913499368a2d2b0d395a8d6f47cded7661d7bf65669d8c83b9c6c74f1090274b8e9f2f8e2a835a783c9a202f45da038eed04e7e724fe187e8ad0b464090286b7d496fa77274f7953ef1514f4e4709b81ef6aa6f3d680c83e2b843bfda24f91a4532507e522d069b8acba4a1e7b8cde8cd3edd2741099707cfec57579739f053092e61ba9bfcb65a2828ae99203b1a334d9f7aae7a92d81705da67fd3f906f91a2b051be429eb5e75b3fe79399cdb904195dfc9f33c3dbefc156c4d1f13036d33498935bd8b275e29373a063506037cf46d9eb1e2a892d91be66e48fa8580663430f7c93a9846b454380c999029282bc91fd764509189de7204c2bc6e7874ce50b53d08c304670d1894c2cbd98df6d5bd382ccb09dce2a1e599ae6186f8049d23c2f4b47a089db97f5ecc4b0fe2f40ac09de6d90937a9a76440fe8626754111ced4eba548e29ebc455d2c825c7a45da9106ecbaeb8f2531c78c15b1d7436bf7a27d08d2bd528176008cbbc656caf667347e056cf597e364687fdeadc91ace3fdce2740e831f84f7b1983bc9d3ede347a7e575901348c6c26a13836cbc9628687e3d1ed33690ee505c65391afc212545966ee06ebc5b2c56f9b5f72175b8a479bb5c83a0d6f4fb74b78abffdef47107761217f55b5d124589ac455840e63609ddd20d3e0e81ad87765d0d8f12f67c9c5c55aa9de226cb333ab170627c2e08589ff005b5e9dd9ab430ab6e292fd2a63404e48aacf2133251e6a74bb41c05af4489f8345a9038ab9117cf0afc7145e8fe6a06fe78b63f274b5851a3d6c4a63781f66c9a8064889893adae47a5f7076dfc7ab265756eeb7bf56073801198d6d948c5603e5c2a0b7262fb82ca88b4808959ab11a47db2e56c91e917397d8a252d9aeeff7520137fb6aadb21659df1c4d022c37c972bcfde690c65464e3afc3cdce89bb7d4263dfbc5b81b527cebe66a47a4cdd086a191942f5cddc18270be2ff6bf02b044920e8699665f0939968840fd318457e474ed78a69a6bd8edd17deeb1f16a00de0699bf1ccd4f9c7a10288b472c33627b762859fb9cf883c658f07f96908ebaf5334564b52dee6e0f354d95df33e65d14ce3f8aade18e88b7a3cdd770293b91201bf3cf0b64f2f31661c04f9516c53a22a1f5a95e3769b5682c5d8139350c4f4e475a75c75c50dc10a1fa019afae1190f7031190d923306fa4859c62ffe9d0277a065c90ae74abca716cfb739f06c5e725120c8edcf7ab1bb67e085d3dc62311e8c18793f6bfcf3cbc7312630c172bc789234ec5272e1491f4ad92dfbdac292a81a639ec30e9fb377ed96c65a4b598a43ffecc323a08ab7e8f4dbe28651ccf8191aacb2a7d72d3957137a99cdbe842c4e2ed2f130b01fc8303cdb45d135931387d22445c3300725ef82fdd4e7c75fcc413ddea6b4e8252b6448ad205bd7113032f5c3999f0cdd7516f7f7266c75df4144fa98781c94d031db995708d13c1c7aed6c92a7ab308e66250f5d4f124f46b7613abac61b70c0536030b54c5bdaa0c94f755949b08a07c36257f18a16463cd8358ac94593db7b0729cc8f2612097b128c7ba391e47602ba949afb94d8ece052655c7397defc526b53f902c7ab66fb770dbf64827dd6790487e69be6f426a881f96e76925655fc2d823da2e0afa1d06842939fc61ac49eed74a9fc7a4aa31ea8164e7586addf3372706e5f873dccebe1454de5477766ebccd30461830fa1898347c8309acf3f878e404cd5cf9cefca8e04959629c5987f4d3adf130787edfc374129f130e64b73ac249b369435b82363d342149045d23a8490ebb62853fe345502c92e3f8df5c0a6a8440e2de4b372197b5fee6680dbaf9c10f61a7883098dc2c457c8b95f2c32cf9340cc2ba64dab2e97752838629bdec8253c91f50f084e43f45d5a453f5a32480c6b2dbabab495e460b2f739f5e83a006a14dd5d519e75d16c8b68d65141b9e8379a448e9404b6f541460faf8141e727cf87ebdb12461d70fb397824463c4aa7b1ef7ba90c7683ccccab8fb594c6c314775488ba2d5539fdf1b74b22a01ccad54a0c4c0215e9f74fcc7433c9188fc0d7c009c8b0a3fc56f0d7f9b70954db313ef5ee0af0027e6d845252e16efcd8f2068d312a76f51f7a274469f14ca2cb7f5a159ed3ff360cdef9cea8779cbad4f7e09ca2ba5fbfb0fe1c59f1bee505c0a15b3291fd0a90fb8070dcd3302fb620993908511f9fcdc742102a354ef71d7552ee0cc2ff53134722a333da0bb5efeff17e55e12b36582bccc04c9e156bc4b3182da3f54337f95257bc2c523e455a3275d222934ad4c3a57c6f7b1e1ad869d8593c4e839b7ea18efd5feb916a344d4dbc217c48dac6a735127d93b70e5647c0b89b9d15678b1aa966e4134042e16cf6232c57e50fc9eadd17dfee8583bb207585344b3166e71b62b9a79648c4ab7f9cf1be349abfa6afa33778e963fd30f8329b5e6ceae68b02f88e5a72ed273975e17005dbaf12a740de5f40683b99edf1ae68f9499a2ad562afc1861198e2b2d3964d6cfdbb0a307dd5eaeb3a41044c8b87e62363643af2f03a4cf508c2028729204ac0ed39b1a90d2e8ac3a50ab3035e827bd29c0b3156a182bd7b35e3d2262ed7bb69f507a63218bfd1f132f52e9d6ca9a88be653cd71d12fda2dbb50afe7f038faf3ed382f2f12778430986cef71e1dd063b952f64cbeba27bcad33cdec4e915abb59bacad5d92eeb52bccb9a8ac44a6707d26a1a08ab676f3d23757bedd732c7489328b6a4d8fac2cf1f8f9ff842932b9237d47caba3f15719cbe0d2c32950ec5441b6911791bc15b2faad36e0593e69de0952ff334b3d241e46c5c716d419343af0f3bf54c80a437668dbee858be00484da6e04eaab530e6acac76ae86d9552bcddbc2484e902c55b5a76c96ca0af86f735772dadae0a3a48a1a29d020dba61527b59b8e481f9d49313b0ecde22e78b4a1644ed6aa53d243da8333145d29cb4c8f1716c73c2bac590ab020443e03c506e6ed5b957c595905fd1da46fb5dad333499d0cc2d045b35218cf7c8a76f65417da1bce6f1a4e237f9a1280f7f530254d983f4051e3e977884f83d6ee4a4a8ce7c075772ecc002b9d0b2dc3f7cdcb5fb18f0733ba991c5e12f8011e7091b86e63c6ed84fdff85b8e6c3234db122752e5add72e580cbe55c58e8ef044a6b469d4769625f80111e40915638822f5e44d54856351c28eaae15da1dda2a58810707011b47e4b4432b99959193dce5cc8982c4672269db5e1cdf100fb8f6b49183bd37cf0f90dd1684ad7633fffe45c0d38bf2918ba82ad117adc79ce65bad777d6ff6be6cd8864231023715253cca6eff2dd211a4cdf338a015d1e0c0f72b21bac35cf296c1ae9b7d4ce61e4736c52300f086874925884c58b4c6fc1f92140b888dcd5bb6bbf7adc8c186b1a80819a941dc279d4cbea31da2c1efacdadaeee44b03ceb9743de492fc0779f2ffa73c766d66a361ca2a55e0856ca9563b3a3607c02751528d5dfd4af81ff2eef9552b4f2051f4be3dc327831d6873a8cc96bf35e2f61dacfc65e9740f8d6f73d57c77209897e59f5e3907a5b6a744aa26ce956adfb61d260aef79c2b770a3d8d0022948059644ecf11abbd30de8e03d5ac29c47195395da50b045d9c2637206ad1d186b0aae32d24d6b97d7ebca4bda9542b9e74f86d81da12cae3233cd3c8f294e848f8151c325aa894a1b98f1589765ff4ecc562e3da2f47eb9434c314fa5500816937d7b67a0dca5fbd74b28dbde7bf6f3039d9a0a14ae51bc2a7f5b836596122f452191fb38cd93ff3ecaf1a0a7c76cd7de5bbf03dc21d6de838242e78d99c7b6ab452c7055734fd1e7d343ba208f85eb7a56a4f93a51fb4a5082fe2ec417d0f62c9b10d6ba729c1ac2a8ecc3533e429f499f9d927f3f2d75e1a8eb7271451acb4829991e2e6fc569619bdf77bf90babd9a2f52e922023101fd7e9e2a866d9c873d42d0c6e32b89a451514803b29c883bfea38f32396395e6f8b43b3ae638820f838f0fdb4dfceff850b6915e5e69f0bf259cf6c9b3008ccc4e5ad06cbc60f29782d6cc310f28f59428dd58b062a3f004e20ebe5c2c1c338ab7b956dd0623f3b89b2f3e260a6684f8752071b183ebdcdbc32e9c9488beebe0144993e489d3389d0cc6ba56febf62626b4200767443329cec665aec7c812fd741cf31cc9f0d33bf206294cdfe2ebcd5e3029552bada2f7c8d760bae9254dcbe24370b33368a1517eecdb759062be4d2bea0d6d28c32bbb12a4721dcf42a7277adef9a51128d518348f8d8432762d13222632cb92209400ba301089633342633975adfb10e5ce5e7604ac1637dd92b367d08f2357351d8745c3dfdd1a0251dae3738583dc68d3ae33e526e5b189051ad4f5679849c5aa1ca7671d7fa12ae3c0a7aa91f55f101b3ad7ce9b98c6214c46424883e635673968566b0ffe681d7fce73ccbfd2dc4d304310e19267267e9f07fd9e02a94307a51c25e48a0f9ec08cd26bf3334513269e265f43cb8fc7313d5d5e72bf16a465bf4d532788fd00ea8f7b0219318328b4ee5a609c97fc70fcc96602623cdb0894b6e1c751c7064ee832dbfda19176e2a5f901bf9b4aacec55840e62af25edf57dcb5816bf845e5ffc207113cccfaa46ba82e59170b3a6b3e646d744c161849a5a586528963dbef6d9b0c6d7459ec3a84928f9a650aa96ae0f5b783ca1b1917f3b270ef73c84bb28295fe5749610febabba1bbeaf5ac5836631b31487ee15904841855bcdde453b8071932fab0537e410f4e11aa1b8b10d82666fed0298916546d199c6be7d9604d8656771f67f66b9f561272e604239880d6df1b6b985cbe39816a420c8ed0e55428052804f4c11e9335888455f52d599b9f057b2c660c57f91f3634c7493d6a86674225cb7122de1c667c80eeb71ce460e50f1af351b67220fc76661553c0d17d2fe690f2601ff5b937bdf2181704d609bea464a9bbe5ba119f2e79822b5a649562dd76ee25e41cd40939bca96699dfda755a08722b24abe9e4ebf9e314b0f82f535457dc87d87f83d26c66dcf93eff06090ae7a7c919dd3c7aa100bb966072ef4bb1db1c3349d7879c222760aa204035abf110cae53d881a65cc20b3fb6706e8085f404039a065ab04802d615560900f44a7f0be701717b07ceae8d6d294f7bc39673df98d2dcf98bb6412305c5280a0ecda1255810df4e4ec406a1f700cbdb703dd77b9877f138b081986ca24cafc6d74e9338bd1c7ef47b3f282796adfc4567f17c631644b2925454c95119676562fdd3e7df43f1fe3b12667e4381dae448539bcd02fc2e5fe2c5e631feb9f4e28b5df881c814e9e6e136b001036265dcaa9b399da39284be1db551758e5b00def1963b99e0ea291f9fd0022c481ead97efde051b9d87cb88bf4c0636e343ec0639a09d9b86687a8529a328793ad0fef4c2aa9d448b78bd361d549c5b20b4360eb84a3a968be6a2594e9a7e9a02601295e026baae39d8463964b2dc8d664bb81ad87b818975a5d9e2d5fd5f7b8046eca9f3f770eaad77d994002bee61f41280f24450282a6ba974d351d889b44fdcad0419e91d9a1beede8a106e1b7a7510f16367ff0c1968cf2f1625e6090cddecc086251723cf90230701e01916d03bd79a32f592518a9b96c3f56e3504a0f17ef3255c5464af6c007f1a9705aa3ec6973b02829f7d9ad0da6df0a8b202cce4af23740df2f535da01559ab6cde7cc5cf8e1ead11f1f4655668029c98ec0deff3596df36dc00e580763da982f541412cf5fc4b7245141a25b1b7eb98aa413894fe9e9394c829627e2dc93cef20439417d008a52218ba40abc4d8070d38ad3eba230ea0ec972302ba0d2c96172cc7d5e208d956df5c403b78d31ce2931b6ccd303fb446b4681e905ce77aef2c59e9787d0a987bf0d5706049d1c95ffe3952d475dddef9ca31300ed2365babe19000c3f56f6c7c02554a84d4709cbcc7c66ead45faf935af4811e69f6179575398a2ff6e5a0e113bafc9c793a161f988dbc2330690a31053a976514b76d8d92cc6b74684ad3efec7746817bd5057a3c15ffdc6134cfd4c95016471f9a2ca23c9ae508a2e8401a4a045ede3c81bc0e711aeb020b51a05c95063330a6847c1aef28cdc5cbc99d210f3e20fc921cc5fa928c0ec270f5644b0c5e1604221f68ae6c940c6d168022fc55aea1832c81cf76d22a4e2d4dbcc7adf8535c667d46613fbf8220385f4d0c828632a7f1a03a2b97a5e462349e44b70ee01996d5981dbc2046b79bb6676b1d2a070c2cd90bc44f9ca2e054eaec76e87bb39cc89547b02ff014e1a3fcbab61ad69869dff5dd0357d32319051cc768519acb5aeb6a0f7058fe24e871219a464cafaf2ea20ece487f065748a9366574adcaa2006b2335d236471b2bd20d8aad00135b350da813beaa7d9aaf477eb61ffb5dffffa9f2dffdaefaa4d5a99a5cb7078f8e87ea865528de8e49203a944ddbffd97f597a9b5dc80795acb56bffce7a36eedd14b702f6bfc6be5a8eb24de776be4dbb1ca66ad1346e87e9bba190177ee754fe392728a7282f71715f34da856606b667b76a01d84b73708ca26acefc7e776d9e07a1ab70d98b29459b163a5a98e4713808f6efe9668ae4090bf6c24f28c6a46796bfab91c3715b801c0405f6a82bc7664ef01f87fa8cc645c20cb1e39fba763aded143e8a91acb78e37e807fb8f476167bf00e75813330a1c085125d61bccaf77658eec865e67d63b65706e2001862081e02d9996aa1eaace9cee6714b396069618bb76f49d53ae64c38bb3492b83d4d2327133ed2da7682856a51751e224b1b77d53f75eb0f9b285f1b95d147b657eadbd475bf5e45a605ec6e8848ec6c803868548ef1e95daac612d0b1d61d396ba2577157fecf2fc390e004130b239172bc3e5cb3369fad17476a7bfd0b8c2d8554eadaf60fb04749cdaae85724ed0ebe8b36f23a4d5591f5f68d14296770c0f8a2d3e295ff1a232ebb0fee7b809ae3e5455a526f9c40c10a4dffe5f0a760e763d463cc63a8c2bab928ec7a901aa81620e2bc6a6b686d94b2360a10b3c6efd7c320a5d037f22ae9ee1c5806122ec4303269585cfd0fd78b4df01107562f918c99dc849c1bfb99ee83f6e8a0b6a9debe5d5e9200593413c7414ba9127f0249451e34b4d0a5f75ef06a41de698cbdeed15ca9265f9a34568e8b83c0cc18d745058868fc8124d4e175ea84c317689e977f6e13c23918a719cadd0695a629b6d5145ec18eb797e064e9458c5773a3d83f5f73a9e58086ba588952cf7963fa4ae57ddda3ecee7b11811ef6eb57dc2bcc7f111a95c4dbe8a8297eab33019a8c815d8e6d826099649f2cc771c525c3e02b05b41b3fae2455a6767e224f8b38610d415a2496a49a660f8bfb850b172d429a2a6e88cc3eea85f5bb05ca62befb8e92603a5b9440cc52eedcb83ec9ad346fbd8be8234900f03c0cfcaf7dd2f8f8286bd94853d25b7ac0c8a45e5f2ddd867464cfcfd1105fac75f18dbf629f5e15f54258e2e9883de4e7297e306b5cfae4ea97ba54124ad4c9673f059bf881be907203462d5d2a79c14cab32d73d4bf5a4e5af60ad8e83f1a55973364db9052d7630ca9a9020fcd0b8385f50de3324fc3031c9ed92b5c688d4427545044a48de2c74e30f35c1662de50520e437e3f8b985e1331ac067b1849b3e81bf913b3e79ea2f03cf8bdaada35b398e01b463c2f7b530b5ce7ab4682476cedcfceb5a9fd93c49368ac61a026b9c6dc7718f27f28465adb6e203a6763792ec016aab91dcf77f2f461d5d571fcb230d2039e26315f350b9bc26251ee5a553ee29e39056c1786ac288768cb1e883dadc69a330094ddec257be1eadc22709d0366200fdada2388288568ad0cab4e9a456912ddb4b9c321d4c38e9ecf9a65a52b9940b76ba3d00a435a1816d093cf6d4d39bf2abbc4f1c588815d138f7fdc4d94b2e5d27dda7737f816429ebec5adab48750394df1fe177bebc6f169d73d7597c94c075d66f235174eebe595721a31004a165fd1bceae4245024265ccdc7e172059f0a0ad6632e441e2312f989b0d06831d0a32cf8b9f205ebf033745e905c868c60cb45ac68d631dc77c06a996a5a11fec249b4dad64163f91cc714afddeea1381b20c8c99d79ddeff1478112b4161554cd13a7e2ee9399bf1e5926dfd26cc692181f7060965b9da590e59816ab5efec57c6ba44d573893d7fc9197765a67ab05adc2c1733fc998e41aff4e2bf5d04fafc95f2f2158b653cfe309d8e141233ae0c69760e902364a35e04106ffc5d3fbde833c32c39f15701761935ffe970413286bed97290ca7011824ed673d4a72ec11bab17722618b92419d5f24b7a0ed093c0a67d44f50d963ab2d15dd73e8777d3da6612f6fdee0f78e2f5f24b1efe8a146d6662aeb3bc1e6a2359117060ac801aae8326e6362fef1ce643be2077c1696808580ce4e283d578dac9387325e553add1bf4ac1beaa3668d1325e119bf3b58432a089b72fa15546b93a1e526c9c7e2540bb5457f017f4f91577f2b65308788eb0adc8f638db3d295e0baac2471d4261117b9fa38947b39abce1dcd73b00f7d8ba3bb4c44ca3c97f42a0b9cb59b8725ef8d0d6e3fb563c7d3306758d8b458c26d3bd998dd8f35c084156736f04c4475d3089eaaed9ea02d9a5d2e623a47ac9567b8d6ddbecb0e3d2f353043ccda099fa410189aab6d35b7e2387d0373296c6afb9909a9ee668750ef6af64e87e6a39e15990e8e2657da40f5f0dd59b9b093674b46b52f2be82e1249ec5225196ec564401a95ba196075bbdb87e804df2984e5db12e6c5baedd4ee0f74065cb8e7b3561053f9b6a6d20bae10bafbaa9cd8ddf525b3ba64cce032247d5559c96e9913cb7cfe59fe3df7adb27d9c1174eee397d8f9f6342b6884a747966b1909be9e9a909305987317d2ccf5c98b918f80c1c186b1021edcef416ec0e31e57fa534eec6753f886d83ae7dfb7f846daad2bf25829336b255e2f55564977fb2f3fcff1a28356d7505618473fd0574f00818121669fc3df2539fd39a07242918ac093019a0d4d78b8f8b0659c29bee7e54669fa24fba84b651d6bfd511fbd98a427af56dfa3470f45304f7e923e1a4d6339cc4252039d99b95b86540507dc95b963cd044ed3d1a66850cf855da35002af1a5210dcb40f83b66aa89aec375773d5293dceb9a5dc90621278ede47c6a4997b02d2818ba39156c6457198e8e40d01b6e0464be0e791af61163b8ed0314246742f9db54f9fed5c886b63b7f3eeaf969ee04e4b97e620c0d4d75a575056b75821ea3f51007a1b265b3868f3b511dcc3ea83047365359c204abe76fef651d1fde156a8467af41225df2b6b50d22a0f14060ceb109634de3b3735f685698d7337afde99c446f3526d2ec84a4e2e00c3f911cced7c17758042ed096e8d5280399ed2e385ec8fa2512f76f2dddc90ef82154a82de782ca88c0e95495630f32d5b1e30610ced5b80446b7ed2c77d706675e82126daa5e8e7b0f35717814e0e7aced25650f5e2b41cd43a2fd0f23177196941e2e13d54c67c7e4d200cc8de50ec2536c31032c8be617cb59d9d38641d01049621242ac59ee366234be72abe6812366b9d4434f69287ce48fdfd695eff71469529b5b7d542fd326ffca1ea9b4ab05f676b921c54e3ec306951159c6198c4c8570e7b594432bbe8479295a5f0e1161c3e2d261ce9f94eabf91958c46cb8cd23cebd95fa62757dda704421fe98dc6e0019b15cacfbc47f68bca35369313c78992c599f5da241b54f703630134aff4201d44fabd0785a993c3b7dfdd116b5fc326ea1185f2fb55ce3b703ae53c8c5a3d5652ffeaaa1ac38bcc4e268f90f14d358f0471822fdad39a54dc5c49fb01c9af14fb24053a68ce05f694bf29cd27dc404748abccfbeadc9585e0c11e3d8fa67e04336f93224fb999aa77054266072c89f201f7c179c2a8e3a7b84631f5646cbb5b676accfc97a732e2f07691d7a6b0d9ebec516473ace37ac7602a20becd2751e151a082f8b46ec1fdb134f763e9b15c5accf68d59272e7ff7a291e69348f9228fe023214b2718bf76ea85080f15c82147b4eb3545579e6b675804f40571987ae9aa09b8b0e0657f0c435431088b68f46cc476c42c093b0d0397efd46315cec665d8ea18524a485359d1ad6429495282bcd3a377bff2824d27a4226ba6a9f8a97aac6403a2b2667bf99a445d6b4d1ed2ceb0c0d79837e4cc354c51b6179c88583326976103c49aeaad3aef6d716eb909c028e13bbee0454d11ae942cfbee84476042aa944a4e74d8245d28e28ebee1842cfb5d5e98df7eeccca0e7a2b8e0042bbf7844d16b1db52352168ceee718ab3b4a3f4f01db69db2e1899abc54b5ab744b4fd138ce8cfd48481835e31900afe78588315a30cf89dd9315b2321fa515f026f2b49f6cd9f7c908cc904c3c8e315e1df1cd542a0f4789d6bd5f8b9914e50ec93361a8a56d90c8ade4189d6bf5f01c87cb5f334d33517918836b635b2bdf5e3c5fb3fa7804a150630941aea73fb25d82d1d9c07c1b0947b80b24d93e2dd4bb2a844763f2c4fe05c248e89879cc6b80861d03f775f7000ef716f9073759364e6365cff92913de6ec32c5cd94ba3347e4386d0f7354bfe132cdc3b9f17d9de603852b20fd22df552d86728034b22e301fc4888231d89faefe0146e30b57468a014c61a3d02b529290ba836036908b277e21f2d554152155043a2d8f9b7fd99d17d5575344be28b136a78ece175db1b50cca76851fb9ddb64aea2920deccde82de2086826fc2d9ff6f9a3152941f3fb4a37ba8d7b8a5f32658452c5f8d35592dbd634a865638a1790fb46c15c6704bd4bf7dfa5181f810ed2cd885f787b70da9a731c53707ffbc528d47ac65d643c91aa83ac93f77d6c6c80ddc72fa091a25942df34d73c5b28b71f96f8f353ec38e5ce466842fabb02e671e075f5a758e519ed87dc29b477473f0bd3ff19a966575c603314175c4f212f0cbe52871aa71ff0edbe9941303ab9e9e31365e4ff99b0c57b3336fa8954d387a07073e7af0affe3c0eff03c588148d7efae4ea32f1bb626424244823c10f19935085a9156e8d801841d35b9ff1b22f4640ef53c4fa067a15c376d7856d3e60bb8fb615c731d53ba2d67375105b6cc6e1aecc6aad94f119e33c7424310a60b6e7c00c5177cab0076fb4b4ed8f736dfeee48fa1536e00bcf21d7781ecf232bfeff6d711df36faa432b817eb41417a1e03e0a8396ced12d1670155868c9488bf360f9a5f069bb9bf1113336a79dfbcef7595a0a5886f1c8e148b01e05cffdad3b5ab872d3eeef8a7c75ee17e2ab6d33b6fa4ec24b94ab81e276dbac3553c20959b2ddaebcfad2b8c53b349f55c21ca28a8fbef7b1d430723e98dce28b0e2591a222346b9cea1461375d6056f6edf78545ba221e4142e7b8aca3f1595579e88351d5a8c9ca7d49ef4a6878a78e371a5066d24a85cc816a343d5e87e304553790f5eba54bb55870aa26ff4988d332c727aa63de22e8571c8f6ee4bf482f24e93523fb3ca174820d19b557d91bde7477edbcaaaf198a2f45fc9626490405add69dfc8c21436ce8d19fdeba1fa39516f5e23d19dee85595c4480e6783f0ef7c3e57499a736134dad31c5c041a57e634888a61d9c86c10be5121fabef723215f39ad2d185af1d58c5781ef292706db6f338f5a43d11bb7fd4bb017971e7668ea3e8636686de3cf9627acadadb68ed06e00c85b66ed28d84f1bf55f327666a4f4b9ff5ae4f1cc73757d066b2a479103585ca875850a7614d02077ee23e95f7618ad37c959e28de7087aa54aceb4f3a95b84afc9963f802744373624d08492ac3567f6cf87cabd80132ef2e5dad70fa32902a98ec3cfaf6c8fc3fa9af67fddffee472afba271e93dbf321a812473a78a7638d028edfcb59c3d55671f259183592b946785d454ebd818d2ca469672f7a37bff282924aae268fbd66d923f95a3d1965ade94b9f1e84c8ebff893ac7984a376519f5f30ce9bad1c4f0a45fd85affc1f687856e1e0beb17560b6f2fac17ad72807c04b0cb660301530651b6ac05beb794c11ee51c0e5fd85a45a81a449bb8f55a41c20651703410039bf86669bc0759a6efa5e02ee19975a6e7a29e551f0ef4734ac3c1ff3ff201de6c82f244ea8073f313d0461125099f58b7638882d43874c36bb7cd04722c74d7147fdac26432966274deccd7ac062393fa0a1725f8d22349a3cf54394d970966229fd5a10bc6cd2581c701d4413436f56367016091f4fd4cc8dc8f729ee75a2b06c985d6a673e8a96c3a91755f722c2d4bcb5ffa874ed85d4ff849e4f7dfe3f717864392e86525adf3003fa28ae98c42a718f4a297604a57db71b9e33678ee019ccd64566d71050d48df1f5695625e7fedcb3293e19bf1497b6dd93ae98d41a50261a2ef4716b403d5016ad5bff450586c9576114811fb93e2015cbd5dacf284a854672a7a003419d266bbb15cf9ad96b1520cc4ff57bfb3d0ac1c85d122668bfa247e6a3ba5c2ed732d22c8ec5a5844b2f44d8594a4b49766db2c2cc187edc68cdcf3118322226e24460ff73e68909b836fb0700951f925d07c9237d99c7aa1142cf4e4453330d8e07fbabd9d3c2d0d405cc6d6243efcd8a3e5709709c9f9c37660a0ed06d3c0958517196af2304277ea55e3532f5f573525cc4c7efa86ba13b869394716222054f0b089f0efa4ebc01eefb1c40ad870073ad5c6402aa584a22f91ed7e3ce6a310860929552b2d3246756fef45a362a76d15bdf1aacfe254ee4cb62232d521880a32490bd04a16458483415237ed529d94513c2d8016088064f56f59835a32728e0ad7950963816333aee612150a3a7b377c352c10b87fbf1745b68aaad73f726418354faea1a28f5c4e224fef026d192ca10d8ab77bc0c32ae97b09f44596ab741a2e85db3e179c3ec05e6d9180c8a865a4691a11bbc830d29de43142f377fe09943728c97a79d244a77c36a860e52a408dbfe624896526e0a28ba79fde171e892547890319c1185b292d679bd749a8597db2cdfeb78e0611e372890c2f732d96b1684e6d72c49b6703d6e6415b40fc7d7cbd0c0838a169b18aedcf289262b02ce95d2ff378cf56c0d72339e1718958be4d0ece0df4091f064c0a28a1e5bcb7751ecbcbfc0a26cbda74c2b2a2e87d1f56f3652cdb4dd7bda87db2f172192608f117e80ffdcb124a6dd19f024a2be35b01fa1cd49fefae6f90eecebaf6e45ff6551a5440cfe33fa88442a6938ea9f5567f97fffcc3151de8d1120d637d7af1f59307eb72576c35ca7716cbf294969474ddf8ee3ff40211dfe4049e2eb2361b18c6ec45f0023128faa920f4b4cbcf37c961c7cd2426cec03558940cddd6ddc016741f5cd45e83bdd1e96b0bc0a3a5034fc01035927e5635dc041ed106dc43acf1438272b12fafd0bf3e3d3d261405e2e3b920d98f440ba9312edd152d6230c4503304a4075f79c4afda5ad8264b335d3943eaf0126bbc032408a4062a1bbf06e6759c1f4f5d483a074a07a99b70b7e4d4eb30a2fe9477af6e574989d3085a4e1b74b896f0a97e526749aeb8113d50c6f30b4743ba0760e31bfd52f60352a77f82d2a5c7aa0dceb5d7e566507c87c6562090966896324460630a3caec373b6a0a4ad8858299393c94f61a17a73670f445297a0e167cfefbd5bd31c12214b66f67503e5a974861dea6a3ca293d538b1fcfe5dc9a88e9d42a329354382e7adc82c3c79d7f12f620cd56318d89a228d785fc163080fadebc83e8fd0d0d4e6c5a95f2f9f57e2a602abc10b2070c1e549808b09ac85d0e4003b6faefcf55bcc3bc8a7d75784c8c37aedc4a1925f56187ad77c2d28d484b2aefd2db7e4c9bc0aa37bca58f0dba3c9fc6aa8bbe6e1c2d083efbc3234691da6c5477ec66d62af522bdbee1dfe8056fb0b10dc4ddf4348d30b8942575f357b607f6a4fa835124d37c6365bbbe2df7b1f8db54e616c7f92ded1f51f9b73f7e0fb2b8757d58b325abe11b3a3ccca33ad1f8badb8aeefc401740ad06d059f9ca3158a512608b13b64acdb8e6c26d7bf10ca2775e024d4e37babfbab77cd09f507598bb9872664cd2b0904448314622a4d5e268496dff3b46341404f5ae150069dc4494d502582b3d5ecca726e0c5270b0f125e8d4e33c8663c7b34d96626e1d7d5e2d305b272317500fa9fcaaa973160f883b9c297ffbee24548c5773e84c0e63a2e6dff57ebe37170e505ecb13000293dfb5aa55bd98ec6015f2cd965b49e8b0996b371a79dac8a402c64b22a4683ae5d325a332b1fb1858eba5adef7bedbec8abc3d2552854a295fc4ddc1ab56fe91925491f8a091a29353e78aa5ffb123d48ef897659f46d229a675648eef1e3d328b82f750c5d16bf3bc875f4a1f1d6be224d4c6fec1a73e0a89583f6ff021d8d2ea50cbabc5eab9ffc8d1511b4a97fb96c48e9978be10914b765fe12c982789b734488d19f0c686eda00c709e6d39907b2599cc795d1182a6eacd0f03faba8fa5eb66caae03b751cb42186cff351f8207211cfc7c79dcc1f5a6ba585133a9257d059bb685adefe7fe99e6f9c053ea040cd8716641f2c6c8f7f390d9739c8803dd774b97687907bd8341fb7dcbc94173bbfda77ea1b3ca374a6191557ee82ff5018fcefd6e947cd36b91f20b33d44cab9d9a014fb98806c940f74e9cd32f377e54f4f2d1177f2d356587f7b44ee9377b03d275ab86e73bd079653140d3a6808e56c6772caa33dffb45904c26fb2fb51dcb342e346d7c514b5252f49c17efae46e3e79201e754b9e9e036c79722877ac687f40b284800d1829b91282efe86a7ccf2b86b9b55b0c891a442520ae4e3c03e5eda1d07f4ce693a34b3b03574d8163054bf9fe4b4be7c10c7de6bd6f35cc391283d2d057ac76c71670c43b241cdec5e0f62be825bd4295400f9a18a0fb345c9b252334c473a4fb33219eda54c9cd92961becec0906287f514251d1a9f870bba2220e00e1a0d2df8d5d48eac8eb8d75883c9c649280233a0b3f8165bddbb5de0dcfd156b8ec744154f7359e2b667a0cfb26614b3eafe2c0b67015cd521a48a4c781b6a1c75ee76bda2b7571ab28b33c225d518d794c256819e2ccee025d4629f93b0a8fe5defc91115fde264939753f9e3dda6ff77cdfaf90eb8e4135f4aba169d8f4f6000e1e7c1b3eb2ae6967d0727b37588e004e244c1ee862602c9a9eca9df9f11256b889fbcd3bab3fc38a11c4b891bfa61cba7c4b0cd06b24ab8025436675c55b82666627064d6d5b79b7d9e2737182c3bb951eef70ba5baf69064d4bb8dd5b5cfb209c79b3ab63b9eb4bd63c53ce8c45ca1c29a74313404287a1351a943db80fd098d07aeb4b865c4fcf88008fa84592b5642849ba786681769c6333965e6391315e581b75adc26339d24d154adf6f989e014cdf097f256631dc3e9ddae0f165438a738ea5b46269e510e7642f294a4b49d2c7810f1684daee80663be22e3827764ff50aa0b9e606a7be35062b29fceea4dbfee46d447658d5bec9642f1269b7b55d5942ec067a5041fdc8812ba66e1e95dbd7bb9ed16c6c631e7fcaf36bdb447e98af3b0b4b31888c59e0963630ab937f509ce1e79ea2fe0170111d61fa0d41eb850fe2ed1a7b1b47cd7010a09fc263d93d97d07f53f70fd23bbd7ca479836e258c96cba79a5ba356fc9646833c96d3045cc7fb6c6c5f00e1cd953b760b5c723fb969759c70e5538cf57699eb168c7cfbe2fb08950a52750e3640db479c1ac08417afd77ccf8f3510d200bb01035e1ebdf5450f23c952516a9e994b573dfb9fb82dcbfe6c66adeb695c489b922fc5bc2700b84fdff2f0aeeb27ff8887745fa2fe7fa256d6124b0c08d74e633880f49281f26d46cd1c88b0bdcc24ee07e8c2ef3f66a17e96647dac44b1269496d0eb5840660ab5ea13f74072437306f425e47d09b5ca6ff88ab1e3b83171e3aed341a9b4c1cdb86c92220eee6ef3a171f63eb81aed0815d1c1516345f7b54d0cd59eb259d3f16f77cc3039e98878e24c431f6b481f68087ec01ab1f056c1ee584670a418ed2767738c021761d97519abcaaf69a12061d3b89419ee339a2d2f4b90951a43733f56eb5ee94541ae52c8f259aad4cc937f65eaf18024223738cc3b57c0dae865bf65b217d09e5d576cb9923b293fcf2ed4d83af41bf8f5f89ac1172e87d3d1929badc9b98433f8585614de9f052f95fbf88f2f3b511bbed5b54603b75585da9a2ec473de597d51d1c23c6604d4880cfd5b15108cf08a29f0eebc5165444be53ee16d5279a6fb306cbc845ca15086013fef6d0c4f706b910659cd25d496001430b29556522c80b437010e0e1c3d96a15db2aec7ed844015bd6d88442e4bbd28968f97a93b9b317f08b3a375e2a35b928e843bb61a34c4f049dd04d2d72dad33522bfb3a099d74430fa8b48dac74cbc505adad24fe880e1ddd3fc2527e93656e504958f1bf5edb3dd34ce487cbb7f20687baeb4f0219246d04d96b1c178bbe56977fdb9d62743079e70cf90eb4fe57f51cd21d0cbff0a94cddb1f752f2d4ba341115b82e33d3a813102df996751c9e49b1eb912934bc4ec6321da197e14396ff1f376f4242fc872177a530a126c291c45bda008ff296f57aaed4cc224a2cfe389f4fb80fdf3f7f9e1cd05d2bd9f39d8831f258dde610fe5814bc26e58a1c81fe141fa35d9e56410d3b7d156d56edea2c3ad390b3c467b276521cb71d8e2d6604b7b678796b4c1e99c2e19b129ce34a03cd4f34dbadadff73f46baf424f71d6edc1a9aa70f9853a25f7842f8bc030921d1250de18acb3c8d0966258c51b2b6d27badd06f464b743d3ee5359fd9925f051b256401a0dce3e21aee69dce5ea2de85592a8d8f4cc12f424afdfe0080ceeeed23b7b1dfc85fe6e7b7e234e7ee104b173e2a60284d72ed39ad5b13cba179f43fb83f9e5408d07299e0ab8d21e032e8383b0b3ceab805faab80d2003ae5a1200794926eedf8e4fe048e9ceb8694494821c089b063d774d4a9f0726f274e3195a049672dc8e9379e29f90d1902c2894eb06f38d15d8e33613075cbfe196a83b339bacef2ae100fcf18fa06f21f59f7c2d53529f5092076bd3c26d6e80323f325bdf14d277296ea48c102b1c6ca05914d5498848cfcb51d4f54f6cb51f285a6da20abaf66504291182f8de8fc115b911477ec7e4d3b7cc6800ce6da09021d28fbc62ac580d2f7f36da5ed743e69fb1a34a8e5a34c4596eb662f7473b1009efeef256479cfb8a35db0bc256f74696eee863585fdbb1f8116094f5681744c16b15a7b1d986c471ee1c836fa22f3d483f76feb23fe2724b9c9a832a72c946303542a587708222baa3e977e587b0635c06d567eea8344adf12af5349c06bc4ef35e846cf101ea710c3841429d307fb1581029aabe223207b3d7c6b585d1a2734982e65c97e8afb85ed05ec4a22035e3217eed5d12ce14a81bef4d3761276d3bcc76f1ba6ad1fb174d5d9c0930f37865088fd0dad3b08e7916d9ae67e702575df25b9590f15d5a120383f9b13a179a014b888207f0013c0ef250a4ce0af505b00d24e478375cbe0b17aca2f8d4966b1790e5be20dace2c86928704a4b3923e509212575a807c33a6d04e295f78bdb4903de37f9f73f3a24cba60f64078252feb45e69e005aea3464c0a424e10807d4bc1055186da1243bd130dbb450fa88fabb43bf9c4c883f50557227b99b70b33bd5711d1a091620870fb225f054ce315a716dce73f820e6715619f701e11a2a6af7c6d55a01d6ffac1812e79b08972200d6bd1f9c1066c4c6d0c68d8e0a2bec8319ee36b37f79f6e2a8cd6992aff237218856c2b52d9101ab233b6c087ec1f4a55a2731a5edce41aebd4a2e55b83fad0eee6ba2ba0cc0eea141eee06f430fa2443f3ce5a0c60da9c78ac9c98afc9b9837d2a34dce6c366fbc31e87df266cf81160492c97c4c639f3724fc27fe23f55d865f993d49ebd9b30eb81d15278afe61081e5e732eef9c2042ed3b47a216756a0f9659ddd0092988d44e93fba1d5e4ee42f2d6800009c1e16caea460178c250b147b78c77580ffdbd387abc4700042295ffda0ac97a872d56dc06599c8eb4626338facc55265e128f8f7f7c7ee3c0d3edf03473cab342da54a791bd6ab9e69313bae91edc12d7f974b060b7b5db81d6fce6fa6d346f32c884e349b69b56139ba3fdffbea7b0b424037310a098148397952bffc3aa4a952bbf17fef9f67c4d36f8c2c240050f4bc6767f5502a7e7d372cf039bc0c7f791acbf812a1854fa1fca7bb59cc5791cafbd2a3f8eefb542d702845471e59646e276a68e3b7e23a21d6d99d8d5ed39d9f766235dd2f86a5efa6fd55631411800e57e78e37658e4ebbcde43047d62b15983df85d6938ec2c41b13a45c4b23c29020949cf5cc06ace198296f71541ffd98e98b1c9b21408da75cd218a5c972fc8280105f3315e4a75e71da955613b4721b8719d5bdadbfed86561d1d41bfc2a3c9d7d270bc76ac919d84c632948e8c1561c9c84335b9c1da27ef52e107ff5a87da71ba65aaffe53b171c5548719aef2228a7881b2c0709a1da44a8589b0cd0ee98bc0835e9ac4c0cb16621dc67b7cc877fee4eb8e1793261116d5dba7c79710f1055e5bd9f64697d3bfa927701dca066a95224981a7b1e8d46ff29003120ca98fd00c03b28a5299c4096bca6c3edb297cd638edc20d2276d33a6506a75efa9292c5f73d698ea838a22026a7a2cbba8237cda5ccc5409b29c5631b6fb8337b5d79d58da14dc0cf3fd8920c709bf88fdcaffe062514dc5fb8bcae0adfb67ab41aa869c01615918d9bf249273eb1f83337e8044507a88af8bfd6be5e97462885d1f17b13d8f2d63a82db48127bd41a8b1e6f5b7b1f8abd60089504f24b65e9539a616633551f3e007b86b7347a1a1f91fdce9921aa155a6891c8de4a1d476ecd2244e9e5fbe1bf705679f31e6bc9309bcef0ab69df07bd8c6744c9aa52588c0a573c2f88ad4d653d354c230bdb79f3590e28e7f06835e026aa635aa48c3d79066debe7ac4f38b896d2ab504953fed1fb02b65f9b59c069f7198227ef00fc77c3ba6af6d5a5ee27017ae64c17086c0399d7539aba3a157b97e8726ecc82b98a9b37fcc3bc7be59937b33b86090bf0671be1e46bac49daf976283e47c3e7e42ecc61393aa079e7220db24d08707a5f25266baf64007bc170badee35b6713457297c7e11fa3ef0662fba7e944537f5a8353b8e5a0823d20444b60121a401bcf51c71d000b39286724f105293a4f2effc3af515670dbcd5a74e59471f1a5948d07c628eaf2dd79aaba822482f2a77d93c3e4e7f0a7cb65fce985620b466df5040a6ff32bc1bfd466b2fd3171e9dab7c0effaf2d0e0a272d5323204053730a0f6b4ac9a69f9e2f0d46ac5e0e37ad48a55b1ba536c9dbfda21e41a32d978a550576ca76cd07b53951949a398f9156a8a54eaa51e6a25c6545b7057ec4c73e8d73cb3562fb010919da8a1ffa8ef319fad25e8a403a65b5d13e62c5f0b4b02ab6e26e1cc777f5689b55e39b0316906483046db4a2645fcae8ddd348c601c886119c61a734006e7583d2149af6c415c16419f025b64d58ebbd1470965f83a4cc673117a96f45b64df8461c1bbe4a0dac157131346a0e519f57f81a730a64c4e2cef4f997256c4245f307d58371ed87256a72995a7f761736194399fccba6a38a967cffa43d8ceaebf553e1ac28081e5a7e050cd65088a80975d580ffd29b220be3244d604932f5f291c922d74d873dfd5517559f2ef962993f2fb9ead76b3c51c38aec9d92e0f1b833edc286ef6dc82e5619a596aeb609e916f68b82ca910d013d13908c888963862fa9cb86124e030602811df2a35c96cd947645b54f5e2a31de666730da173bc548c74f970ad5d31b8b476d417cbedb116e1c6533bc7545cae1ed03b460e621bdc97823886e404faed8697d989963f89c33fb5dad6a4d26c5d8574d411846949ec9d024c252df9837a1672db3e10699042648e7d886b3efc017198654cafccbe66d4714811398109213c71f2422140b356fdedc9fa845b6abdde85fbbb8c17a66cb39fd3f1e66d103569296e019311f1921bbaf1ae5f76677454020b28cc254758a672e1ad8d57fcbd2d55fbbf0b3db6b8b8166384d92fd149ae0d4d0091e6b1c9974c222f5886808e321d2a0a525ff1dd6f62d362c300b30914c98ba886c56410569b8f452b4ee17ea0cd9a584c652f950847948bac28c64464ae6c00932311c90329cfdaea24ac25cae113c0608cd6b94bab317628a11e98d6500edc36f6282ab1fbf445f3198d08025f71c911184a462c97ef229a131e75a6e6221711238d0586400b18a5135e6ea111fbe783d706c330785d74b7254fc82f546453c80517bd099250f3c8970b7e11886603a9afe647870d9b8893dc79c1dd43bac2212da97f03ac35a117187f7d663ec0c6b6a7396f220e89a559523531ba5c62bce9493da2f28b50b3739f5971720420f01a51c78e5daf67077e305167647199169c8b0e13926276ad5211133aefe23c3a37454dc8e8435d974e0132e895dd668b5e17fce685d2d2b14fffcbb33b467c374b7536a0ea95e1aa11f5366b4cee54aaf08e57907d8ecd75ebce3cdf37ab8ce5580d48fd2f5e7046681cf806b46591c72dba4cda60954c1a2d787345b8d43f306968414b4bdc29908be912938b460f991e97e0cd8426382d5e0218820b074d398f347b92475ac98e6e3e5e101ca4eb91a3718134d3d055331fe8ef6f7a6c21597464b8d38370f58eccef19d2481ad3e84beb5dbb69577ad3a6acf93ecb98b51ff72e6e2dc437288ccb49b0e7c92620564eac6ceb94a4308d1f2d4cbaa36020b910d4f0e43797ac5559cc0d18efde3a05f1c40aaf641c5a64277a1cf20ef289043e21a5860e07a161ce2ba0272867c1ce829c50976c246c2996fb517dbb7dfc8b67092cd672395c08eb538e5d233a06f92daa3d9bbf8a58d1f63ce556ce9ff4b4468d3d84d0f6a0c7d9862a4d1cf5078756a9e3e67758ed55f72f56f01e2884bee32c3cab8175ae1fee55160fd6b922dd20b86bc8aab413fcecddd5efb82dc29ca69d1c03cd05558b045d703cfd7effb6e61a5a5d773ef678c3993090ab00bcaac5b8a9064753ea83139919c752d0f7e7b05fb1c4f8a9e66b6907878ccd332edce8af9a2d464d60de795522fa4475603860344356a0fa572094096f96b59e62ed82ddce9c79e419106ffd84256449f96085cd6f69107b1b0aeca38100133af723fafa358424b16cb1b74d39a2d07fd7657854b564fd960687244df49d25797d4372f488b9734fd0a4cc438dd20b88f3447e6fa65d2d36b24b6d87952a63dc66e71ffea844f47e281cf11ee1bb04169e3b45d444c4fa35a7ffa43f8e173632d86cd2649b00617b5514654bd7cb9d1811637b8aac836decf9ef357285db3c944f0c5defd8ba15ab9a4cad89b79555dbc94a23ebe517f7c609fb979e4437776d7c3af1695ac0f69672ee3bd4073d71afbf9b927d7150b5afc72cc2392c639f1dfa631f06dfee4c3dcb7c3760b902c0544b6531fa068b6b7e6f35063f01cffc8744b3e446d71438adc999b4b3a21585bf51c7ec77067c1b73e2e235a3678050dc5eda31c8078ea0460460cd87008ba210a76ad3478de16e3172218bf81aabcd324e5c936ee06c51bb743448c8ed7209d6e43cc6eb7b869a722800f5a95dd28b6299a85857cf5f9de383ba52bd5a466da642c1af3cb8441d3e4224489e7f22450d77308b05ff955fe83fd4d4d5fcd7c29f94022318ad8d51b8246a0be006732c5db8dc892fe96e1524f9e5bdde61f589b8687b50f689d079714c1b8fe29d2a538dc49eeb884ba9853ca09379adb2924874b06c365b78fde4395bd78636b600853d10d41e644e084a08db5ce99c9a0810a0ebb1a7fb07ab08054bb759cdada77c473c1c593ebf6ed7c1b4f2ec10f225982d032a59e4cfb7e9102d45968391ca8bab8dc41688165f14bf089804de6c2f81396b35d77f0d3271f488ad01ee40aea6c5e199ec43ddf9119236f294eb52ac822372a69b2e61abbb6eea2e829107245928819b43b29ef3d1296d05127bb31dcdc454ef86caacda94699caeb845a5444ea6c956a129065de0bb7c1ee0f033e18b002ae3fb5019386a20c0a4342a69187b40fe223c54411b9f8cf370a92abd858d16094b413e8b5c104084b1d084bfa9d3710e36a82b26ed774e733d4117d3558b51afbe9fe48a32087ad6f33919329e03a85b97aabbf362c3c3118ce3dd0a69a98d6fadc618a74b0ac7abe33298cc1448be9c1b55880f765b40e0b34a0c437b6fa5a6b3c1fe7509ad6d0a4143321e2397c18b360d3b82038609f89a214c7e6b51dd3a53b8c99af8fce0d12cda48df591605af0fe1a302ee76ce18bd849683a2e88239bccf0d5c47b3f34edb685192c7b1381607d60d2d90f2562dc6044832ad6864eb0b10895a3c63b24672e7428414a70145cf18ca37ff47e7e85dfdab3971ca8e812b4852edebdb4632bd72398639ad88e8411b8811d178c3376ba391dcb3f3f9d776f8ac3afa50477b14ed86e5be5b1a996b6b3b798890a7fc0d66080b8e1e3c274903279f88c66c930f9ecd94640e5b989a2805f3329d64f132ea2a2fcf0efffea160783963d7a25f9044c7c885a5f1c8e84d5945bed720652122804d20302ff2d2a8cfde589c5bd31b6fb0cc13592b2bcb09d773e8d84856b0f6be675ef7f33df50b59871c72ec5408b8e45ee4ef16e894a76fec80256d2d27dbda2b77b0eaeac6a6a121e7e83975dd0ae20091a78f14ab1349900dd56453b02c150ac8f1901669e08815a32f2710fc79a68bdc2223469d65ca3dde6974a7a69efbcc0195035198bdf6d1fb890bb9a9ca295141e704705c5aaa611634fb2b08912afa34225ce296fb5bfc6abd6dd9e08adfc2d9ae91e2c0b8b6d630057911d14c980c22f51de93a4417506851ead6693b44d5ec2946de6e4a4e58ab5aeb13c8d090452b74d2ccdc5b12f98372a61ad314562509f7f92b70149d2b6e6f79212f399b91dedda66f60a258d577c9e1dda19015c01d43c955d7d064f2692f6fac7f39457975c79694ef33153e1ac810af4741d13b2d95f32e42c1a2280c41616f7f8eb9fe019f490191b5f69fd92c0fcd0d3e5d4272601f85cb1f7ded140e6274cb6e186c990d31f6f32aba35dacabae401f3572d6584072ced9c4d8601b97cb5b5c8da9fc1de8b29e65055d7b2a76883610fe9dcdc4bc672cbedbe4b60180c4d00a5b14d40bc4c51f2cd2e19d65b9f8331bc79b83ce1c266ba54bed940d9b5e539f8b5990813f473d5f6caa5391f15c7520293ed79d87deff385753b54b055fcc88b1f7eb50f43defad9d65e83ffd79a3205235e3952c1260c0f42188928184e5d833104553c27c94ff28d3a1a9a1840bac83ad7cddba0e521f4f1053e0f697e967ce6dd7344c677e1b406ecdbb06ba9fc0923d7704e87a201a45545f3bec0c668112dbc0217ac680f70fe8e8aa388842516925087beef202f3ea441ffa1432303ae1c8c9c5f6acf76331726f22a7461ebf85dce2210ab54dcc03cd756cbdf1af42f4442c75b06bf56736d91534a77e0b2478f0e8b2fd5d3a5b8bad891a5f2292c25640d3215b968a84236f7fd7f0c3476b673387238bb55b5f3290ce301467448de725ccf5ed175a42f1c5b5221e1dffdb8c3a1a6bff8a0e03d9e92d3c1aa0dcc8dac0e4082357ebe00d3aed6232fc69e7b52cf448a4a179c117ad790c52eb4df33773afacfa6a6cbbd74c60e40fc781c2c3e0ddbe4dadeeaeb7cc926ec4340d0c5abc6737375fd6fa23cf4b87feef8d912c4c2df6ce5ebe08388eed721957878a720f710be188a579bbdb55372a9e0bb27a41cb2e8080d1c14cac5d201fcc0ab403fa69d6c1ea9751daa1e9f925e6b1fdca7ec228c1229959594954c649ed06d6649a0da8e9a92368b2ff518f619294b74e0c323272d827abfbb1f99c2dc4c2e342665d8e2a929772e7ec9c91b87ba732cbeabca47703868a80efcc04046bedf81b41cdd99caa175713b59193e57124271c12d1263b3193f66a2ab3822dfda94cc4e26b091adae7834002440109b4951aadbf5f762987065ef1dca088aa17f932e48d41a4c39bb6d271b70d6b27f986340015e1db343f60f67ed54636a1e9a1e476165ff27bc3d9d1199bf972a7081f582c06bda55034de6b31096d80dcd4fce74b38579d6752afd9a1d1e8cbd7e2236159987335f67ec5415d36e5984a9e411f863d1cbd755b9428e727fd9865792bf145701e39a421931206573f047c2e3bb31ef57bbbd2f7235cba2d1203e6f7fa4fc2b025a55bc5e57f6ae3a98528e2824b5ff9b3c5714bb0025f204f09539c5680352942e31f86320a75e4a7ecaa6aa5476946d8388b9927ac0fa51b69871fc4d019d426757bcf96384bd686db7e3ed3f6b02006931969d7d5765301f9c2ad01de7aa56bfafa485971c25abed0c799fe1d7a4b9df85cb9bf48361044f9296f33337ba99322afd2eba3e5bba318cc0e8976648ff9495646680662373bac43a00972d0724895ae91806d723e1bd075a6e231de90661b4edd69ac0397b5ff7d3a613344d92444900c84b15d4431ccd290bcf8aaf6479e50c11d4b9739f22fa72c219f3508a9eaf3578eb74b3fc50d9465b14e61e0afafab1f25088c00f8137623561013dd31baf8529dc01873b06392f484bdeeacddcd7194ba89ca0e563200e2cef8e5950d51c4f3a9ad5188120146a65dad10115fabfabeff13260397d4422abb9dc3cf0725ffa3eac1bbeead184d6c9959dc7dcd7361e6b875f002f10e9d350f355dd5067b58ad54c3ca0f7e01eb4120e43ad7619b05ae285e6694fc1855bdad61723d94defeb52ebc2b51f103853c5623637ad83f0adafd793e497c6311939a0a5ad96acd284dc6683ae770b2242c0f6969945fb6d62385c120397f8e5ec9b70f7df363f48f879aec8c77209bf9cb252bafc889df941e91b62a4c68c22c6ccf6cdadd5decefab8dc2678bff33d38298286d277f3fb42bcaec6b55a4f54fc9ebf181d0f5de8aefeffb45d2d61daab34d602d74e6a5697f581b68fd1858ff4840d619182fdca44275ca55349b3d189ee1f051bc40662a351a23997c967458d624115b9461f3082d6801fab430b7dbce1fc38b3db1be3174d7e8ed25d697dbd7e5929486095381baabb24a25b78755200ab3629c6b10ffa3269f42c1cfb3ecb76a2d27b052cc62775e4c90a6ca580c4b8fc5b46a42fad6b0f21feeb56f4552fdf43a09c17818ff7c30a4cafc61292a0653db2efe0f3b3d60d5413905f8b9b9f379d744165727328ebe2d2bb13494fdb7293bd696f37e80b5b7a9e014967033e458cf74c8c18fd74384319a04d3ce78b6294182d59cdf2e666a759449f7bbb3dd6f3c4d0a82f34f49373a32b2d9f7b11774795e821fed995e28407971e636910dd12813d70fca01a99d16c081ebed611217706aa3098a5956356ea7fe3b5609a89a7420f1628ed52831be48aa5ab467eeab89fae2b5c2c9740a61ad11ab61bb60b3aa613075bdb7b6211f8104ac5d4585924a3446140d8905c331095435e42c736d66d920506c1ae5885c540d5e8fa252ec8c9533630bfa3cbbd3b1a24f34443b526a1ef1bf7d7277e04b60b89df5c614b9476dc99201c50c930d8d8801ebcf5d988ed7b005a9b9fe0d117912d36821fdb6b35e844caa5508078bfa888900fc80063c44df2294eb85959025b42f1ac470e8cce135d9fd58595166483a50cce194443eb649983d894ec50f1e04a9d7e658e169c14a5fca42c404ec3d303455f4abdd56123ab2d21a9decc2d349085b60033e649d6133b2c9bc930891b678369363742052f5bbd8a695ef39e68ffaa23293e5976a3135426a8af5c2705be1dc7edceb7f048ab4403928ca93af564a88fc00fad0efbf7257e37c1b64fb49c40657438717c9ee5e96660dd421b716ad1c85ab971a48e88bf8b730286fc7741d4129e941eed770c7fcc1d2d7ab3e93cee33878c6426431901cade39a5a5c90ec92deebe1c37872981364f74ed6b7fc83a2a2833ed287f4509651d9e00eb7dd67a7c86e87588979d8316b18554446e35053f661e228c9d3618072cffe8ac8c4cd8e1963ba44fc8deb9b782d24ef49f9a7ca89ef10ea94804113a28244b4f53edf9c6ae038dd56e8f17b2fd52bd5cca591b855b1306629d264c4b9423c4295114d03380d080431f0dd6cbbb115931367a2007ac516391c2c1eea539748ec76d8a185629789b6bb8adb5948d548b114056bd4b9632be7d4349be54df4a17a58e0720badbc979e36b9e24c863ebe70fa93cc97f7ed6ddd0cdc72014c7ea62070d1e1e440e3e9ea76878f882d6538aaa446da59c090bada55b17dc836dfad92e629b94c28c24339664a99f273d62fe323113398c273f372928af295b62ad39d5788ad12d519316b832e836597e106143f1bfd154d7aef0e7a7a0185bcd266a453c68d5f9bfc4fd45f1660f95a5456bbf302edcada935086191767b3fbabfa45e0e26b3432a76b4f1fc9c3b4814765c2edee0252819ca3324f10b2ff6a023aa6f5bc089d37e9e96dfe703b6055fb1d670f71e7ca81cd6b254f163509c4bdff39009e0c8da91d4d23c869e76722c411d8f13812d4bcbdfbfc2210df3c08965cbe6194d6066cb3cc045f5f10d0bb065e6f5c96241f38f23dae22a27327f9577f01ec9dec79c94546733abcd142f4448481d0cb21249ffece7b7f04de1a9e39b1716ba2563eec4409cce1673326273a8785ff306fa3c0350b3dbd299871c33ed8fa1a3e6eb1652e3f0b72d54c7bb007dff35025c6f90ceaf1093435606077ad3197295cedbfa855f2201f624b007171169a5dd281c4193e005b8f41b6538c92361c3ea145988d3cf597ae0376b04f2a234975bef2f7238565ae4e77dc4b1b56dc38f0a58ad7d07b2c14f8468846b230cbb41b2fa254863333ce44735387fd5fa34e4d0179c2923bfd9b6a9efed6093ba912845baf1b455f6ae519fd0c1152cc25cb55236403d63be29687002ef14434a2871d6fb0fd86d008acc80dcf74fe2f18ec7cff14f5056d69e963d640f758c8ddba045cc18214a27714222681f1276dee632c87e3b96b181c9d2a3a7386bf0094f58a7daa95dc71fd0e4af55dfeb997962941f4943db7981311978528daf5f0410fb70a496f7021162a384f43dde2e9fbd98be3c8c77fa23cc67d30b40dfc721706c658a3da8726d21036739a4196c843764f3ffff3ba1c8b7842ffef01d9f3af8ba4ed52ad7b6b9f2a65a292c80cd8f6f5c7f980bb4489a05c5f81f974155068fe75622e5bc9bf1dea3906a81c3e2088189d97b5bc5dfda607e1dfb08b3cc0e33f10165dbf95d8e0aaf9e6b0236d41ec1d748aaf122aaa62bdebfbd2a561d591997eaf3e9023e141584032eb839cb832925f5bf9de33f5bb9e32ddc8d6988547687e70d0765f9ba2797f51c4211034532468a5ed1ee32044797f7086c67b483d9804587a6d5610c4aa785055f5d6f7a0940be023850c8576c63196120fa6430413b6146eddbbd012f9c453cdbb3d529623b8f9bbd66057b8bfb88092643b8744216ec701ed63c50a194f5f44965720ea8d54c292bbf903594a1b63f2f65f5a566abb95b094c98f14e9d76cb09c7d62218fb825a907d11515e5736f8585bb9708ac5f0cab5b8b7a7e4170d2f6c8c8e00e18313648434bafddae6e25378efc19267dd2512110eaf972a302f9536399dff2e2fe06ac9ea992e614cdf027db53388c6dbe28b16a4ebc0ef3c962934611e42cd3d6563f8d93ba3cb4e617cf72f552e49e81c2a14fc04af80ad501d2e79a88b19a5f0ab2fb9682678122fafb979c6f5281ae61d74b6f391ba288dec40a5c61b673b8767473a25fd656809e501804e6aab78ccca8fe2ce868ae1b0ebe70c8f04559f07b61858c281da111920fa16f1c0147958f005dedd2c02918f9e99d11acad0d8c6fa1848aee40fe7a1657332254a11df80f741d4d7262e0e1f17e05ae0ff86905d70ff2b965974b75a0231ae685c7ad186e3a280aac8aba281bd98adac3e4e548f2adf79bddfacb50a6b3f5874ea5d00b382d9986d31ec5759ebc4a14d110043416ecfa91c954c66cbe268958eb88661cec3cbd483a8717a432380d9fe440521fd7d39b65d9c03abf0d72f8ae4637314a4b037878501b9764f2e19503a5fff952035d6ec3a1ca125206040e6014cb35c77b5da96655c182b98268458a160272886d775abbd4877fc475b1f04efeaa8b79fb377e0398a3a37a89b224c40f04bd738793d6410a910ec6e3c56bd7e068de7c62f61266471dfad373067f04be0ec737ccb31fad181b5d9d40518c61179c109df5207148d340c0c58cf4a5e0395bdeedfa50293cfdb236a4e30ce0d4d8085ee10842bd88dc99a5a163fd56b42b43f922219b3e8d545e5bc7d971131fa4a613fc311d5da9b50c3a91822e802080f1765575e1352463221f0884da05b2bc5774db4aaaf94f73f5970f752fce862cc54c7c012ba6eaad3393fd85c9ab03b763a91b447e83c68ce87f6c1e79838d03981e7f1d4ac6ab2250ef5004b040091507ae8fc78bcb7b0cb7424a60b195a1995b94bcb9f47b96d005803e33cd5ac4f2a9fdbdaa65d580509808221165f37039be5b8279aba8c130e8741935016db3c28f6c62316dc30c5b9b3ec0429a216ee8c9a8969d79f1a3fef587c648ad235c1a12fbea1f9620f9b2c135c2d8f415527bafb4b1bed71598a0b19d4048fbd2c78eb8a3f89daa5645d465491c6f674f97ba2faeff573b696120ae2b137726de03a1bf1ce4e838539130cf349690aab4fc9654cba1211d2aa1b39269e1a09f98a0bae04538d694b8a5bf159a4c3df4ac046e6cb02c3b804061d4d60ee18726a3343a6d833326977bea1fb71e2dc1a93c775e792c282644658ce9b8a55ba33230d78f0e9846a6047aee467fec7eda05addfac865f9390cbd2747f314a2d2e45eca669bda69923c768e4c1a6d75430ed6a0c3896bb5befc1664b10e5014985fe7f43f0c1e6872d6274218648e51d196b5691fd17f5e7e8098cab5c56b834d610c35313cf3b1da84d11a051916f18b190b144ae1de48f8f1e5b0f54db50891d6ac0ffe58a549f2e20c57f9b4aafd840b03ac252bc8b1f6c711e578d162e74752f9d740b162a0446b3248014a304c5f32ec3e8ff4df959588fcf484ff70438b4634d633364bb755fabde89b756f39ade66c15b9b85716f2916310a2982cfaa5a53c281a677a7d2aa0d6d246a9868e85553f101b0866d8d2ac5e0a81ae439b3d21fe808e3d1d92db9cdf3002d5f8b929401158652733e4d67321c934d92f2d1fd03593549abd44cef7ce56edcf8094fb2ff4ad81522ca40c6eef9205d78dc96132c26c8d7c3442bc88542e48004922cea433eac0feeb76cf32288052f774028d03a7787f8de7278e79caa7e2f36e2073146a8f7ecd290c91aef98e05bb6c1c0f2d50aba5c3cd716fc5103787a86af151c463b751647cac3721704b0278a062b0cd4d3b0e99f8b9a72ea29b571af695c17f70f25e5856fbb96c7cc5a0af0bd4c9de50739b5ba98d1f7ef013b3319c53aec0c1fcae63b2186b176e925c8e3e79885d3f52b12072438cb1c35b50b98d2dc9762246c32c76fec7736633e1a0f0d98c1ace2deac695382e18f24f0269e57c871b586d5cc7d282af3f62035e795606a797dfeafb7295416d772de5ed0c20b54a22e84151dac0a711b89fa7e0b019d415f693a802712538fb27b669643c15a2ce934f1ff703d7d65426a9ef2b189cbe15d436b3600ce5b3a77f442d20c514cd0b3404ca991404a9e4da26c4b3c470b2397fcac3ab1d1059b87320b6ff9793d1384acc6f067ba192ac13ea8abbb01dc77b2846cf981638de8d7a7a7709845299da6bb27f2b07861099a344816f9aa665d7bf5d6640e95778cf0f70efc4af0586a39511c91c2d6c3b0f8b23ff30dc0bbebcf23ae71d33f28a6c0ba8edce4fb754a82119bf75fa663879b304d8f8c15064adfefab98cb56e18740d719fedcc5225f95e7810d9eae876b29be3e9a38d03e39b8eeae561b1f7b38e49fa9f40d8370a14b297702d872042c4c6721a74f8c05892747e4adcd21c5f2dd1a7a7606c3c35d89d365cab33347109bac00b061f5dd9fe6db03a2a0e8e7a33020cddbe9dcfab2d498a19c365c894e5560b291b9503af5191d7dbee1d74f709237eba4e0e7f3133cbcdc73e9cca794ec6faa8855bc68981f1b248e5ab7a12d1f604cf21821e2c88cf29e8f0db755825f64f9bc6f24afabadb487244dbcf9e7da2b2e0b57e13c6cd9c3d9f1e0f9e62d64a367b39631a0e340fa9a9380263e5b04d95fd91584747050278ce2f233ba9d62bb16d955b1f90adb20c5765dc29e7c3753b86a37e203976253246c88fa5e4f3f4431006e3b2b7d119ebc6a1ae272f05f467d714542e77839fc73df10e9f0b3f51ef9db9d15faae6b47cb8ff89f87947badfdc441e0fb007e7a984eaa7ac4af021ef381fa9795a2118f3a0b907b98f42c87c5dc066f4ded6e942d10b6df6e1a9f6006f0d793d573ca37b0872ca41cd41d9a1b583835e15347e37e3f7d416642caea2488417f4acf4a1a35b03982cbaa3173ed7ac3a7201bf50e313ce525f46724447a8c02673d77d3d578b7bf513b208807e9a8efa39f7f1f5a092934e5f73d99272ca53ee9bbc11f86a6d2efdfc4974fa446ecfeaa9c907d380f6de563036eda8d705e9aba5217a4d8c7b8c0bd679f6716b1755ec143b07ce940348e82b016aa9c1ba9f37727761c1b45299b79c280d333cd1467d36e18375179edd1f5a945f544cf58aac6b8f06a1d8a6416b252b5fc301dc0adc15853ba89f2ca6678c681be99a5a57805aa2ac60039afc413196cc56b5f12e4767ac19de605432a89c141d82986aadb0fa6df374e9dd30f946d076b9613ca48a99315e9646793452791736fe655ea0bd6925b6bee429d257b489d386c8448a829b3d84ac11f39623e14f66560d8c785cb0500ccdcad5ff40e8579095a8f13ede1ece49eaef579eeb4d9217d666809226e27c4f3af306c37f1aa9a9bc4665640e54de9c4e6cbd9714e0c3b504ea55dbe67fab16951c99f93829b32b2d6b65eba3389ebc60fb7a4f1d323001f72d1d3750c4964b07ebcd5c3dc18280801e81da313cac24faf336a6534d82268ade5606937528fa4ed616ed5357c961296b5aa4222139871a6f17e3b8ff7c653e24c5a18c8ac782b2e34d074be194b932bf2bf417e936fbcef1f77ed5a2c03eed1d1ff4842bf7731a3c0e4494dc049fbab1e3a9aad6599a49900afa419756731d188ee8052977cf82c859764697b489d416c9c17213e3e3937d2c7ee4d0b71e12466ab0d39e386b65e225bb9853e32b085dc8d6777a9d92a41c92182a1bb18350ec1209d5d4d2d84598aa3ad7f68a5219d7656bae19cbb2121e96512703d4251d23a17668d80199ba6fd6ab4b21e74382df365f34b458764e109fd3428ec4e934660c1e0a5a988e4aab8adfa3257260752d4cd692a34b4ceba3c985534618bababe3320fae61ccd78d2dec23010a75ae009ac3380f430f17fb0eaaba5dd1be87e17be255a089cba8ad87ad164969d16f7fa6222069c93666fddfeb99dbbca00d3e4d2de466c77ec6fafef9d8fd30db8fdd71e8f40d0a1d3d2f9cd43c80755ea5903fd4accc0172a9d432dd7c6416ea0895deb7a172108dab5bb42535b63a17941ccc7aee646f4af2415deb1c6020890dc0dc55b7b28fa0d6b61562a13f2f6226949d284cab8a3ad89db0b6e89039f75a8c0c2c8dc9b747cf633c5181e938c5aa5ed3ca75de838ee7149b5941989da55bd6bbcaf19e5fc4b86e377d1aa5ee3f3cb3d8b060579721108bfcf1e513dbbc19166e626bc954303f759ab66cb915bf5c52870e105f05e603c5941e302204950108194a08281cf65d77e68fb6fed2aa5fe8bc7cedc44663e5ee108760814f3e74bb637bbfb5f056768ae8df0956a159a462a176b1b77f3146da2853fb9bdcfc29f18014613858e3b568ea41f0ce5298a0c19bdddba2207d758d2db0341485c364f3b0d716ad3705cd1656931f2dde29bb315a74bc121b8a1efe434f6da0a6c0f25112ab7494eb78f5199b7b3ec07e3ff67461640be10e2d371148e6be4a98d0002c2c1819808acb48f8b8ddb8bfb8036ae74eb8112c5a503e3afdad98650e06bdf187cedd8322e366fdcf66a49c28138fa857e261da046fca53c3e26bc70829c9c7c31e986c74756c7d7e05b5fb9ffe58b3963420ad161e688b7f45681f07337df6c17f69de698f39109e6c4149f7c920ef6a68db220580c87aca9c487527d3f90efd0ba0bc450f087f29935719276d5f837d19f5ac8cd13fc8bd302f2683ec0ab093ff267d582a525eb136e1c5b05638f807479af976de93e8ec4e92f09614da14523f8749d4e7088eca52d8fc19e5d3ed98d74b5486881adc2dc838e1c21671d7dacf770f37a21a1a4fac8b3043f08974e511f75efc452801d8714633f7700c91135bfee2a28a49351f4af31bad3ddc4f2c209ea5f89fce5508a5587573d63d4225b01f54672252484df714cd550a41ae4f9690ce39b3eac24bbeb28515addeacb340209af0c3614f45c378e7b188bd1f11e53369a2b2834ac9a07a371691d2096aa354b64b19ddff8c81ad106b5b50d5cae65b9d40637933a72e05b339cf99c49e1082f070a276702f48ddb8f81c2c739856333d7e9f828cefd232e369ebb3269be8ce4e9033953dc4f371b1dcd7c1ff2054b4fb8cd47ba0a9f9db81691cd21c072c43ebad720a6447a1c943498d9bb91ae5b0c91883053637954d95cecbd97f1402d948f65c194ca435fbb93ab306de30716b9bc8c372b44599d570bb18b78b48341d0a55c1ccbeebd6aa8b9ab28eaa2a43bf181cbb0788cc815f8add55ff6d2db05fab02c39dbfa971eea0d6654723120cd9f9c22b885fa3c720b711886316c9097422f05b6a84b532425d739ee767a0607f12a1e4151d346f55a1adedf2b271f4018631d4bb05230f8d6483a0b3a16437eb4d504b5f94ad7c0c89d6722d827bcc651ecb4e5de1b23d354c9ca637d4f16e301d18da1d70e57804a27d3ae6e4460f21828e3c42893ab844aad35bd21adf44361a009fe4d8c8f097a0921bf6d968dfda9f07e6bcf803c09d348c610f1d8ddd3fe1d9bc3eb7d4f6b947bd20be4d0382f0d7d3e8046a11563fe25ecad1c372c20463dce51dab8f9e020a77077ae7e5e395871fb10dc686c4994038a24e792a5f5931b1e598126981cb0faec3623ebb0e86a77005adc83712f36ed663d77d98846788a219fab9b13d5c3433fba0a059b74360577c11137f91db71307e741b4ae57616eff31d0f9cf226cc148a0fd649b4e9b5170189a60f54b2d0f3d94fc29c9a45c4267821f252c61446b0fdcd88b97f8bade153978167b239af314cef57539ff0ffd9c3607d3f63e2f9b418a33d5ab5258eb7875c59e7c63bd588233f43ff556c4cd380c55e87748cb58a512f0621991e1fb4844ba958ef3e56969f6cff09008d178971659ca62bccdb8e53ef5676925606fd853093d8504acb3a75903f3b469f1874ed70dec8bea325637d0eab28a8c8a74f9b8749b351ba504e4d29ed4eb690b97c5c71982fe5fa58196109e05f0bbb28c12923feb81c25bab144668b8d3fa84cc0bcd7ab835e4b865f452073e28c7f78fc66228ffa214fae476a41c960b5ce10851fdb5d6a6a5d9ff0a5990a3922c48003de376ca9922f9d44456a4c8d4f6746522ba7ba164d147975afd4f49f6773df3af13f6e0be804c2704d00ff112bb8d6c798dabfd9d14a8ab2c52b87411925cc5153279b105c79a36b6e1d6aacc9e24fffb655e8e4c3bff956ffa686e7991e0b6a8a21101e6fd7c3bc8930e7a88c6c37f6e458f38b7512cd01a67e042d6fe30fbe742f8c0e624e7381f227b2f43d57f5bf1af300543bcf4a1aba6d583dc455b1e5bef5543ebf46f6d3f36b265f87c9d90360c05d07ee8be1e86dd42258e66611c46de36fb5cec6719c48a9b0967c47ff9955f897d66c1426f6a6aa80a5c8579a1b59a8692820e74f58a26b3fb50e7b0bf0a9ccb74815a7ba158042db607f8933bfadfb259508570e8924883c1f978c103da217b55d504131abc8dafd03c6dd0f5a69b271b61df1a8e7e957590f520b11729b5a84cf04fc3aa782015be77ba7b7d1e0255166ba5f122e7cc2b768df65e842810c55cf1f78b67364c6abbf1f2b30583aea444c7ed16c445e183a50c3f299995538dbf5135b6f8559bd527c5aaf625ef3d4fee1cdbdf4e383bd039de5884844ac6bbe5e54dba092ffd02c4d761d25b95a5341ee4cb9cf4dd5a3746713a858b432f58710db5c8d7d2ae5b901df3186751311e312695a3188b09529dd7b317f332273aae3a61dbdda5f49c786a93c3f0b96ee30f0d02d88b06450cb0732b0be09d0b17c1435f2b59a59fb05030a726dd6ae4728adf7baa455b76eb663b13195ad1fda418d0f123ec788b936f6dbcaf403fa89436993980c796023c6fa52aedc3d0bf098be3bf47b748de5d5c91b752e666f1bd3392191ebca8dc3cb36d701203321a1ed8dc73b85fec03671756cd4c4f9d4901cda5ed754f05e217dc6e473666c0263c47a45b42e6caa3c11349412df3c4434ba22cf4ac62767b6e3be80d0b779e4e1fb9c495e2f588bb160cbfe3ffedcb0dbf4b13acc1f6c329e969a9810db4c10bf5d87ec8be811d59c7b9036042eaa5b0f12ec2ba2804abb6049d77c921147659f186b4c0d65bfae45902971de6a305c80d09f70edbcfbf28c79b960e2d0f0d489fd110a6d612a73f5bb0602f65fd21fcf8c289e2d5df63c23452ddd12bd86e2959a66ca3c6f8a308acdafe67adec2e4a1c8dc8ad5591af3cfb3d26da565ed29ea8c4ab671850e28d9d74f3996b0c43f0b0ef5d0ef04cce1f5fc02f202878bd70aa46cfcd08f2053e1bd5abfeaa92a964069dc4fd3d3cfc98894032f84d2002d78b0a94b6ccf5ddbcd22c45ca5172ca6a0f208aae7fa7cdfb708ab4d0a7fad019d17445a2e1e079ec867029c1df243ff092310c9790de94a68fa72582724853c0d87e76277673caf9ca1032c0672fb8381eca756a01303a97fba51c0d6a1b55d1461ff9fc9e7aed528b04d5f2deb32eb28d1aa4130c8308bfe55bdd9c51641f593bc5fd28505960ba2e1bdaacd0eb6428572023dc1f0e4f5ea1ae95b2c27c8293212a2c2b7d49afea5cbbf286ef7aa6815539140c201d2e10ae0a5e234166833d8f0dfb6f2971e921d445e1653a0359038c4d1079e80d0b96e994c6ff573bee18053a7fc3dd0b85eefa344587eaa7bc2643ae58fefd54ce0cb03c2e5f761da1557721d6611ae092b82bda1f33f910e5aad813c9dc6811429a770f18d52b02143242946c861f505780f31e19156b0a487998cd637fce3a65e4ffd13925055f6f60e4d4f7bbda6b1f9c5085b00a0bdf3cd4ad75029970ac2baa7beb886aa01374ba98b90dd78ffd614bcc66cb359987183f398a9824038c06fc2abc9db4ed38b734fada9cfeaa2a762b24eea3ff18fba569d3da114c8147f224b51b86bba68ce36d654ca36b9fe5d34a65578df64845ec43fe21df7b8b9f56ac19794f1e0d6c26b8996b77a353ce50c34412df13382e0a226cdd006fe0c535483260f8ea543c5817a009509c950461fcaa0c877dfbbc697a81ca360961b7ae2a72d5045e32606fa1eeecfd571ff18daaaaaec97fa02b39e2d261ce85966f687a9a01a7e05746dea3e41cdd24edd7f5abf66081bcb229e88853f09c6cd520eace02beb87302e437c33da22793cab46f239710cf279be7a788eec2be408326e2fce6ea10efa284a6d75693748819692a9087f97add394b6652bab9872a88f02df3545c9a8b1f70f8f7b741c408a3234b9170e0303994b4b9b8ad7bd63d4bafc010929c94e394dd348e9a75766b73d131264eb9b4d5f24523abc62bfee53bba8ce20f711db6d4e88100dd1194238835cd4dd785d0b013d58b69855f10db224b0f2c8a0eb8f1f9930f7ae181363f86b12d91addb95d9e3d0355f759c0017279a10f1a94a18f9a01f31c2f59add89b106129d986548a2d96f0b434c444cc5310183824e5a062bf481d37f4a9f6b88bde9e8351ae177d9bad1b4309d711b5a8adac76e9819175ab4c80102a7aef9c122233db59beb8427ca481c4af3b90c8b742eb4728fb9298f1756505d0d440208907b4d657cf928e4b8d2bd05be857543285e61af6ab8c806263b28817a8986592e56d4f039eb9353d3c425200ef8a6bb850a6d4294af0436516ae9f122a0b9268e22dd6f8a74ab19d10510966ee27656d049a4e962965dedf214163b2b4e14e39469a2c1c584da2c259670a921fa85d9c4e6cd414bc91b2af9d788115b33048920c9bc9ca6ce6498b395a3d7c3902b26c1812a75f18a003b2e089bc267a67b0dd865556de013e0b712603db15a55888a683b27cbc9129785c4393698b26cebbee2106337fe83af4276ff9f69fbb2e45872c12fc76aaec9ee56cdcf1e1d98e229ea093f2c6362131cf61eff15109fe14a62180422beb0f65fdec665250f178a3765d0fda32393595b1f88d0450ce6965e843df60e068bba11524369f620f267f71657665e62fbf15962e64b9f1371c6b86004bdaf203828a3abcd37b39138afac27defc2bd812f4ca05c4172580c8e9c1f970228cfa96a3c7b343ca2085740dfb6667254304faef8c0431ce055c29635df5dba397c236938e2408d61e5cf19fa9ced3c30038f147bb6a37a8da23fa8b4b0b52eebf53abde2fd008619e5e8889bc59fbaa3cb1e285a9d81275464b7065c84a0d8945741fe01f94e27c08b3389198e308caa4171f7c21080e8e3046322b4a32bf07cd63f904143a9885f8a478a4d7de42b7962c45cfedaeacc40580f760816207d608c5ddfb4c9784495cd6bb613709344f5620904023658a34113b8f2e714ab12f11144054dbfb5b412dce2a6cdd513313972e8c9e39b011d1292ab0da390f0f653e98c6b64f52c971d85f438dffc2042154c8aed5d273628b3eef0fbd4c4b9e39fb8819535c4e7960fdf0ec0346f0e598a9b115a4c8488157867ea82d5dcb1416154b97a608378a6d6959842b75eda1d4f2dee3f33709a2d87156e0b46a8e2bb1a9a5bc36a922d0d73894ec8db2171974ff27351b8caa7775df3ce51885f634dbf1a16c63bf16c877cbc460214c0333529d25f39253633934a33902244786b7738950b5f9f68bae09b92f2bd5c8e1369af4d344de94776b1f0e5bc10e4893b22e2404896c5422afc0c5fbaa5afdbd4c967f19710154977edc8f01df819428ec70ee88493c393425623430ccb8fad4a550d981fdae086e67b9d54c86a536983e97fd2428c0f128da9c45cf856808c9dd20f7d2ecd2433256e0ec4bdde4cf73685f07f8a30077f5b85e944b4878d60ef84101fd667ded4170e108a998d7e3776248c32d7996b9f805420de9fd2c25dee5665f2df16b148a62a6ac0837da8fd446e7449f9e6ddc3ff870a73a59baf0029689be36a9d64510b0738d6e4174dea249a3d6bf97e68e1f457dd240f74eb1a356defa9c6a060b855016966930734bf50f66f80466cf63937d3e3a92b3c8feffba3d4e9765433febc6e415e76d5e9c35dc5077eb860d99478c4f95b5cd4b75fbd35712788150f9bab9ed773d5df3b3af513f5ff31ba4307e3491197664305279cc02089a0bf4bcfa06f98ecd1d5bce297cb9fb3ea4ecd4c77dab06722591e28c56af16976a9e1034c63c2b3c1d544919d335c92acdff4afadda7189e0dd2ac9835125bc9681efba83a9eac066aa6ed1e245faeb8dcd97821bd080b521505860d55eb33ba5b73610fa2b77ce3c681a9bd6b83941f2177b261fba1f41790b58b04d90c67080b99ec9d03e246057d9e916da644eeedbb6c7a5d413240cb574df745e823c82f30eb0f76e3c82198abbc47224b761a5d81c88dbd48979bfa47027ff53a329d8df491a263afd7364d5d156c69c380fb9120b2e2c817c9b5b1afc6ce298c9528e08d42f1c15ceb167512f8d075ccbe83651e90dceddfd1c17fe3470b01209b3b6e9de642dc642454d11ef1d0fc925f2d1f082dda13c18228d8f85f7c5a98bf96b451dd419a70310d9b8c1fdac803e249d1f613b9b3ad1e53b2eec4c274ee109b45079bd7e4080454fbe56e7cc7bd0ec95e5f926b9d18a3453438bbb6c0bb47732aaad01d7b242edaa92acdcaa25dbaafec392828e811af1d226c614bffd0c10ab8482a60b0f7a62242ac09f1754063389f504985ad02c90f919312fe4395f5154af7357940034d4079fe935cd531e4e51998894b1a6519c02b183625bfa5dedf965afe0276ba8c7dbe8706bd37a703aa7524b06b5475c3335468e0c8aa33b4aef12b1b5fcddeb93d7e18a50f4f2ac27aa27de62dce797854250ec0ef8704f04d26c366c99fe57e3a914fcb640ae16b8b2a875804d24b058f4b5ce4d9cdd47691c9af03e354dfb368f0c14b5a4b736878daa8e1cc5dc12872b79c232391900fc87441e8aa8e46b61b82191298c51fca1851dc9570f32ff7e2e53c929c91cc7f33fc62e53ed176b0c01fc412f97942f063fcbb13410ab6bc0471c7325fb2f67b9e3daa9bd2f0fe230f645e7ac5e80602a6105c31c5cc26b62b418e81bbff1846c597eda936ec6e19f2c4aedc648b00877118f1b5b605ec38de284fd82e235fd6070e326c4ab0f7b7ce77412686d266ac93d4b4aa01bf6e4fffffa7ed9bc49c19a53ac92e2ac7a75bf0ef4f4c38aded1f0b67914816664ad8bd88bd021ec715155cccea3a14b4426b275efcec8091b49321b8d4bf5cbabce9404a5ee5f21b35d8154cec55e56afea7671135eae129e6cc690e39ed8f12c0196671e9a1cfd37453ff93552960069137fbf671072b140155950ecd46a9111c19efe0830e5fbe524660d7c986ae3da6014bcac15520a9ab98dff83fed0ffed93ccfe1846cb72f3e501acb99a228e8c8ad0e8b8e1519ea8f476db91069246db90d843c5a5e39b9045cf0ae0b7a6a6f6ad24c39001b79faf691bb32f771bcc8b2b8b0e76a9c6b2f51ec29c5527079a69ebd2ea32e28e56ba51fbb1e66c3d8c1c1141135d5013216859800425fdf4dce9e97398524d2695775b85faca77aae0a43b6ed8d096e9ba08bce5d6276945bc2375f3637aed913c5f2a9e1080997cd9c63f166545c26d0b7d917f2dd70674bdd8ea105b512028d153ae1f1470a6ad25c23d523f3c594ab1c5751109ac5e1f8f68b7de67fcba041f32c155cbbf0fa08bcbcdd2bac5702ccbd3e01184392d888957b34507d62985ee00f4807dc98297f1aab556976dd24474af0029d56a9f15bff4cd814ff681cfc8b239549d28da3af6e13334efac3ec750413eb9218f76d09c8113a24645b048ccab297a43a0931619dd32d6753bb96f2a115853201df2e71a7dc70a8911ef1c917de81079665d1b73f1c43806da5df89c8b563e8b58702d7df4fdf13f6510b91d8b6125529e541eff28e06e8170b5a4d2a0bdf56243f298a3fbd8e46793f853eb86603ed80a3e55721f6f1cfd98e4b850ce47a5008512c1eaae514267b7d961fafc45d4c44459dea47172fc41f255fdd3e526b2f8308a679c603ebef4205e1ef75a86d40bfd6d388a811c5c9ad3b3403d233f32cd9711ecefd319e04de6f4096e28df1d36165ac7b990879df03b1ed28d631170708b46d69aa7e984bd16e165106132058c9eb6548773e4b53d168b08f48e116b97c1444ff1518459724781ba082344d50ff5208bab5ec6ae472d28bce787f654dc3dc8a696506162a57482525f232b4848a3547c7c917e25b06867ca56312914b1ed04ccbd6d1079107156bd6e9a2010dbb5891bb6de50cc522d1794e15dd8c321e0d9d8f93c6fc7b45d534d689a8051f79d7ac0d255a8e8f3fa897f27ba5f3267c3a9849e31b64ef1e3569be801382490e50ba8f38e59d33ea159742304a0624992edadb0f0805fe92ba5009c7c91166740cf72595b6e7b14a6c35f98219f0b529b89d666c007243bc9001df3434fa6e6c382a26cdcece129b7f5b48de74616cf4b08a63937da170618a654e79143cacc4f3c2156438a9cf640e2e5dba46123a5682165848c8d8941c4e94ccff7256ba57128bb27bb3f53f3845eedc8c10304f29e5f83b52cfb1c6375b770430b38e4144da39f943f464628c95cc8b4066a407f9649120f73de4955d81271121b6887f252f9e3396214c60b7b0ef8d55bf49104f02e47f7aadadcbd810ac045de041833d14cfe764097b9b05f9b0206144d663ad99cfdd8ba347294624115364fc150a3a5f44cd62b0617452be4ef9d14fd62bc208931941ad60209c950ebaf24bc5b729d278e05c75e6fe01978cc626ca5dbbbf375877ef78051ba27ca01289b7a5a8c4bef675e99cd63a2637e2a822bd75c82af2bc4f3a7777c3c1d8ddaff7f4ae2c45f2ce0fe5a052721a6d59e9e8e0a23c470295176d03b72ef757b3edab04b7cd1c8ede4717bcd06289d1442ecce86a4c24a98bf5fdc7378252252b7ff807523142b7bc5e7a4deddc5c468325d2924043f50603d21ac77a3995a9315d3b648c935763ab5fe3d7a9eabbe8810deeeb19e3cd8f924149b7f61366dfbea551383d290a3074e1b2c08cfda941181a2af728293a8b86cda3ef3f12becee0fbd9515966be7ac0b51586c08a433afae81d390f152f099344f82dfe65639ae1d15edd2e302dfe8ccdab8b379f5b461c46231a6e2f33b113e07fe2da8f6a7dff5f6c9c77a6485d75623e12e61f9560a74b97771815bcdeb7061b0d1f2b3ae2e394c02f38e19f9a1d15d5a0e9804382c7412789caaad0125542777bf55f7e353d023b91f48a15c1048ae461b06d53176fbafa70672efae7198bdec32b48fbed66696fcf745bb5bf82ce2103529206e3c4936675f9f79a35b8fe48a72c6a8543fe9ba8b0216821f8ad2b82ad394718e811d68e3fb2b3bb101af0835009811933b0dabb622be55bb2cf0bb1be86a5981903df3a0bcc5cbeef311238ce508d3a670c9978a5d38965cf224a86229e7ea5e71f24c05874f4e182ff429ae954b16934ed8edbb1bef2dc7ab6d65ebedd78ae6b4899e6e4f61233aed3b1143b0675b2d413a8a00dc44ece849b08f1b8b1d0e782bd3c1acf0c56b0fddea5af9cbbafff906b0fecb4d0cb08d8b348fb7562e28a21da54ded15f95abe54ac37d4507114a3cb43e69f180f627eec2641223a9329a08559b26c4cb059dc3f5037ed54128503e1bd4962288abf946a1630b9116b0e619a4c667eea51f8e3db48898b60f38d00972e146c33aef48dfc785c4fe9e6dcf1b55e22bed976b186d128cdb2f8c082abbf699741531f07a68525950885cb536542752e7c7a6f7c84dc2d738bd1f9e465e2842027f5751046a8d75f178cf3e892e1fabdb6c6bd2ff3feb4ac469471ca01fe5c9c031cbe484f19b0711f19cb1747755a5c329397b31f30beec5f36d09318ff0e4c43e9fae0ccfcb4b73e6ad6ffbe068669834d6804739272f85dfa12f311bd7f89aca69ead16644aeffee8295389cd73a61ade4e05057f9b87755406429565d69b581712a1ae3cbfceb1c8ee9426bf9bd1a6659d22c7202675af17d1285fbbfc9b42aa331663dec3bdf907e1a702227b535d655ac470eca67f74826894b57223691e45d1ea7feee91d81b8d6aca19713fbfb81c19097fdd099a0fb19fdf3423cad69c2326a187d2b23220e31311af369ca9579c07e9d44ffca7f9303fde9f97ff1e8e98e07b418653c01dd6cad36fa06d37c1479b85e7cb19f56a6993606fbe7c2029f90e8e713d50b5cdc9553d8e07722dbd1c44dc911072a5850228b8719b3e6aa3cdb3fa75fa2ef0e3acd2d75e7cdbeccd0aab6c88249922bbd3ca12eaae79ecf9a8377542c521a13c9ca8ccb061db30cffeb252f3d603763bf55bb29fec9fe7a05902bddfa513b775b56c624fc012c87e679ab86b15b5f878631de3436644f1a827ff46ae671b794a6cd43c15a16a4628bfb26f420eb7796bc6747288208849d1bc00173b665718366a3e047807c2052ac4b0a96a8e6e61ccee4336f6ec8be609239842eb844220e54dc98526b597d425ff8eb0830c76a46a266f620d6fae411b9c1e8c5750d42a9e69287727c56cab474c5e9fb2b2c0b8b92015eb66dbb4304fca6e2b8201d802eca7335451b20b7574428dbfe985799d9039e6c959f6944a40c28464decbacb5e6641377512e80fb285cc53f42daec0619d775deda02731fd6efbb20ea48cec6cd4501cc036e449f09f0f76d1883ca75fbb9dfb9c3a5c0983b3c90ea9811eab12250aad5b96cc1f276c859595032d1e8b6672481dbf3466927baecfa7a46289f2e76e7954ff5d3e9dbd73a9a3f256620ccb2f72aa0b1d6f5803ba16e4952a0b487b8db7bf24da00ee23558fe748aa6c18e45f1a25404cde14d0739aabefb08e28c90d72da0b863d1743581d7101c6d296d362ec650e3615e84d5061ea9a0fb5d6bda60670b32b22c4cde0046f3966377327eb83fd1e17419f9c3f4ce110c75aeaf0a7f42119962067a60485b398ed4a7c2083a39a0e6e8f3087207133b83d204fb171fb6ad918ebf4eb0ded2c82e25bf2e804bb90f47f72d9a5474554c338825be2fb8877a8068152bffbeefb886d77278b1ebe3838d0f510f7fe97a0ed658282de4d782dcb3a4bc85e00c211d2de19d362d54a26b8ac22c0cf005dac13d5df61e20520fd8891480ccd0215805d276ffbb501206beba37472df00478fea7adf2e59d5b055486b4ab5991dd1e4ad5d23961ccce362b2085e702920323a09ada4c3c102e4d6a82e4129891fac9be73ac34260383ac10171dcf53f7a5e1f0eab19cfc9e7f99977c39858ca0b8b9f4c9eacc087972bdc6f3b1df7c29b7ab7ea1368e9aa70082ccb18a5d82097b445ed0a8f02d411db6cf104322520027ead4569018fcba09913ee9c1e2feaadd33c81237c018f218d40c02fd524d6a653bb73973744240b9afbffc5ae602dedc9799f3588b1ae18fe937b633e29096d5481be676563ef5c97dd5e8d8c14f42a52fcf6adfab9789908130f0c07650bb691e28f0aa757b8d14ec5faf128df04e97295bd9e0ed3851b9cb6d2d91e0398d0efaf9f942246f5ba8b07b071eb85b6e5c019453d43ebbd6b03fba23cd099e38010ac707de067f4fd5506a594fe4dfc8e71a98a653c7628277b2866edd449517f0d32412134960c9570dc71612ca8fb843df89e143387b64318a890687afb8ffa02091341b4cbae0e288a89e6cacb732113bff9ecf5987e88fe9ebf6ad66b1f116a2e9580c39b2c0b717df5b4b81200ed98c125b379253c10e7496e780bfcc05843a189e8a8e2c5c1568ff5616928c6278011b6d3f96e47ccf6d21f589d7424581178b8623830d679b9d07f05025e7143a77920dba81ca4fee49b1495d52e350fa3cba0410bafc8115dc0ecbd85b574660c3becfcf08c37bf7136c7ff3756c86cae40dfb8488acf7c47c4552c12766dec8f859f8c44ffb17a2c85fa29073411d43a3ab15ffe133a3d6f004df52b5b28e38656f9877ef7376f8abe17ead8b62ca0dbfe12b2082eabf0b9e49e421bb13b8d1da75009d37c63314d4cf8dc3aa8499dba5ab6928bb75fc24a0cbf4058f060e2891465d54f16c72839c1fce066426f8a728876c2fa002bf420485ba007aab80c0e6c88d1eca6b6101c5f7f980718a89db96afd6bb288cd5c06ef501887d7ebe9cac4cb882c8bbc7823e745ec0c8063ffdb6005981f82d0e3cdcce9e009dfad8c6211c6a64c9acb4483ae938690f7334b064002bb78e91e08e24ad73645df7abb6a539d8006edf02c6c31e255ca2bad13603b032606262b5b23acdbda2c148e73afda368adf8b023b529e130e159e085bf6c44e93fa7c8e0317767292bf66f460453ad6de09678a50831328a60214641c76b1909ddbd6b539656112a8e3794f666cddfdbcd439f7a00d891bd98045e1c2b62872447a995dcba76bd46753c6551b42661704ad3bef32f6a097a70d7baaaf142d1a6d7b98549a88e464a199975c4a444045beadc07a62bfd791d1fb0de4b69bc36460705e3c1c3090242c1efc4071a310c292c02b9f5e3fcfc360c7fcdea0010796487f50c3da0339496042fad44365ee5d504ed4ecacc6ad281f7a0575aa70afedb79b6e23f3b44a9e9b8d9c8e28578cfc6a7fc174a296cb4f1c20f8520522d9c61f4fb286989e503fbde9a696b8d0be72750c5d95eda728622b4c331a55c813df59114e508e747147acef067d444607c5ac9e7f0f0bc1642d63ac7cae33ca04f722314f30af4142fad14351c94dab0f946dc078dfd0dc33649faea7b9ab4c263e994c645569b68a5b9a02de550aa53f77b95df467e5e08889fe34bc6abf0d6de2c359ca063dc7d484f63c56e4e78cd520b8d4df9644a4f0a8635a8fbae20edfab083fa26e419be148dc84c4a201432d6a3396b0cebe45d713f546000c7d0f65c3b3fb30639fca5ddec4897a68aad01ed21856ae8875d2b1d8a0144bd26dd760a37560cc8b14ed6e5c2cc55d4aebda9e8afacb22d7623c468e93c323048351d2b7e25776547178f4cc14376dc6fde40aee8f039128d0a08b5ac03e8b8603761563f2be68c11eef95798232922ecf62cb17303d56efdfdedcd160709066250583f382cd135a925ef8e1ff390e2b849b35675c5e561b027e77686c666fad98117e95d10e5111dc2db57a19ec1725f25bd7e635f295a581e480b52587c0d6fa5ab87403eacc461d3ceceddfff782a237996ed01bae3c7f8dd0bb48cd5a9746178b70ea1bae8bb0b4f8b1768513f63d7224ed2aacdf3dcd671d883ea45445ad5d2e42f1779979c70d1a4604cfed2820106d4353f4c8e4fdf09c8e84a22a7b8d36fb23335f3f68aa9e16f6e299203b6463f8f3a719cb371abe83fedfa7e4bf7cc8b9ee7af94a7f228b0cb521f43de6f811d0bfc173dd88e24f0f3980ebfb9b4a30420e09dbf24c7f32d608d553e5f1f42b42fbcaa259a0e0cb8d3a2dd28b3483394c525fcb49f36220bb635720450484a3e0a87c9b6910775f41362828a4955c7da11e02a0d1fc96405e09724d6e4b110504f81b73a2034472c8e075cee9670a039de0a6ec0e4d97b868f669d30eb5d3fda00b1d8f55dd5bdbff0558e5f07b5737d4e526ebf5c57bb92ae66105a9bc336fce1d238c789b2e83e79a480be6612e8589fe8b61b163c8f07d92139fe5e5986a6a2ba04599c0d2b1033f55ca5248e1caf598838c08841f5294fbed2da1ed1780dedce0ee1e017bd66f44c925c803d20aeb4dcc7b9a9e3fd843b941f7ce98a85ebbe6d4d71b52b2a89934854df8ea877b930399a0f90ea4d186679630905aadb93b8685bd8d4d2d2670e201af04c719c24b5e0d02bac55a0d6ae056821bee8dfd44b54f511cc6a69839ee5203d2f8e7abdba1be6d353b24274e943fcc685bfa5aaf4177f477d0b7523f3d3e394e223f2264d0442b1a49d9685f45b6e5d30b47ac1e5087d60d22dd4dc0beca82a2e0e5803ed3dac69ff6f90ed25c415b567ae5bc8d2db1b23017333686df8f5ea2974f6ee9cc6cf479dd63c9c6671c927a2fe7b93c2e5a6e402d797c8aa4816e03a58884aa3adbd73026e359d974e68c5305dc10e137711ca218bd8c17c1d99a687f0bfd5cac368bb4e462e761afd53903687470019977198bdaf219fe1cad2f1d6b8be192c482aebae986dd59681eec99a47dc9a050a8115aa267249ff0a58200a9ba0a3c92b3eb670db4b905709234f48aa83e0fb551ff8c8c11eaec1d986e0a53ff00e89c5442f7bd0b5f1d865e0044cd0f2d47f6fcd68c1234d756f76fbe41a942fe052b7c1d02d20ae5862359454a45b1480a9d03112f7c27b08afd51aa9c0d8512f63549c15e890cb26c5f0b6b8d3b188c0c50195c122a1f7e8b5e38b45b41440221510a3a1bbf1b5fe9a3f3e5988641fee4e0568972e7465569c73b8540da25837b35b04fa0a5dab529d052ab8eae7614c67acbbc7372f3d450f9e51e71aa1e95f16e1d4cd168efd42f094fb6d655e094d3f94c4ead71449a5da9ec9889a85b9ea1d378ccebba292cc7a5235f6f7a985a211705edd17be2bf805f6d6531b9d7f3e4a048618e3c86347a6e2e28b662377229f4e8731aecbd5377daef516ff6169640403842b6705fd341fa61016d94f367854bde22e6b490f0d8031e388cbaa97625c822a736f78035f71e74e24bbad4ea783063363f425f45551fb258dbd499d6648cd7f5f8441aa45999b33fbee39fc5d5b4be5c2318375fce0da8188d07c05c0ae59700dde6b3cdee400d8b0a59eb21786e587590e0ed41c6a2383c5aba2cbaac4bf4d7faf1996a1bdb858596e15990f9b4f4a8a6efe2b7c3f5cf9e82705dfe98b414a65707ba9ea353257434ee60a03cbce85e07ddae34e408877765e2ea7317919007667fefca4735c7844f41514c4723b3270b69a3d4faf13d46475297a628ddee956f783284b52a1e6155945fa9b7d916ec64356cd2366d4abc2984426936b0be71470615b35d619ccc05efdb27dc164fc69a331f9159f4ddb6ad4656aeb351dbd2c675ed6374bc7dd1a41a16608bff0c3eba4e37e18cf9508c9bfc87d4e3bce9423d8e56f91285dcd52e90af7596f3e4c872bc7ec1d4ad218ae2d0785d5bfa778af8f24102a14d1d58c766475b109c64553531a25bfab39b63452a3211a6a85d96fa3dd36219f77338f8d2aa8e1ea050002dcbde2313ef3d2b510e6e0501fac0b36f85372ac95eda87e41638b9308c3f431fb812e8fd9f61ecd63c8c83358c5c6dd18c6eb45fc6ef8aaa7fdcb589a0a3b4ef8cfe869f545936d11cbd0bc3867a6b5df9fdb2a14866d6b68fe4c8ee29713ff334a46b5e673ea051d1465b0f4d0ac77b4b30e2a012e550bdf95a40e41f45d2a7dce4dfb837824b5a9e5e9f74a2abbd1608366c2ea9d7fc69a7c9eb4f9822184d5205ae3a935038f3e862fcc21e41c7ebdde5991b2f2c2d552fbb9ad58ad08af07679c085aed8ef09efe01b08dcffaebb9e471119ad6778b9fd3a79c1e8fe0ecb08797206e2391e4a8a59a3acd1fe77780c1cba87117fa7f0ad7ce8b5ed61c5fa071a271190b1a98eb4e2cb203d35534b95a99072bd52cce475acaaf370552d7ad4a6a99b2adfddee7145c76bcec1e422d79a0d6e893f45faadae9f914e7bb376a876da37f6d52c82c24c13a8184fdb84ce5bbb5e505b94a251cad7a5cd6b6634bf7806f7e64f242c2a068110dea1320595a568accd2ea3b6cbe4e94b86245c035e34a686364fdd193bb5345e6076376362af31d5cfb76f504c836fd64038e448576c689480a5651cd51f50469bc55cb24eaa90c70b954da5bee839229c45b1af8ccc5b7c66d25ca5bfcb8dc8c183767e4869f03c9e5fd8a2d7288faec469bce1c2a6a6473705457044a7701ef52b78306ad964f2efeed91d73f4c170907df112611ac86fc97131ac3fa046bebc46a946c885dbbc2df252062a30949cb86dd08462ed7e05caa553e6477a3bece76491f387c5abea62aacd95448ae80caf2f12cf3440b97e2f8500f896c8d096b972f25ad9a08fb410d09b2a86d5e22fdd197bcb8a0bb150264011c8b646f4b99ba5c1ac952730dcee6cbd8682eefb7b88cf2bb74d830482fa7a66bef77e8d071e1f56e90ffbea9fcc901b6d47f6c7305f04985250e041f376942c25bb00e5e451e81a754c39d0ad369c2f6140e9481ef5584e9a837a766d3e21cf53f86ea28a11d6d05755fdbaf1464a7a9d570582cc0c6b3e00e67deed72aa404fa5f5f7a3e7fc89bf5b87d66cd72d4c6b574960cd483a87ca617b3ffc6f59fc05d602a718489e8acab983fec61a20d3a2c9bb8790a91bd73e061db5182f4c65bdce2bde111f19863143d959a47c4360ceff5d5b063e551d6e146b0220e0b3d01cac2f759609fd7bfe6d0c024a65839edfd3259e869975f7114d4938abdb8d597386c9b83335ddc8b2f589a5aa44e238a8c719c862bdf1a6ed8bd03fc6d6d95f6c6d78e8d7b600b14f25320505bb216156b78682e8f22392ded9fd53f6669506d47532ba7cf223dfee290377d2c6124d01af452c34f369c37c805e83909098b36b0bedd7a15deedeeaefe46405526bb545352d58d346b06a4bc1213360596c94cef8385a97261f9e974ca7cb97b88f57f29c214c3707cc567912ab9b9d99991666d7cc55988a7af38a76d9bc69223874c42f5884f76b3f8031037a453485bb6d6ed9bc4cb5e43015750e40de6fca3a513444492fe4cd68807b29ff9359a7d60f0b204bb88ebfcf21893a55ea318d0db651907dade809a714acbbbf36734a12396b0fb085df73cc9575966c2d9958c272b04bdb17be6db1a065b7fbb84d86f769fe52f694d71d5fd8ecf9ca0a0f575bd564f257b76d983ca928b8e4f4a26ec6e2b8830a3a46f3b3b0bd574076ebef543410bbfb8ba3f8085974df5598f01b472b68a26abf944470c97b37987a33f72c68fd11f4c7d0d2a2ff7670e7aa9e88b99017d875e090b22735b88b83b5a7a67f4f4dab0a501ef96e8b4e012b00767c2c9f842e394e0763a5e7c9e674d8223a0c4a33fe36412f9f1a16d963d7c6660091ada332ba2d2e3edc3726504627484994be413b286ab3f3275815a8adfc9e14554f34b32c96df7ee861493c66ff81381293c60f96ccc9691c3013daeaf6f0a22de1650d3515a41fcc052f69b74a505b34b837c8de1e33dcc820745d2cda89e7fc028cb9542b8dee76a85aa1450ce7a1639cafa3f94b54f43ecf7b08c725f8c96f16e309e3cddf513a3270a76fda1cecddb7796a0cf2087f4a85b060b32782e001c3644d80e38e07006f5ccc2c5ea95b95ffae9903e54599b0b20b9b5b82cc179574acc8c5ca85697a8e0df2a2d5b33f9773e2b564aa8bde0967ba41caafd038afd9eb3611d3c2000ca5bbfc5790b173b694dd9632fd2803c8db6da1f27823c7bc65efc4aec14171532ac1f4e776944b16e76cbe234c4d84dae73b600e7f8bf16dcf32b8504fb608207b035b4f814967fc743c0c9b39eb57718b0fe055b6e78e5f920534b8a8167a1258eb71873d8707014ac460cb1c6bc68f67a94eabe56561c76cbfceaeedf58f5464ed7c684cecbd4ef944012f7f2dd87c4230852a28091ac8e204e9f4b3aedfa1a7a23884e46d922316d4ce03a0dd00a2875119f26e80d666bb1bf994825dc7520e710df14f16f82eba78a2cdf293f213c3f141e4fb4c13d9814f3c2057ab403aeac3becc6a132bb9035860f89cc8c2d4296d7190e6384c07036680b5226852ee6dd498932615d2b992f19393f815cbd90e68c7f09c1dc5add623f75649f52a8230025f470bfcfc240c6ca7c915cbef026c2fbc285bfa347acfe0450024802dd0b0bb7cc7b0d7dabe9af10725364e367c63a9a7fcb85a4de28a2c43227f0c7c31ccf63aaa4435c1b7681cc9ed514fd575b4d5054c1a0d5834e78214a61b4fc8e74939fc5c08be5e2b297399ea91ca28ba44de9a723c29659087109e5d2c58758d4e31c14fd325335f91f51998d5da336c9fb7d816af66c30d538776d3dc0d5497e8dd77768d69a645dab8ea792e5c9a247351289655c3bc4df602d1c0c77684528747d778b45d595c4094fe80655e3cb61057f8f1e779d1215efd0e0c7dd445d07fc8c97fda8c897585dd07a37267dd1eef7fb3da7148ebb83f53ec16048ebfe089e5a7ad2466a25d6e3510a70686818b28700038d10643fc9058774f54ea0c953fb6c5f4cdbb559743648ddb02346ae3fa28ed573b8df2610bfd466bd89a15c0a3dd142ea191d599534685e2fe08ddff4709e8c0b7ea6c80b100a170f315fd949812dd07823b09166ec89200c33c1e92155ea87ca2eb561b443882b579c132d16738cda2e7cbd7c869f4dd63d1f948bc0e83689429f07937cce1ba4832ed6cd73c07e77958d2e1499e0d59a8da7fef8c01931361d112cb00130991cd5a9d18c0a7440780f369441769bdd4764516ade05fa84844f29ec0f229fc9aae242508ab1c8c0c34ea973fd8ac4fbd7cacce13a8bead555937d76fc0e0cfc3b032916a8d73a7deca03fad9714bff92225e17a0335abe8baf1f0c2def50f4ab5b1fe2554e6877d87416c5db11b1526f57de1d9cf28f168fa97f02a8f355d9d760f5166e431c6c56329313bbc54a88298062011d056094842ffea9bee60ca35e27b7077306928d6ba1f983538c0713ec1855d5831cf3c73e7c982380945aee0ae6d90fbf2b3314ffac60278312dd264de0d5732c691394cc6f1c181abd65a51f42c28800994b91d40bf0bf067446553c73352034bfad5e0b7cae5eaadc18e4323d5114b81a89a7400c684f2f6bbe3957197cf981af53d11e7245f32eb4a3ecd05855b7455e83538c3f98f54a5347367721e53c7b0dbc29f0b12e3d0e1bf92bd93a14c48ea530c44836e71edb42e53f8a965d580d3d421a177f87a5f155514ec7c9c59486606cc315a5ce02f24b23bb084509cdf7af623ace88c57dec4ca6635aac1616245877ae9cf75a7251a28d689a01bde17f2c4007d5042f36298037144c54a6218e3d76fec18daef624003461bce18525f8d22074611760e4968707e509408ca8883ff1604f9d7f3f71232d237a9f20c2c55aee0a83a4c919513f5e8ca2ebcead6cbf536098f896e192a712ae8bb1809ecdaab79c99112c0abc7f465659fdeb10355e6e9cdbc8526566cf7faf6ab94b5173c42e82c1ef22028f650f99919652286a61f49262a123a5f30d96bf30c95de393f3286c5702962d47bbcb390456e82bf6defff7a12e107cb63c7cad50ab4462265920c4f59648aebc0ad2407184876f2c368a9edc2a32c4de77d9db7cc6c2426c31b49c8dae4c02d43a407e3f19796d8d1bf5f00a3037a5218a5cad4c704f93dba925327d0988ba8562723ebcc001a111e485f90875e032f6aa3501738195fe2cb6dbfac5c23981b6a78d0eedd1d23005cce1fa4c82835b57139abf97c6f29d3717df47c4c86e709995f790252e9669beac9698a8708926659642762679f99b84b160750a2c8cb89653dc61d3586808b9f3948fd652f1427ffced04a443f1a6c4642fab41efc896f08aaa0b32b25a55e0b4b0fed600cbe5b258da595d75543391d0422d32bdb09ba096950bd9a3f00d1261203f5c9abef895f14e3e81f1528e23d6343c55aca34127ce14e655baa31f2401024dcabb8498e99f1433f7154cc0eb31c385920154d34759624b441b6032f7d4d5bf8a8f68e39db6f85c854e593f313b51250bb64ba2eafffe4b9683060360ddfd52b79bce1711a14b811749c47127b36534145e648d9fd62b9dfefdec8df12643fe5c6aac02a465621239c88b00209027ebc2f14dd77a7d71edaecd5809f2a2539ed9f009befd23174c14e1e2f9c4dce750ace10c4d46e41a49dc9d08b004fb3d2d16e985bf98774660880aaec38416967cbad478bc402cb68ebbe2ad5b5213f58a9f25d136736051b328c30bde99f14b653966bbbae54c97f3e77c6cef3aded3d5df4732bcfcf49d2837bcc775580cbf427d3dd6ba77965698651c5b92d9bb1453fa25024f9ba9137feb67e4c6abe6d17dc4e21d74460a4c2db2940abae5de57e70b9c2fa4616ba0bd99811a4fed65043137c7b798ebc51aa7954ff704bb17201fd27abbc4d024b5af89a652d07f48fc3826101fdcf76b2103a55018e591f558c7fac33afcbe84c771915e2c23d82f05d5ebd7a649ca890b1ca9c7561a3365cb159e6a5717be08d00dc5037ad9dd528cd70fc1eeea8b77408e5b040cfdafd3a861a55c4ff29a5dcc27cdd0c3b0cd7da36691ea70be891472ac2bba8c98e962056c20b07457d0213dbab6fdc1d407c0814c788ef7fd660f1284fde69a95f0ec5938cb6dab2d07f7a8e24d3be3f1730b00d80fd623b5e11ed8f1145ceff4876a00754698eef6fd9816be550dcc1765141e4d6db9e8a723ad3ec69edcb79954d0bb73c4bdcc9e716194552f7cd26b27f47ce523120c89d9fc06fa890113a4839d2db22c98e498e139b39ac15680aba16be2604863ac808abcb0f2a296989bf1255ff2f7fd6d4f497d0874b4c5147fb426b925263bb702ea5105d722f1cf691c193ce346acb1e128b5add95041019c410c8ff1477d71f51de48ff324d4b3246ae61659f5408e32776ebf0198136eb7047a9719b655f22733511e2fb4d336a98e661f16ac11f446579cde9c6370b648a846265b8c10a5183ac8448f62cbbc051aa1f122336b69f4aa1fb022a1d5e71bc087373c4993ca0bc6b6dacc9ce99db4799c07b7862a88dfdc135d2efad543b49364e8fb78bff471c81579f82b05a8a6f6e541b1708f7dca7b7214eb7ba8c17ee2b1fb129e988b9e02e7022f976e9b1d7ae664a0a89e8b33817885900fd88cd40bae564479a14ace87e1175e5733ac562328985bbc6786435df966efd53583cd44f8833d1387c27d09e19f180fd50305f867e82a4309590c2275ef0db8c1b8e2c5c831c729dad60fe8a8467b38cb3d9c39aec67f1d5c821768585dd4be94f1601a83b185e2493dc441f1fa20fbc1eb6a29af843dc3c49144c64572c903f92aabfbf67949e58c04fe79816f271c6d11854565db91e2c7adf7f6a9f3de4cc3114d6207a5ef3fe809daf2509c433af2a77e482d685dd1a50e980425294ea5b289ddb6fa71d2b50d8115f3433ce6c7611bf675ab2a2c936e845e17846184851228b14c6ac0c95cdd7244f21f4e707572ceeefd6806be75b6ef73be206c1f5c1cb85c90a298bba9703a58c5bf9677fdb692fb8067431f8651c45fce7b1ffa899db9079448a4bc4eb7067665651a1b85d23239ffdf0e5160428fc62ed3d4482337de8ee344b1c08cd85c1d4b44a56702c4ea18bc01f7811b576b783143e8c507258c76ca4631dbda7d929e970447cc5a3debfae0cc73d654c58ccf4cbd3bee23da5ead75aebb0f052654e1860cd29e2b1fd998e81358c19a74b55e53bfb34dede7a31e9b4f89b224a2227be3d25fb06e9e4c401d4663ecba8cc2ef823fb9db6b97de149149f85814e8cf15775ac7f16c4d52ddb3e4b5b0b44b20d204b3ba676252456cff240b5a41780326b13b2b28b13debd44d6b521f521d5079b96da715687b44959482e9defcaebaf1fd4e6db1ecb48cafeb03a199a96c686b69db75ea049cf76668e95b83ae7bc3e6871fb458007b9d939a95eff909ae917fb53b9e6823e0b3c1f6ae4f868a2242415dcbec3a9b9bfb8f669cec4b13ae8a6c609b9f3c69ae7d01dc85578aaddf2f0234db44bb219277b65bc02b15fe4823a7cbbe78f948cd9140bdfb2be6bf7cd53546e9ae87ce3a5406a050a62d534bc390c1922fe5deecdd6906122385822c538a00a462ad3f90cd26f09e39289663e3f5e6814dd5ea9d831b6828bd57e7491e367dd65bbde1308df108b7234ba4ba91dc6dbe5f1b79b65b593fa31d5497ea7d2e05a6d800ebe335cdc7148818cafeeea2e32876810ce56e0f51138806088f0cae1121ddbdd8d9d9d70bcbf166d16bb06d34fea8565bbb7c648327e8b1829c3a0b8891af8ba03083e1a104c9098aa0a1e63de08d8bf1c977dc7e5c4b00bfb7cbde29c15cb70758af36c5e7e2098fe01e4b8b1dc2de497e064b61316443f388567a738431b8d24ecd3affabef6d4ab798ca1456691930550396eca309457cecc7ae626d41b4be55c3d26f21079379cf17351cbca75b7e9bfd47dbd9b3a77e15dfe2df7ce51477a1689fc8ff2915b438b2f9803925798767b7f2442801d723b2af79c306372bf9c80a088512b247b742187525b39065c0ada7613c1d358854b32189b1c195e4172ae91429bfe83d788c0a903f7d1ab17a53789c11cbb5ca12f46fe04529248f1142ba19917173633ee3deadaed4f614c2ea88b9e1578ba8d59eaa5e4aee4bae690424309c31dea44814feeab0dadf0faf526b805ebfe65688d93499f12d34df788e4682bb041b28bfe167ab91219292c0cf66dcf7c797f60ef346d24f8e4ecd64322f6530c5fc94f3e12b792b524fbc0e50f41ed16795e472076b3f8c2bcd3672b8654303c8bbed312eef3257d35ca7687745122c39799a18d203a084762901e3e15f57fd0173f7a36c6d8aa2a58245afbfbadb3329031fa12fc123ddc29b5dd2ac0b362f5ba43209eff3d2e7d68e4cff716aae513f930659a32ea1b647a3fdc0944929ce87b3634556e31fa10ad1327c7da14d7536b003570e241c85c38457c30f6d5a720c67ab076931b31095a13bce385b5ffba48507aca8539cc0c8c6fa83d4c1ccff0a6d2f0c92e8bb9890d693a5ebcd8e1b9b5ff24637bf536af6b4f48b090028c414d652bac8a564dc254d7ed495dd7e08f330d46d6b3f15a004dc44835add529385947e217d1a3284f86990d2c9a58c2b090fbdee52001bb89a92615b147addecda007a967e8db1bf4b8dfeba98f4612636b61d6ac2011d9f596ccc95d5f13b7411575373ba5677defaa3f8177ecc57842f03b858ad95b77262c7b61814cfd0455238d8bb1e646d9ad398160df2e5a84567771faf71e42192e641b643b694c7f220279ea8e2b971ed38ddd4b2c82974ae9dbac9403769349ffb3d89364134bf2dca0e9b5af5ea448c36e820b5cd368f031c15af44a575bf290362eb7baa9944720ab74d3959a0c86056b1f5861bdf281f7af67a9d5aab31ff721c7a8aaf1e1d31c35376bdc0198e9d9a3508bd85ab3b0764704cc7c7828e58b01aa9cdb115614b1cf65eaad7a7b444f8f783610e55d0ef8fb8068d43c6b460fdcec6287a4366e69e18b4f74130183c8f7ad752c64a1053f8a931e77518f5cc2df01026f35f7ae2ad15eded7faccf48a6c890ffa82e280979416aa67e69f881f4fbd843dd966d7e3fae443795abfb8b22ee4911fc57fa89212263cb8f83f67e21afb811bd0aedf1e28741e786cce45acb7da5d87b50a1c2e91275ae0d803c89148dd60fc74ead08eca7ceba82476a8c2c0b5b8c2558c228fa7bbf58a9510352fe5e3a8259ec640816fae312c864c09552b053adc0e1aff562ebd8b05338961047ad31aaa8df966b0ed2be6b39027f10ed8ad62161115cb3f1950f5d5a4ec9ca9648c4179e192d3b1f62aa52d31100849d39c5d9fb441b85cfcf397d79febfc588c3380590fee210a042581c24180e8c5963e80dd08836d6b592169e2f8803d908f4a8ee61e77f0ceceec8dd45b0e2b6d0314c6cd71f4e58d2fd3d882b7fe99db678a6d7d3a1e1455a47c469ca0675b4b7d0acc3a7018c7be6d3a1cc3d4b4ab56f2deff041ebd6851d10e2547f5d18b1b3f2f2ff3057a6cac9f89d30d2a8879d4fb63190dafbc94fff722242ee8a6af5614ac0898a01b2d439f7985b9725e3d208fbe1ab5cfa3a86a17816fa03769225a1130c5b9311c58ecd7f3468a5a6243478a61d4dc5830a345194980e57a0ceb9d28acf5a804e6957f5e10d3b26e390e5b8a6a0a9ee2894447c18150d1d98538a446fc2d194b2de052c940ec13ce72f2cccb0c9d707ed98348bbe6281fb857a885389270a29ec2dafe0b24c77aa9163fb1db9ae4ef8cbf90537212b4b88a03fff211fa96cd3df0a0b2c787bde7c16ce2ecc7d41a4362e42007341e3077bb710f5ec7f45ac002187a217caebbbde7d88aa9bfc833b56780208a903549347abeb88257a4bd3877d943218ac1c4a0321a9dbf3f7ccd4937cfa9656afb2e017f2cc6474e66643cf7257ec5fcadbba03331a530e771c32f503149f0f7049aee1166e9fb61bbdfae7b1497dd8f1f5ac7136c074c5478041244b2de28bd8afd5a23354a7d47719b09ca22c09e89377775381f0741275887f0a82b388aa59933aa57d2e110dda2998d2e0cb7f9f487d6c39c1371e58d5cb83554f0d0147cd0f9a09b88463ee2acbe5c1b88ff8934794751d7470190d7e7ce9794ad57151ee97bcbebeb539c4d165577cafdffd6ff1aaabc86e29b2354a0c067e7fe448dcde6dcc78fd4558ad3a5a63f0f954f23c4791cc8c585c51625ae11fd2f02cb1b7a1abd232f67d8ce9577dd51702bdf97f6f09b0392c742895901a17c01064f9c29fdb9c42bf58712f1ea5ef48e9eb0f8933681e59bba3c6861464b288487d355323218d0d82c2730307455fb1005f37be81c5aae5278c923ed0db011975f4a4eddef016fbfc26b01a901a9ada4eafe93fecbc72e3693362d9f4058a5e68e8a71b6459ea57ff4f83cdb9e973b70dd433ff29bee57bcc4aaba572d128a565054d4daddb0f7bf093e87e8949ff222c35284193edf57f70c5057283ace615a53fc29696d376a44954ffa687800be9cd8d9096a894547ec8befa0720a92562d45d32945e70b8ba18cc51e04171d168c89e09828a229ced1cb1595289b1e0e29353d9e6293d11447f2eb7f0c896a1a227b1f2617fbc894408fc8d9bc2ccecf7f76b598d8039d3b43215eb473211a509a8e8dab6725ea83cfbbaaea511736e3f08449bd0cf81d273fc3e34d717c203c33e66568a6f8b0fe6b9eb421b82f2f4514395c29de6878ec33a9eb47615790114903a658d2b8e17a5dae4f3fc63e3ed0b19d001ed79db19155eb73c464d30e4a07928d680794925ad481ca83dc8ac62116e865644a08202286d4fbc98e358b5c686374f9dd698ef0dc53dc0b3081932251aa6787c58e2bcc0826aaa02813730ca7b16e6fbde40e0f2639170e33efa37216b8386adff3653ad499afe14d4506fcecf4d7273d46f72460ab270ebc9de14d9a1b33790dc9c5b46849c95270ee62a862b4203fa1fceb085f5937b0accd587f571b7a0af95a876988a491b5763819a5aee06245dc71e353235db59c9ae438ce28375d17eb68fd8e8c3d2d3838c924b53ac00d70efc6f4bfb2d8270d29cc6ad3ef484d8c431e19e552d51deaf471b7f62aa5773d88fdb1a6914d00de5022b44d6d547f5d8d9cdf4e8daa3ca94d46be0d9643ace7f77d7f58e769fee1f16708919a9d502d8e5004894709e9ca979efa6b308f059ce4e16340cb3f4b5134ddcbb28ebb565537408102fa787961b0009750cf8ba24f5d2f7376d7fe450abe421be05866a06f9034eeac2cfe7646d34ff85a24c8be1fb648981d060ca80fd5893e7365bca225bf89e0c2547a7858968f9fc6e4e8164c48c16f18d33cf0588ee67f62abf2b004e1b45501b077d0416ef0b6776fc3f3d2f73499b80a8bf232a2c1056fafa5ec84817af571117ade6c0a554c2bd890f591967dbd07dc1327b715e9a2bb6c9781bd6fea890b01517e00d64279ce9dccfd63d5bd53b76570071711227da51dc6c2068c2241584b4942be7904a186d0fbb7682646034b895079e6a9c6df15759781ed3bd92ac2f37a8a0d48c43f356f1f74fd83d26afcc7b4501fd9350b807575ce9482aca6fa7fa7a604543b3d1ba85fcb5a5c44167725c3bee7c5282c52c6468aaeea981d143ea2bb4c9a0c26e6d81c077a17bbf254d60a5f147783958fe0055d00dbf570d84076ef673159f7b4ad0bfe22207ae48f9038126a468f562981fee2f70536b3fcfe976a2d81807428310cd332db5beeb559463b5c9ca527a2cab8d9fae3384a68a383de24ea2e65b8515b45fe8f0755402b720783308f019dd006697188dcbd26933073624ae0312e164ac5dcccceb833de4c0fb38d261f1516f9ca0627614c3f32a490f6fc0ba3337b99b5d7f7f1c1091e5c75a419a372b78bb339d7867b432d604d4780d5bcb799e8128944ae2c8a6f25ccba9931910e08d9ec632a8baeb99c6ba5326268a34c620619c8614f4e6db513b59f0e6c8bd278c4512e2d631dea25eda1d85335821d3e8ad96000357a225da492ee858687b7a3fd050959e701b15980550a83958e52279f18e1d42d1ec278ba4bcfc5f7b3054e2d463affa7eb7794a713e2a491d0943cd6c2710f1cd9eb12b3a0245a2c8c54bdad8cee5a648f3f95267325ddac366b496904f91e0d7270d064eaf7158db89104cdb816f701e753addc67865dd65560adf843621a224cf864efa9ce90882a5ff96cc7352813411359a7ec01695a4ea1239a20d846208844a8fa7938ef451a0436d110dda1c2322539016c9ae73273d1a53f99a242d7d3efbf69661e4831ff5af290162805b65714a962861c73915cb9a2ccb9b4681a3efa5f15a41946087b6ca386a3dd433dcaf7e78dfb419899ce1ec6910f732953a02f2055c9086ac6843b6e60bfb25854b7ee93e7fececf56e1ac740f2bfc73e296bb18a56d50cf5660beda71b3205e8db05cb07de1a4a633fe8a1404f08163cfb49326bd916d615666301ffc12fd9bf9820a571e37b4f1f483350ca3b83f7815d19f4dac781a73c166ad37599411e68f211439d0e033dc6c7055f627bf7c549a26fa28bd95f13a462fceebc35d0115f3dcfcf66f039146c7d480e2a0a829ed23f4f77b21a5ea86800ba03e89a49d7fe31d83c93ae6ee5ed2e8ff4b298a8da2d4f0767ec69235311e619a9eabfdb5fad6a9f7b558232249525f59f11063ca9f90fa4b7dafc36ec00ce8ee0ec11ff9e6ae9824bbd3a289d68ad09c68beb0af19f63efe523d8362c0cb9bb07cbf2d0d54b91549e57b722e79f99ccd47e1264e78a5001fa5e595e80dc69b82c19353effc1ab72d586709369afbdbaf623fa6804c48060ad51b15c799de565f64da4fda680627059dfec48524c72eadba654303f9dc1b204d4617bceaff9d2924d7b5ab579484e4e234b7d14991638da9c89b8bb5786fd3e9ec1142fc2b3b05d69eacef9a9d042664ebe344b648a82364a32aa543664763c407e5a5cbde7d6300d966e7e61d11215810bec1805b2f7a84b9eeaaa0d96e3be83166c4566acd890bf1821ee4909faa5800a975ee9f8706e2a4ffea42e7a8c25855ebd1f76dda827ce7cac97ac9823f6ef2aef635bcadf78197d3b887b12ba86e0fdccb52d9a4e3ffcc48fd76081b2d55aed11cb5d18ca909a91cc159384c7486bee2a6616c4a92ecd63adf25a2c5a51ac676fe9bdd7d67912ab8255409bc29c1402cf8cb88447ea90c3c5ab0ae78666aec308e0a5806a6c631e78f8c8439c7cb15bbf508cc4462b2dbfc11d986016e4bfbefe0c099153767dee827d4710e90eabf306104fb44cdeb929cda4942ca079032a70b03e0b879474597781f9b83192c013fce3344789cc6b4541e0e4444e37e96da8defff2ec97144b3ae54d94d33150dcd6f722b3cbb0f44c5f17808345001aff9d19c7d13c271be8a3b389247166e4c197ab3cf5a62a74ebda8a20299eb17aefc02b4133ea6f380632f10d88f2c3150b3ebd924ba64151c1347d275becff325f191e182b2158b7b08abfa30716572b544ef2b0b7916ad8e4e4fbc6a0584d6203b663e2cdd24336a4ce7eb78c90f4450ca9fb1b83c240465d55b3fa6548fa72a76a9469b2d1be29d6bbd61624be1b4df8b08d8eb88765fba7ac57f87b96f2caa6de50cd78fe9dfaeb9fd19881e24e46adc1e6e9a59a37f234686c87945b79ee73eb294040846971bf1a73127af9023a19f63d5b5ea35433e8c6741fc9467508144ba53d2e2522c9dfb0741d8917894bfb3ed42c3acd16f29c03d7c21786bf31a196b921ed96b333102e12824b5a933366c9a116556b802e9c5ffc4bdd3e37bfca945bfb28efa5d2c6d70f88046937df42b5d9a70cad47362afd77495f969b544fd8f64c1220e026ae6f640e1885908fcb1bbe8f8362f422f9893e95464a6e7885aeeb2edf848dc22406310c932a637810667e2122d1417fb2b9388a1ed960ebd85d6cf1497e7898bd9cf008a240648675f3196c149836927290ba1ad73a0e1443b453ec0c1f6c9cbf98092d18da05a8321081a4c7a4a46a4a7da91145290366d9e5cea469e86e88a4052bc7ea85ebadd13b6e6d6e3703aabe445b77a0f856c940bbf4d1a796e8829ba71f86b32f689ab3bfb1bd82f7d43947bb776a050057ae3110a4128efd041207de00323190a7018c95e9c23d180c8bd14f67bcf29106a7faa14358eaf4e540fe55f362c62d65eee39ddf231d8d1ebb3d661848ce847516fc2d3d230e55761bcb8cd78ef8ec529993260873674ebcfee08125495f4b2fc42ce04daf4e03f0ad6c2298caf13091234dc1da76a2ad59fe0240a6f0519d44e896110617830e678514fcffc6ff8bda6e146aff2a18baff6461223514b6afb192d55612478bdbeb46aa3642e415004520bffd890258b514beab29f41a81fd22279dde2753c761165349e99db249cfbad47a00be00b6970ee307409a3b0aa05a043220ae5f9c770c94beb7c7d954dc8c9045b414b3caa441a68d73d3b58d37b854eed97fdcf22e55840d35032c159bc07d747f7ff4ed0f3d1cc9007ef0aa63a573bf5f5d3eb8410b35ed15169d0e91fb1e2da320a7bfb067e13c2d8e4de6cbe849a639eabd5e8e409a47337ff0e5652e37ed41f43ea4c7cefddbf1a21329b82181391250e459a4a4acba5c9b35e48361c803b6230d42d258958e7595043f39ee7421f5e0d6a260c0ff1afba1937a4845b61438fdb80470ce66b2578d1757e17d569ddd5264389a5844ed82a37ab8c467cb95d5c9389a566b3898ca6354870c930dfe946ecf99dec9ceedb6f309d50d321a54b7fbe97756e9b4047e5ffd45b9340029e14b344d3e6fa188baa95fab1d0ff5f0b76e1d526f37e8ae59930beff092101803752e5b2966ab63f41f9037435e035c4ab589a5f44e7fc4076a8145c0dfce59858b17d25b41e4d3c09d371c23134b60b0ab9a31c2d82a664f283eeabf1e138d5a48cf42da7cad62f494f3214825611881cb8278809408293f70cce62c2df1ea4bf17e40fec46a502e1d94af433155737ff94742f9b3c395270f1d2691843fbab79d33e692629210fb9681545eed56b5c0c7c2197c9c30a765a03efbfb79b7da9fe47b50015e8fc6c6a0a542d85386f52266accc52c08450fdcee997cafb0b3c08ce6e208d2bafd09fc721d0b9a4d9a013bb64f04582eff30340299300103265a521a3faa0f2fc3ac99d41f833a186f4a55e0f5a315545ebda6f7b6c30957cefda6ca7092bc25c481d2161d890a2e686f97103a6a889a2033a0c8c01f4071b080a5a7c78094471f03f9bf331b46e5fbdf08c4fb3ab7b67fc09950917f54e01f22f336cfa86c1d65da16572eb2bec7454373c40256b12b40a0f4e2bd585cf3c81d4a95b43e78a066d503bbe8fd017cadc98c96ce21cc4945763cb3248c94c41a0483e598d0314c6349d0269cbf5dd8137a748cb96e08f677fc1a293e06b53c6cc34e0c114f7575d778b3871f22ab9221eb121b22fb49f9b139f0b73f1eb33649777a2ca9adb55f032156833798b38da9d08957a617884d2161778e03472cd3d27549ca0e495b83f133d57b97b9fcfbf9299f0ac22053e9683a4707f433aabaab4f051993ab2fbc4fcf0d31936cc918031d0713c2f4e814bc4ac3c5a208b36030d253e1feb509f9685bc4a4f8c54bbaf82cf0d5e8697eec33cda213cae3fff661499571fc7223dc1774b91c683e929d2e1f799f2eab929d8bf46444137df5c933d33abbda37590ca52bc8eeac6ca261d7ebd0e52637706be15767141e445eaa613d76500d950c3e3c8c2ab1c217724cbdbcede82bbdf3a637b20605a48dd9dfcb473e7f1c5a192e8409a34dd08ed51db518b9c8d01c549f121cd2e25d664801f5062b97349781ba954c81662117b250b8bc967aa0a4097c3ac3c2b12664a4436ad14829d4a53632453afa47d752041db08eedd7aeb44d984b6a32cfdb4923f2ca613500c307ded49c0f04882937d527b9d518660804756dd49b50a4ce1518a4ba74dd6f76a78ef90d09f53c36e54d08af1f7f444e9efa317687dfbcff0b343f233b5733ce87bf633f0434b6e2a99b7b600961dd607dfd765870be2314a953f8b5f2f20ca9fff0f283eab6bbb9212aa5e99e8904e03835cfd0eedd7e83bab9ef854d298110959c7f60b8d3cdf958a1096e05a9e5ec8fbdb13d9777b99723c132dd073651aa2678a7a5ffc69bac58e2208ccfc93b0840ff3e3bae4cc438569d4c6a58791e56c51471d75aa4800d7c0f57faac996cf2b2f9e44f639a4d46b05821d394e32a26c4f55a13af9af6f25b7f617983ecdedf48a920ac4050a8700fe7a5a055314022a8afaacd2dd13c52f6d6d5e0b41d623da36f55ed2e8fc00845468e1163b2aae35062149149308401544ef0e4a55c6481c5f2273bff06d3e1ca6fe848fe396739da00c44091c9af11f93e05af024256fbd8c767da2c27d2c504438c225d06c43771b24f17096218c36c8671d6e55a56805fa4bb78295444e8bdd8982a1e97c885d3a1f0f3d5d8218681874c186ae906ffef69d28dbfcaee39fb5d3b3628ccea7ebee2bb616fbf8c767c0b912d190223af75d046a1f8339d23e6e115bd8e69ab94395dc0fd5ece5140bd5c5ac3abc4a490518f929331495fe825f0aa50aa8e5a98a40f14209343e88cba38b3d816692397db1992eee377edd668084de11fe0cf969174e6d74b98bc448a30d5bbaa11d3a7cfe56c19d1c51c14adfbb2b9be4ea458dc0b0be8e7fa7eac3f3033ecb1203935fcf44ee827124d173aa204d69c361a426dff6c600a59707a26fa24df1f773514bb5fbc4501175a588c89dbdd15c6e846c9f07d32b373bfeef4be00a3b7f6ec5a6d63ed5b17cac3d1601eafc3c0c19971653b2c39fd4ad4ad848e3930e6df4799bd3d775c0240c610db5a5e6a954e0afe11d1422a41f8d10307270aa9f1200b69bba5a008f85c7844227478d29bf761a0fb25655b4008a20ed6f84e27c94b161d70cc410a3105700eb54172e6109ee3de51cdac5feb8ef04a866756004816e60c7d2220473e74db0265ec1790ba4a9af2ba31b6ae5c40ab020684058eab6ed0da6c0c065ec653c27727e369b7581b209b0adebb17f8e991ab773e451b69b6991f36a7ab6f07c69333395e81c003446f1c4c5c40417f0b0fbf306ed1043a27227f18a39fed2dc6ccf323e9d0aa25cbbbbbca15510ac86bec6ff3d8edf42211c582753c3e3dbeff38a1b7a5b7465691509724190f470d492472784f4e7b6797d4ce2d34c9bc9a43b18fee635c5e6a3e17cc31ad0f249339d875fd4a938778dc11bae6f87d84b0909f2cf88f336d68f37770f56e9a55397c322bd89320baf853ef5e15f171586b42afde621d9097d99f650bb1a33f15b2180d5fbbf43e65fe94f821049a6e89301cd9f801b150e32149b27b2ebdec466812ed9f643353fdb03edb798df03e4fe192b083cfe8f7ae48844b6e0132f8e2829b396311491504a9b3595169c065d48d066439e7a23039454c712b6225eaa8e4ee6525371e2f527f76ef9ef7f1edd674a91548f017a9ed896e366a978497b35be6cbba47e267fc3362bd1c9e9cb70417c2eb3dbec18725a70fbe6b3e330cdf07de8a594728098c0f79457107a6cea3472bd77b13757e95791e258d14b1d15453fabe635571395974c190e72071047916b0f0427c742122118a176836fc0aff1a154dfbc5a23a61303ea5963a347c81cc57724b442b6f99dbf435a4eb0dfb06046064904b0e0c9d43a126c9aae20028eb31e7eb47c21e6c65d13e93def9b0335b017c90aff7d87ce4b173692fdbd1013ce009ee86ac6fe28b2cde55c792894c8f87312726574e0c7fc5fb20a0d4165cd686523f3ae6c236584b9ea3c04c3fba3510bb101071739dc09dcb48646f51d4fbfd54fdeaecd76b412b00d73bafeaf05570220202ed85cfcfaa5cdcb54efa922efdebd943d3b645dd075591858b4065f32897660fce12c6b4a99846788928c43c2281f67d342b73e74fc984b572978d98c84caf0aae4f941db165a264cae71a349aba989564fb24957682aab2c4069ce517263295b3c04e9bf71869ea92567a6470d4b94738bef0423f3b0de93c36f4b4134fb0377d01fa250be66a5dd52fda56a75d00386863d07d005860dec1e739452aa959da14e030192fde2d5fc98436c5758f0b6105e9782f423bb65a41544a0e646ebb937a35c6debdede0fa8d512bc830d8b72c66dceb462825797b84b4698ca06fe3d60d15ea22d92f3f4d0a4dbcae034865f22c8797517633d583db18e5c0a7733b3ac8c1f2fe963466e8ea83abe55375e8890f945ad7890cce2fc226afd92b239c94936c5f8141e4d250147e9350a6ce85f26d8cddae391e87123d29744a58110d2bf1f38b40467b60b48903dc080acc736eb61bb8e8c7382c7fb44a32a1805cb444335c470af9879d1843e50638161c4c6b0eb0defaa3b27a9bb02b25e6b9701389571da0a46ca466ff0010c16d45290ac7b220936fa665d60603cc9c2a9cd76ee73bdebb619062da0ff8add48cbc37de77ba17e308706cd399b0408ee1bc225469b1c297dd2e55163970478579ea6585c951edac0ce584705cee892acca12cb47554acfb9f50e037c51f0251894f0df52786f965b2cf8c39a17fdf57ad13dfd9b9a27869bbc493ff0d0966a56fd5854f649e9878a98f8525070ad920b81451411c39c46a53a2c128d267e4c4bff0c31d8dcdcb66cb78ed484d6e546c651c6731bc6d02b30d493c25f45987d282e226a6d79315b31371a00188b63db46a6e8454d0bd88ee80467e13c871bba93f7d17b8b3660e8a07eade5f15d41efd85dd858e4ca3cce4a7659bb3a41372324ddc72cefe151ff92ab6c9e780a11070037ac9b3dfe022d2d66ac8d3368160fe77e0c25ba78e18974e15d511aa4131ef401e7aa59e00947365d25234845e2c3dd755762f02f053ac9e7840bc81263981fa3fcf760feecb26235b52b5b3ab7d77afed121f80ce024a618642b789f1ebda0e5845a1ad8c68b807089b69396bd7b2873940e1f0e3e38359f3c49a290ecefc81d0645771077c7b4de81b3dc7ad8ccc396686315e33cd73206a896c3d662b9a09b816a092e4e70f694444664ce9c601caf9a356d352301711665a9ddc8284e60ed2b5d85eb401391336a02204382a9766fe34d2281a80356057286f5f0e3acf9f1fa91461805f6a47c7cdd456a80790cb3b4cb0d7d2fe05bf3cd8b94e8419b8dbbf33fca08baf2a43ac1c0cfd889de8907734e7013b5398eba7b884745b1c2f14162c53397fe28d8760b2afd5b97d08e0766876eea75d7100da0eb1f959bed32e56498c6b3960edcff0c38d23d35ad2f8569e6907c8c8697e3d474326b32ee0dc0754b6f8502cc1f40e2d39699c4487684a98a41c1096bec3b0c39187c210875cadbd93c1aa93b6564d26654c0e271df05f3bf53654a9f6e279c206ecab7ce10cc2d43dea7785445d27db05aada81fdba0631879938a568e294e3c57e35949bb1e51782b5c1e43abeef4debb8ab4274dee81a1ca4cfa9551e68b70f62a4fd802419ebf9ebd37f7583471ffd0452f450fc308cb4551d4fdd12003fff5383cc4a9381501d59b1f745d9aaa3bb233406e6e3aef544798390abc095b680145dc9a26c5cbde129038c7a44a031dc2c5a0f1aa9077011659d3a9d3ddd4f84ba3cb7b2362ff18bb0dd71f67a8033c224e429a78cd4ed75aa37d066dd4f11308517540011a7580db022d27b7a6e0573ad43723803e07779e877ce2f15d78fcf29075fa502811a59e08d960f66e06cf33f9559adec17766d4cffdfa5fb27267c697039c586cd081d923fb0e5d57e1d67fec3aeca5e0c93bce47e976c8af00a2c44834be94ab8da27f15f1ce77295f9e4f70b0b857b94041c6954187d10817733202286132a43ca32d3b1ce6a4b8e2bb1bee99ba6eba0282d7a2970435c3852bd979753294ef84739fff520097c8a3e41e9be082d72c702f819352034006e08786a9635ed1b4e67c38dbdff3d843b5f0d8ac0f02728ed0f52950ce0e54391f218acbc430342bd3a47fe42ff943c22b9c8a9d926093bc6026969d92f968721b2a3b6784d2bcec62279cf092a6885f1e4bbd80c7f184ee0b9475c524f784343321716d3a419b189472df43afeaa9e17b53bc6757a20180599fd7ce3502467ebcee0a655536b8453a7d3ad28a327e2feef415d6861eba8b5935ae462f68602819a266a7ad132d450a90c20f4ce2341746341b467011d4559a74539860b1179488a0ebac9313c9b46b1a3251144aca64294567bd5a4a6886704203f64e03c6a910a28269abea8e61faf767bc954a9348fd0953719e4903341c5a5dd1e9885927184a18b926c2347ae17ee5c846efb60d47777e7bc9b225be9446e5e3a562d23e8c821edc30f1a5917aca5050555eb120fd544b9ef665c277054785c7c01ec884ab010a049d247f71daf8f42ebba0d26582ed6119888ec4214555071f2fc0e32ed08bd4ef29b495bc357d3e7416efbddf45b72c8eb8c98635d2649c4ef7e861a2d3937a98ecbc6e61473c41232762e9a540eb5af4b3a35aff1d7858c4fad29452e5d57cedb338c4dc22e333831420222bcc47609e2a0e05893059b3d06f6062efee6960a177b2074c1cccedffdb6108d48af4f34d901f3e54ef99090f8eb3c4e3df932f062468d84fb0d06d9a8bf68fdd5c998f58795e32e590ff1b5e2f6b47247a49f6f954a28ebe959a57e9293c7e939b3ff4dbc718031d07a48d6867d88e4d7afe47e3d1cab426636170a96de0f0d61482a3029bca44646ae766bd533fcd1235c9b5bddc528921728522dea61dd5f480e5655832657fa5afc29278a67b50fc035b5b36a6ca7e75ea23fc817b9272cc6cf0c52e375aaf17332c637bad2ff2751b9956d878a3bc081f932fc75945dd45d2be91111eacc6d8e1050c35d8faa72da4db46496df69ca3a6b111e8c1de8218d95a5c4b92e828580c7c25fbfd12f2c131fad195b9512c83da159e87c9a7a1e17919ff791dde2e912bd32af2e6016fc0f055208eedfeb1bba476f2a17a64cd8afd9614337d12a5152362fc85d9364bf425036132ae6c1f6bc819d00cf0b36e0093b47ab8587b4ce8c9dd5e722e92c604d90c432fc266a3bb0daf1e88513a40387525d4bc48bf5927c429795b2d0fb863c48e7d6a818f1ab6fbc2675e117bd2875baf09386351f586f79958f6b3360a8ce40a479ff91d97e409df58950df4084b2e3fc54bbd0dbb9b24ef6dc16913389040f1de0a5a3ce1c4738043ae76bc9ada3958f73795993df56e600a4d82f6d6f8c5c905297882f20a184a9ff69da493d25931515f16ae7f08deb2e913507a08f351d17f807ff079b30d3985680d96d2a8d608cc5be9a07aea20b7a5e329d608cf4124967f1e7e59e6d82e351b8bbd8c93efd4212302f29bda1e853ed564c5a3ce24c29768c2ca28a969d78f43eadac8bf4a7c503b5e62419d1e7499d564f9bc4369cf544eb4bbb1614ecc9bc4cbece1d7c52e1dffa41b42a68e1dfebd054dc8fe1e7ba02029dd7228c4d90150b05008da604c8ef2dbbc6613b8af98fcdeb5510d29d8d6c8962b1feb8e759116fe59c03e3d856f27a1b4eb76e674f9cc233e74c878ca5356e0821d5fc3cabd08dc54606feb82ceb19d2d0072ceba9300222862332ce2f83a2b5108c4ec381aa704e4958c80787f2c22f64a1f3d354057a1bae2a9a99fa9976f532d7638c8e362f2aadd842e6c984d1f38614d5a8eba67a51bcede1225d0981512105191df768e07400c7b58eaf68aec8a548907162418540d912aa9cadae2318ce8e1d47cd4dd9374cb7c5d17b7ce8ee421f925697fa60257b029fd64b90e33001e6a8c4a2577d91a148993c2be3b71343e06cd783641ab223eab034d60361209d2b1d33132937394b8e62c323302c2fd13d8bb90932f4d9d65ef69634a91585756aa5772eaadc09dd9ca01dd0044e51b7d9e8fdb478af9b6e435c359f4208c25b0e096dddd4632fd704f782c5509b0f74dac886ae6838ef58c9c69855f6dd5271cc0cd8dc431a4f7c8b904ae24455dd8c761e8f5684163ebc24d2c618f51f01e88a43854f7761dad0111fcd0a5e96ea724e73162aa42660901ff24ac0ed8b96617bd9525f5c39ccb8e04b36ad40ced7210b7d795eb5a703255bf4ec56927edd09fbe10ffe06bd257b72223c0ee1cb5352cf91f524f39ceabe015b87cb27ee1dc2b660219fef7e9a5185f90c1f824cbf18dac49915f8142c558706a14fc415be579cb2dc03948c5fe20e9e8793821dff43ba23c63877302d987e16094e316c22d7c8365d9a0434bd89352d970ba637833b3474e6197256f12ca2f28e601114ed0db92c2e6e418acf90906cfe3cc0879036641e598434b8dc0933302667aeefd6e7130d760dc0ebd5e6c0d6347827beb2238d5053ca38e7dd7e885a6b871646831cb57875901e5cd2f7e28e777330a4ce40125117eb47877aae17efabd907bba09ad2b71a9f2c399ad51330f5df98ac08d0f3941c2d4e908d62d845a6a85fc74738d280f5cf2bd3583cddcf0893510f074a2daaba84a229cc88ea74e297fecbade1627fea965df1e8a2dadd38f0aad6c256f3ed695bc5a86555895c42db51943c3a72e119457cfc9ae825c8b4fdd49e1eb1e7f2308b6ec2045f6d228bddfb73ee6d2171d1df5ff554e2e795692db887574cce7a76d2a0bafe2654df913bb0cc7b6ddfea533116f46186b5e7b9873ba19c78a0e2b34eb0a121fb737abfb892ca4c44486d897a2f66139c27a1d19d8e1acd0dcbe627c303b8efd9239fe8a90c67e3d35e9786d422d1d5794e6046480c92ed43ab4b030184e22a6afed16d39701cf0c12deb1b4aed62fad954a52f3a72770939224ed3e205133a75bb0fa6c6c681ab3f348ed974ae437714b1659c0cdd2f2bac41a085cae358fa6f9bd46f461e6522921be20589a058c7a93061a1c3406ec2025a18b078ae907007da13c7dcad3682d9cd437a4cac89f0e1dfc17a163b69ab3fdbb487c5fff2896f2f27dfd8012622596488653274b9b9ceb218111a5f563870934b9da3fe7c967f6651170613a885ef6ed6bcea4790825d663e6998b686751f930e0201cb77e5339288b36cdd57fced77294eb56e1b7040935fbfd66ca7dbff3a3b372508329d0029072da2bae5aa406f27cabf80f3fdc84a617aace81d38a0e8258cd16472e48a91a3fdb6228f7e7875529076884b2579acd76811e4591d6d8cebb7e0e5d0b331ebb86a660b13e1e108933fa282f7dac737a6b86f5ce4468d17652a2bd8758ff61783731973a15698c7c9e0bde8277bd8b3e0527b6a2fb8beef909730b8a6e5d085cf6e97bc60be299f616dcd414a0f731c3df0d09e1410216e6b379c34f3981bcb0fde5050528ff0a378cab79cd1724097016d4582c772aa821767e16a6f67fbb90143b330053a967694a5bdea8439e8b6196d6539d2bb8f4050aab1884e30458c4797285d8404c68e7002d3907369781af48be6cc776a6bd49d47a42e3193677f0dbe18291e38f7e4b5e62658dc1518d03cfff5ccd7ac8bebb1fedee2721c91215b98031eeba9b4a532cec6195d78ca17fccb49c85854a21cd04edde19f3f4b65bbf81a05d93bc2492158bcd2e14a484f4f4b5edae81b309ecc709b66c77250636099161f7850595f37e1f70af994854685ccad134aa7a0aaeb8697dd8b71237cc4e1908884b0ddd0d1f69b4d39d16ffecc0150f84b111abcadb5820c24e2493bb665baead34396e443d22286f4ab6c8c6c8ac32efce57ebe5e9f3ef73d2d7f624b0747104591f6ebac235fdacd3a8c8e26e5b0b2364c6b92a6d371ee63e37540b40aaf262cf69391587ca8ae0e55c6b9d17372bab2c4c7bb5072a4617a04a3102ec6fd526014dde0e8301c9e5cca7efd8078b6a8629b888ce2cf0e1f47ed6272c8cab44916a139dd8d28fd711f74ae863380b63cbbac9aef4d656d1d3d2a664890ea66d335a796f4db2d077b3b5bfbfafbffef55c9977adb704cad978791c299635f5ff6a81937ef4dc8690235d1d2ed74b40c3aea2c3b96206bb22b177b14c391c6ab1ee67c3a8d3fb47ab652690c5f46688b599a87dfc76e631b87a84aeb702d9b9bc6a14cd00120b787658748169c97f727540b7ab11f676a40bbda05ae6b9da265b2d7c794d6ae1cd43765e44d0b2f012f30383ea8b7ccd346f91bd836d3cd8ea52e38b518a6d43958925a3d306f27ec1e287f0fc6c2c38289c1bb35d70ac6d826564dbe564952c46629963f0ea410cf138c7e8edae67f5be2ef2095b2aefc044dc8f5ec7bf2638940c424bbbb1bd748ab238eb9bbe5a3894261759d1667bc7e20539a5cd32e453cc68cd04b46317d3454e031042fb56445567803af109780783b2237c87869026327eb71ee5591ea5eace426c963cfda8ed3a3018d8862aee2fb71001bb3a41d411f76ac1e4c5958a343c5c0a624738e93dcadd534868b515386dda7baa02810a66a2f1d83c2a5b98576062e80a5b071711a92d8be2cc2614f132a18e6b44427e45ef3494bfe0dc79c44c8aae64a7182e4d4a7afa90e4d6ad06e7078ffe99a145c677ce5b4792e0dad9d1af0f41d0333749842c40347516852170801afc5278b7a87268a263bf99d3a507ea05a56f747ecb15a40287592da13938a8379ff1bebae63fae307e3c1051b7c92f5d3c915466711db03a9cffa8665aaab496aaabbfaf37e0ccce16b0d6b344360c3ea13587a1299f972452e6fadaf6b9efc227ed185db47b836dcd16111f9a3263aa51d1a2ddf803b2199225674d2e59445747d48f8a09dac330153fcb31d7e3451eb9ce9b2878978fd43be642bf8a03db87b9b3b23313ece2fd944d9f248134d7c688070f9b5b09b2e128e682a5d335c692ae914b7c4d42596f6c39ac8553554001b54e5c11d7eed43ecbf799dd88f0bee94ea8a2d043921350ce4fdc0c0738b513d3e890fde53fc66a887fa7ef7a38f126e3e890db09385300a4ad7b427b1689523e0b229c52d7ce6718f7f5f7be98d81e1471623cd5b01e6637c71ef2cce9522360677715d8f6aa72de6cc9d1ed9b2ca1a31d8926021d424b33e6d8a368ef2190b29a4556e2c1127067007e5d3754ddbabef31e5528c6df4aa4a4c295152dc25c99bab8a83dbfd252f86af6fb53d71f8b92b538fc689390b00671d0ef4aea10d0f9d06e587f5d74fc5c725a99c7bd8afd1c5308a2c99414ebd712f92bde2f61a38d5742eb0f6c765af67625e13aa542fb4a76cf09437a9374941cacec3e4ef0c949c1e4b8869e15c25ce98475e8ad36e555f6a19a261c6d52857d98bb01586a02d6f248eb750ca5ccc9ce06443dfd6c69fa64f8eb9ff4998e42f4c26a70a57e64fec3dd31ddb37636e3419851d43c5f059381126263a09b4c5a94ec113450845c3c8098b138f253826eeed397e71967c758c529040e61fd5b1e4549e2f1b92ec7f06bcffb880d8663eb98a0cd28c33a210dd973f6559f646a3443864ae101521fad37e415c0ad6e3011941f35b67179dd171df541ee633137279d2025f791b7eff58dbedbb65eb0dfb14bebc888dc478ab42323d4ebdbae6ae1b70beb3e9302fb3c8a16472d10e75193bca5624ac0fcea223257e08cd9d3f2acc8a7246077a4cb6d3f8f655f97f0e965f7d6b4fb5fdd42eec4100d7f5593070c5d08931b70e19d4b30a107682bc5b51da31b3233fde98a4708081724e7bc3883d48acbc0f5e4d50bd6547201e9dfa99978a33b0765bae41e6110d4df4d6d2736e2bc3f3a40e6730c1dcd9d334f37b1b2c6ff30e7626adc20e4cabf2deb0ecd0a0e93eb52d25c3842e6a1ca26c4c59382ed413bc047964bfcde287c1746ea71e133ef2346ed446afe860a78a00ea2f5f0e369edcb6867ef5d4cbeefa88846b55102773f7f197471f846d804a8d9a7f05ab12414509fb003df62e4eb29001a49477eba13ac9ffdb61be140856f37b00a78f6da4e68c81a75bc05ac84389f4a43cb7eaa80ea11a77d76087997e41b5a8a75620f4cab45617c8c50a0845a7b7e26412e5f84e6c06ca4cae11e413cdd626c9f3c0dc31e7a9eb4501359a63f4f7c04618e2e9133441685ad84741f64b98e7bd4dde6847652a8fe324fdd7db9f91b5c42f1122009f9da60d87d6a74824f214d7133c43e0cb87cc3f3bbd53b61fbc694dd8061b9cf7c160d474d7ada9525e623543f81c7561fbe6790428fb4fefc883984b5d0ed98310dd206e950a9d420c9fb19979c12e9fb3f02f183cd540744b055f2e0cef9b1c0c0ad23394955a30fff3fbbe575222a46a3a77c506deef643fe94c5a6280dfe94d46cf69ab653d820beed3c66dc9308fc02dd4b2fd786d70a6809597cff70aa5b824b325dff4d8b698625416bed9ce59fb00351dc419108d066b83e3010061c899e10754687addc60fa6ce0db4299d4b300d036c5c43e396ed851d26149d5aad4acd4c80d6acaf96347235baf717d1543271de58d3ace94e46fb2aa02b2acddb904e77ae0054721321eeae075d8cb615a24b09489f30553d2b51302db9f025df60583e7df556292703b05e2cfbde43c8bc0bb1a4638413aff980b87083e99c10e5d475e9f1c4b212580f21cf1becbf96a99379d019cbb23d68ebd6a78639cb8c857a10fa8debda13e9abce0258ebc91113e5c312d1f043ee443dfea6fb933b4b0499e07f7ac77780c3dcaca4a0129b89c5bd6fdf121c29c5cdc72080432c3849500d5967dbf476bc1e92b9d5170fad1adf9825aa72636a093c45896a4ce53c40949592e9ddfc936f647aaa8bf633097e9c6c60f5f6420e284111b4d8db6def2e582eb9fb2a21388280e17ac2f93362f7875d2475a29f7a04c71941b549d747fc245d6e78774deb7367aa2f8cd338c89892766fc8f0d9715afab547e60797bbf80152c22dd71f80be5593721db96968dccebe5ab29f5d6ba92b2e4416c8a7c02c3265711e91206535ca31f793018b97df649d1e806e3a9517e424cb609b03c8671332da8d2a5de65a89d732cb35c0c9340dfefe63a9bb85724137b041910190c355ff95d37990d79399915cbe5e04e68e90ac5818a159db9d4977708550de235ff2b2eafa896423063383cb555d7e843afaa1c58f2bf29c5045075cdc98dae9d827eb111286dba14df47cdf629571db25ae7c2801f9166c3cfa7348b539161d1b5b2f4cfb2cbea6647270bc59bdac2293211b90cf6ea2d2d73684ece2718dcf753c04ab2b1cbfa429dc156007a0d1da7838c79030b4d9320d2b84c474d5915eb5b6f2e1a864bd2b6c41508d2a5ebb14120ce1cc62bd77d98f2adb396214993ff909c6a370519cab7064f9fbc942f34c3dd6771ced39b20eb7d6b5b4ed36ffd86f98d564bf8eb46dd8c6dd03acf10dd2e1425598fb6c23a3f42d4bd4ae50fc458625798829b30c964e97e2b587452cff92137fdd33a7b49fbb2ca1979b09c1551da4e1fca1ea23b4b90666384b5a15d6ee671ab5bfbbe15ad63018e19df809cc33391482827b58a365936754058444e5548986e9847e4478b915b8d8ef134bf5486965f8f4dd2ce810017322b35177689b0b8b366a100eb7d98dd4ed5bba55c89acd6ecd29e6093fda6c0ed60bcdc448b8a998e4c3f1b0a8e7b2d1bd5e4ea3ea2c57d846519a004dd4e8353186cdb1c83c9356f4f8c2fd1b760a399cd684dbe05b53007c935540ed861dfa3e45dcc8f6261495ab7a6ffdb5d12ea7e4e169e4e8fa2e18627593eb5c12baedc0bb719dbfdccedf626e1b69c077e2f3f4d12dcf09978b4415f616835b15bda8ded396d3d0886460348425c04a836e5a51f532bc37f65f364e8203faf6387a85c8db38effd6913d02ec0415d764f110755c261b29965d138a5fd87c6daba68f77f28c924ab0f24f34e9173756ba092d6ee5a93c466397c81f2cc3e30d9994a17c333c582534326a2d2c9c2b2ae2fb9866a85109c5e60e2b31ae8ec8168a918394b4d358331fddd4996e6a21b697eb9949f76419245501eb3c9bd96cc36fc2f6c35371c50e40063104c48c8f3faf1deb29b180aa039273950eb6b8291170aff0b5fdccf557f9c284aeb5c18793787ddec4d812762d0babe8cbd9356a577ac343806bad26d413a857d0f6fbd81391180d199fe0d75053b538a864cb75f487d0d72498728835f558dfcbd2e001569ca197f25a8044ad00e0e3ef6aec0ad9088872d1d28e9d6689f13b2c574cf3bd55edf68e60de0f25e13fd75dbf7dc9fc6a0510203105e9724d98c47ee3cda2be90775cba2eba83728a2823edc9556d05c342cbd98af1ec66f4f8635939266daa08dda208f1284679aab79f985ac1d8aa5b9f56fe16ff78c10fbd04205fcd483d5461ce0be90e10ee404ca1ba2103efa15861041238f24850fe180e5ba86cc84aec681daaeaa9b3ce1809bdfe3bce3e18a92f5ff4031ff391253ef0b0fe63ed39f8fea8f5b90873646d559d174ccc7c0a3d9652c199f026f722e9c0d41644ca387e519527c38fa6caa35be6aaf018ceffcf86e37e66623b9d33d5e7fb9456f551a832d2a05776b333678c2a2501c4e2f3a2d478990f860d5c8a44dbac4414cbcd0a976b12554852664319ef67e055e0792eb6b370d8032f41ad7becf86fab80893b1b6a2fde65252d0e79c08360421c622d4ad876fe9264d23c432473127f062e957d438a96809fb20de956f02678a66c5f0fc6a4b58174b1b8682f761ca6ba70b41ddb7fa5213a03cfe7caf43cd9a3d20a6980442531457bee3d5b8b3b6bebc7188212529e23fc2f0db7d9317cb878b09221475403984bfcc017a204a5cd293d55d6e9d34d3750cbdede3980d35ec39a6fe33e0433c04914dfcec71b30fd6b476968304958602bba9ca9ff94f48926e876bfa91019465de3d23f8628a5e4ffd62c29c55b847583939aa7111ed7e8e379a4a9affa2edc5d1a8bccc9bf879a4dfd353067488aa31508d62bad48a2a0460d8af9b661bcc78b8821dafdcdc9f069da413616a540e87c0c38cbfae3f9cadd5f1eec4cff83618e402b5169fec5e778baf51b03630a9998bfdfe4b29160f286b0e5219e07a098c2ee4f209b019837e08cc05f10ee7bdd42473413d2b65c9a4867541c7ff0bf6dd93a3fb7e68469c9491fc0a9e28b472f571d28bd5d1e52b5fceddbc604e17777c64e5c8cf0ec923d76c6d044815ca3e008872d8838808e2ccb0349ebac4b585efedca0974a257f65df5e599d869e85928930d9e0065edb5c7415fabb1675126005404ad520171ac82acff8c6e01846beff26157323e803966b64fb494be904cf4d8ddcc8f59831206c76f5f621cc6d2fcdaa13b5a3dc219ec106460f9621299066b5e8f56ee998dc37303207614dc0852594e65d6c92302ddf093668eddc819d790bcc6531070f9f85d877f7a96cd4b4f443bde655a2596f0f42cb15ebecd7841ace5024ce77e0bf62791567fe08e482155b500f61e0aa86da906fb95479c9e7648bb0c6be4ffdc7748c341fe633d50cd8b1ef5bfb96690530accc5d34fcedfa6a7afadd718dc4f54bfac1888f06e141d849fc925336037561b0b2ec80781708f4234984671916bd85c1773080caa6652434553ebee9afd3ec0630b856ad87ef8af483d75dfd5444bf6f664ad557ec90b3a5632f2ac53710176cc2f3bf47469fdc91a2788da4e3e557d6582b14cbc62d2cb4fff8a47cef90561df8370d55a39e660d890cf6364cb003a58fce8af4687bfa745c6fefd7edac8d7aefffbd775787754e9dbcba61b7e62dd49d5140235f0fb77887312254af3015a4e22bf9a0a186734d27b58e1e8c7e2b09a276752d9250dddccc1b62a5d9dd96df5380955038de86fa66a8314c13a43d5eb423b5d805fad16d7ed2a40a95e8b7cd7e26b29de26380e90b4afc29c234643823e27403e99c3993313c85a5fd7993ba6928df8913001a1d64d57c085f43001505624532381a9f0085052848832d059351f8d560f033858b288623c18b974679b8f49262e52b6a64e41c306baa95c34832e8c126c18c4936471dffc6612a826d260ff64432db258f8d33c33d85b54aafdcb11857e83273ef6b6089e2b3ab262effcc1341ac0b17cd4f429e016886961b62f00276255d1126529883ea39fcb2fcb36a0ee503ec226c1b043a25d074ad509da4543614dd04541b11e30b63b5633aa4cb85a2cb8805a87c78ffcd31e0fa8247b2153690911ee7fe9fcd72cba0b536cff627fb0339d8768a618cc4639f797c57619a93e3f18dbfa914a6665a3bb85b5f93d17e3d30e8efa6c707f5ad79ae2b60de2bdd6c869f9d8ccb282bde3930a28e723e3d03efdc46ac7486f512ab722743b9401db76ccb182a20983893018a77b7044a8d32cf2e95c6787f4449658ef1d2674505d5d613fa2df6f89c642d9c7fe0e0bf4bdf7bedeb47a65031fd4d2302f0bef4782e8ab313b92f5f98db0d3adcb440bcf36573d6673914d3a060fbe6e5427b40fcd5611cf0d5374ac6a42296e4a5819c32578bbb41499fff1fc27b2df9f4bfc9c5e9c945f27b7a116649a37476e6a77fd9fa693915fb65a3f6e0cec389b08ee9777a434bc1dfea60943bfb2d4b0bd6564e420abed7ac49242cf8689a69c04f49c2efe55a19117ac5879c4881fa5eda85de243ddf39187255b006b33f3c5e4f0b6ee9d8f35b0fa5492234de369563a013958a26016d75cc5b1aaf8b2a091e3e839629db74798fe7216ae9c292018fbb6a761cbd4c874985a2af25c1d4dcecb07e102bbefa0da74369bbab3097830ae0a9438b4df6ff3791dfa40799a582479d1d42a5e88ecb71bcedc42416171b216a842720f5d3fde32728cba24baca125e13aed64d2a58b8fdb0ca2bb9698a113d6b2eeb42a44720b43151b29852ac694bd64ae8f8aeda10a2fa0a3698807d23c48f3ad8333643a8b22f8cf87e7ee21265465329e316c4acfc9f999a1f537a4ca059af797ac96c0e8c81e051e867f0d212f5b64177dee4c5ebcf0b1129d38a6d5f7b1020e045ad8527d28075b691494e3b961165d38c62fcea3fd366072753d4a2cad65b6fe6ecec84b6acb600f6294f9c4c9e6a8bfd8937f1b67ccff1baaf9ea57a54627344fe73b82633f23e833403a03e263ae83fd9322447eef4f72a463ae4e2c23be8ff3c50c35426221d7d604ab8e4fb0358bac41cc44ebd4a006d7f1dd8aac9197d0ea743bbb775ac1b75326fe61f62968f1fc98e4c409f3599abb0ca7aa599e0c0bc00bfe2e5df98cd2799742dc71d57660f385a67155cb9ea4054d42f0b0a5d727b12a37efe75e086bb9d8c1441d90b89d20d5c47ce6c4d5cfa45d70c289ce9c5fb5c672206eafa036cee71741ac4a5cfb8f29881bfe293c79972bcf2ab68534c3d3ffa4287b0d3ddb398a485e53ce51e505526306a39a42368d4d7557339dc0b8c9fbe333b44950768e7cf48d80ce19588e978cfa4d86c54b59cd541aeaf0467401ffbab0ccfae51a8950d8a02349c4665bdfa339a67e553435bd216affb24683d83d82d18aef0949fc036ed1867782b4f3b9ce084e75ab1ed342dce0b4c17f4536205c0edc2c157e8a9ae13bb7d1d7fcb0b9fc30bf36156d4002a93cd9a8ed6d72f115a2f9e9dfa33044885e7de6a38c8eaa377f37b41e38981256dec830ca838b362f0d9d3c949ba0dab09fe509f5c64785fc44b5eefcc885e6c54dfe9c4563ad24b8904467e1b96bbff3c5581cd29ec6379bd50395471d97d27cb432fc84bd07f28cb487fb9b47fd641f7d44ecef1fe29c840cd8a65e695f1bb9600e2c6193f4fab0c37b426b01650c7ed853f494e66c3d3fc6fcae719269b5d043697253bd6ef5314d289d0097f18178c83778c4a593b474638f667eb4fa200b741bcef668d8ac08a50bcf792e9be288a6ff4128a28bd12a494cd3c49da40bd1ad5d6187931100e262af64fbe03db407af0182163591d0c461eb321f50a8a1d6ccb997d390de1b98de4ca84437a4da9c8253937f4d3645da988c45648328b9d8528be924899e2b69ba81d6fab6613305a4d2a685ec00904a6034c97a202c41a0e70f5fc0f951faa5581485ad507de26cfcbc96fea659f9342c58380dc7709e42d40735c12ff13da860c86d22efeb99d557d9642626cacba6f77a1ddd4b0de00087e6443acf12875d3c430f4beeda65dfe52f2557aa8b07e2bed98d8d31e4f5ba139312bd50e20e999b00d662a0cac7d1ba5257b18265891801289d5c34e652a7adb8a19f7e66ddabf256bf18c7782af24e36f03d94dbbe58465cdcabc324a5e230a7749cf9170278f68ba8531d9bf80bfd999639dbac4132968c356881b48d769ac356b2b5c7d85023f9bb14ce69e08d8800d4e6e4a1f449ffdab659c55f82a98d88d47b79d5947f3840ae37fcf90dc793383c7b4fab7bae10721e24182943c5cf16839060b68c30b98fbd543a68669e2575cd795450293f5f913949b99d8d04ca5579e45acbffe26cf7a4d412d30d952af60a133b6dbbe007aa68ce1cf8f7030ca49c84ac875032dcccf55a7f25c204f44094f1e341818c228bda3e66121ee5c817a3806ce7ecf4d91f890773b1d451b6e83a303cd0c47f549e50a4f3288a4e544fed4d06365f3af63efbb3539acc3708e7d814cd8a91f240347eb8d16c0396a9d19970ba2441a70a4c633e760ffc6e668469912e5332c610e1540142662caee6fe73c21b310b0bb0e0b826a95692a8b1d0267b8fc8008df1e8cfc23e279be3ce90a041915bfad0c7c29940ada6f445acabaddd30e65f2694bc10b1ef4dd51045ab29ebd82d861af2a861ffb8d1673f805d6acee7f1fbeaaf85de1532d7d9b3d67cccc77f1121e7180fc6eef0bf546538bfcfaa9dbaa90e7ac3daac1dc1223b306faafb6af63caae51a683ef3b9b18646f31abad1267f6e33f7fcd1737d48f3a4e7bf5b5149ce1548900ee352835a0efcada5c11313ff7a9a6027be3234b139a99742c63e3ac7acf3d579b411456158140e09398a80724f75cae67506d6f5fb595d493d135345e6de09041b2948a257290593716f13c9500d67afbed3fee616f1b35f8d025e6b47c5257f54078024233c50d03290e19adfb0bc28e72fa85bdb55b4e61b139c21dfa53303140ae510aa2f002d04ce9025c22da25a6fe4ab33da89a56553a14b36fc43929e44089f4a1c4d92ed999fc55eec5b4bcda57316381c391d2c06dd7e98cba5491a03cafff18cc09802a28ce175359265ee1e8c49c31532b0e7094a42a7c9966f44778b4d5cf31af3787bb4115f5b2f444d8498ee5af9e6320f413f13cf96269c72117d5d4af6cd96883948ad416e151d80992fb9a587ed89454168a6efcc286e2ed544fe67dcb3e783b10de4e35e2755c47ca2f9661e7f0a16c846fe43e8b31f3ec678325e9486a02939ddbc6df4cfdcab1972ff6993fa6aac39aad1c633a22a7b41283b0206694b1b2926641ddf7a9c697cd64b875b62436a773e424dee14fd280752726bc55d4a7a2d69cac6f61af18e343a942d99dc1d7c76fce2d18aecbdbd006cf5f9cfcd6fedecbc9238f5a3384c3ebb9418f13217d020b40eaa0352214f58de1f9832032791944951e0258e6e86f9abb21f6f520f10358b8e9fb5304d154c85d5c057b02c9465cd19489558d8538e136f4658cf69810c7790281db5cdb544329d144368d782771a5e474404c2b1be2ca978825e5eec67c4ed371dd6994819298b872f23f08280951c01c4954a858de5b09cad5bd9eca692c15f2cc8274918a3fb71dcbe1a029bb85e0969954039536129e56442ec4e17544f40e28791e5604c1d0e5578ce98ae91074cf5920e8bd076bb8c2235ce585c52397de453c95d54c54b2c2575de235841f28e319bb758902e9865cb7d6c24482807b31f2ede646b233a28bd3f9256eb7f60a970c7e3817149cbe61a018b6ae345fc268e5f50b6876eed683e3f4e476e13b9ca5faefc2d82ba5da143d07461ca6d3e2b8bf17551ee2387f50848ccb83d5b966ad8ee59096e264847db9e3c9177384c71d735ef0c73b3dfe8290de4215fc924bca1a77d4ba16835ae243e8dbf8f90c3c4ff3b2b740202f1cd39a6e3b56362d1be30ff6098ea101578ed2d0a55c43778e8cc5dfc20a3e8102f511078ef254048467fa0d00ed6df7d4b800740de6b3fda8fc0b53c033cb3fbe178a925609912c3930335d915344ce0c59f0f258a828facae68a28248e9a4aeda62b850c65a97a75aabf043bb3e02899119ac29c85e650b997f47e5d56d6d128f47e3d5abac27764a7e0723b59860734944e6d86708cfc2ad640cd1855bb975357bb6320f132a087835326e58f1f7eb14a6ed08dcca30f584761bec9be0a4d767ad233ed9ce9e1e40a62faf167110032515939b834dc211dd3ce40a21c53f02f6ccfd70211095407d583fbe543224cf7ddecdce19370e1bc01d993419070e75f0387bbe82d4b4e92a01a68eacf2617fb5e2351db023753d9b8eb3b7c5b0891f363fbcfc020db608d0e32e0becd82c3e93238baed37eabb5424f6b4e65f89eeb0ee83ddd4ed5e7cd4a4ea50263a499e3b7f02f58c0a536071c42425324752d189db564754c3caac9d77b5a2452daf577babca92dc38ff382e8e179052f398b632f4df956465cf45a6e2da40c8d8e3d8662a722a12e1d921dea2a9add17ed0006bef7a7383147ce6dcc2e5b61a134a43eeaf4e833821715965c0aedf1039d497c1c86802ece99d689cbd05dfe147f116972fdeeb6a7df55ec7c0c046b4fc41622aa6c7672ab5ebe30d91a0b328db29e27129b491b68c17d27990e30089c488d9c8bd9e3bc4548cc8cac16c909f1fa1a892cc0b4d279e58d6ce22f09b47b81c514b71f022dcc2f596f932281aed84088ebf5aa0dc0cae5d864fb18e928291e4c425d1073c14834529ff0a720d1dbb59e2e531ed6b7e71986f47cf771a500b7a24579c3bb602fccba77c5ec182791ce47b67c23b590a3cb1341eb3704b1aa1cbc5291dfc271959926d660f8ddf650a9223d0f1bf4c35fa6ce07d51d47f02a03822bf2a7530494d73f598dac68b40cf10077744f019e98dd5b4417d7a809457b506aeff79f8996c544efd845b7adcd236983cdcdeffe96b932277f8cd399e9c8799cf2d8ba1c988b85b4cf6e4bc7acc41830c905f4aa69731d5c443f3e389d89bf2c63901d5b91b0e92144f3a84855697b0b1094e92a888bbea015f89e8b0bcadd1db1d922cd9862698bfee9d8b864c3344730a6cd3cc11006abb1e9706e389efe940c808bde777e93ee6ddce9ca39be9a6a339a42d4569fbf5f46ae7223010c423494b2fc66b67a0811340d6a5a048a39d547d70eab693a045910b4269adf9b4045043d7cc25e18feeea67ff27728f54d1128609c611684c4bcf44256c321b07fb33046f879fc01be79a1cb998546c54bf6294b792fe64242630f428af506d7fbd6d354c07b974f27a63d1a7d637ad9599b1f1fd7237595974d6f09946175a8a70e7c2208e4ad3c29cb51e13faca3bfd119740f0df66faf2f6c7b1905973964e52aa23369556e3b4b50cb148301c90ec21f2e3514664b7d48b5b986b51802720fa832435de1b4f626ac408e7468e8486f52c22d0c2d5716768bf2453f018918f2b8134fc6808cc2e8b2545c3db6c63ece80dffbb57d0e772274ea4982b79d3d4e21727a671c3771d234c584910c90f009a69ea1f614c897061e2c9121b300c557e0de096925fe80baec3e8523665bae01bcc0ae5ada2796b2b79cd70451433037ab8774d8bd4d1d8abe50882f33e3cbc65de4e88e10f4510b96d49cd5b71d76893714e52471dc7bbf4518d5f0050593203add46e5153090c62a769584cceca3e055f28ba17ce1b4ebb38179274fda400a5697b3d4b49e71f14afd6ac4364ac03edc1ad407a89b5333ff34909b2a7cc1311f307076040ecc6bc5c0e28b75f0919d6099e2f2adaa82d1608acc933bc48e794e2f7808d90ec06749202296035d626ac360c86334fde5932320285e999c8e789a09fe6ec4f9b6ed9c18cbe28be28f048a36753d5555ea5ac2eba6cbcbb00df93957f1ada663cf5923b819342e1dc74209bfc9c9b6a5909c56e027b1d791fb7b3668836fce63a0dab32d0092d3fa07ea5ff41d3234c981e3568421695b9f5db08e20ca82a94d26f4bf6b7cc892c92e3b4192300a1657854ca510fb4fbb4786fe2846f45905c09dffc99907e524fe95648f40bbc4da954c09eef26b445e71fe0196aafd89df2b0ca52bce0cbb69a4890ac686d6378fcee0df55cdec2ffa1e3707a3da744dc63437682ccac962fcabb1c634d2ef21b782eaa9784dce77f0a00992bc3daf03c98cd61a68b2d9b5d7824ca5091953a2c54675d3b774fb8abae5c4da20cfbbadf82dde6285fdeed30ab00fa62be430bd2543cadfd82bc65a66bb0649e2da7f717813d93e3c13efa07d60c840e5c998dad29d79e4f33b5edb2214e8fdb968ac1a1ed46e7cac55b741c51762d95ea31747814eb10ab206bc87dc9f1349aa3ea694a2c0583f048f50a408f7cd4fe90688b2561f8e0fdc97d9dae94c363471a50d7362673d8076fd3af6f44b617318d12ab132dc29c538ea0dd5c781d9f8ca584e6dae9f238b8177ca2ac259a68aadc64c3a87f2c791bb793f7fe6211f84844d83dbab7e707e85623f79478e46cb5199952bc103a8ee027d6c5f85af07fb5d37053dc5d7adcdaa7566dc1f95a9e320d99bc3402cb4e82660b804a01ce333bdf82db3aa2aa9c118830e94defa2073039d8027e20077a2d75ac77dea1c4d3730663684e220d5f47bc190a35352fc91d9eb24de158583d0cb3bb2e1dd0a9ec6bf07979be9fc39d6dc4ffd50e00dae4375300255899bbf3d7fc345c9d4b109d111c2ff05e49cab2bc6ed3aa7db7e3d076542ce5d5fb3cf47412892d1378fbd005c91cd74ddf26d8bbb218ef706ad9e4e8ecddaa8f8b3385e939d081aba5c302e26c0e0bf9914ebe1ff47895212f82cfc97228f4f4c69ab89d7f885cba5f57ce9dc43d960f544a0edab435c1031b571ea24ba724fd0f252e04ecdb28b38e912f333290af4d598f1c214ccd74cbe3a9465def4fd07f2f6ed1368991209104c3fc4583ae8bfaa19264882903d65f10b4dac4ee2cdbb1e9df0e15ddff475a69a1005f54853257d8768bb35ac237fa216fa758b18e7145c90eba225433c04dfbee01601c6dc32911c4b2ac02447001f357e68b0e66a9e708cfabead07996fa28a60896691191396f4f6f9a86c26130e66eaa3296c81499c370b346c8565748b3958a4722b209b66f9e15f6424a228656c8b34ce5e42e9e04a63143d74cc54a3cb70e318b28fbf5977a0e99993af726cc9daa2ca8915b6da4aeb05912242542e4b42750554df1a23bb29f5ba945d67d342fe655b4f5bcd285369c0ebf3a23c884f93ba7f23416a435bb138e9ec0944d566f56957b72cac65b60592b2c07adb104f2ecb1c731611090f09392d4aaa876dc050908ad4a9baaf7429de4af111802892f1f24126cf95203559d3fc6179739cad522ce08a9cbdc1ca0499886ede823cd9c62d779f1020b56be0b711ea2d8fbb09a6895d002d619b5bd2cd6cc1dfbae30700af89362c0021dc54e88f651e27e21d6b61ec4204b8b9b6391634fc3e040b672e607e6040282e1c743d7c005aca59893766f5ec80b5df3c0a28b54644c23150ea5854f3daaee6c648e282f3cbca2214cabbcad290b06ea5e5470f285558b6532968972f964bf52eafd70d2af4bf889d9f7be2025ae54ea657cda8e737b1322cb191c279f5de95ccac6fab249d04c7984a31e1a21cf4678046a77d9f49b9e810ee8a72edb9e9154940d3c4513c39bdc96a5e74739e63a9729ed9bdd9ade03e39f33e68340212e5496a7da45657bfb33367f7fc040017a35598f6bbd3bf9d4ca3b44c5b4bcca4355d51bdcc025e3f306c98e409e712dfade101a82bc036e9766f99869c1edb0b2daa8d379ee7e524dac36583a25c27e9a3617c2eb0a27fc01c635e728828955ba446db634ece191c6ff7f322615fcf4b29d4bcd9ba9ec8a45364e0ccc97b2cca7854ac0c2e867c9ac75a68885dfb437b84bd768eb9d31a19b400cf026ce343326c4856d23fc5d357ebaf6f6de74df1d7062cc1479520a92a365177b6bd02509095fcdfa81e152dcd351cacfd10b12c1f05b1739280682be31dcdcf40039f26886170c856340674bb8aede95909de3ff65f502f101633b7d4f7ddad2af97c89de225878f83f23fea49a217f154710c3649ca885f7c6c10795a54696b2267cfdfe32de727d2c5fc64f37e105bb8273b7568cd5f69199f4903c7aafe94b00dae975a237c42937f7ce48b861b6478166f21d87cf4cbb379809e866e9461cdac6ce7579ee9e136603465e6fde00aaae5c31eb314931674df7523a37badc7faab65d55880393a84e2f96232d9cac4888689d59d84db91a37c9c4d7dd430d839c17610fc8494c262c55826fd528ad79d7b89ce45422086e00d8ede7d058356b7258acc14a9d4f2246f5c7de0349130bbc928f6b892a1991b2f7ca99764fa22db868c02cbcd7c002a7c3894dc045fbbe86927db367d536c5f2284f9da126c1f4c51c6c3872a4d58422c6f061438c1ec7633dc7e389d1a2def000d9a81d0d5a9f921f736e0d963b36acbc0700cbd1ba4ed52da7085b0c7ac7a5b0daf44b9e3b3f565c317ca91f62fbfdcdd1d57e2dd5f0fd64ad92cf3dedfd61458bbdcf65119dfc0944b59d534b8c8eb202d064da7b3dbb8a66d5ad027d12acfcf0c54a7c41b167c27c8ecd8940f4d7d78ba465938d3b9026108a66c7062b6fc5f5d0294f952c824fc623894f8a13ee35674861bbb9e15f1b924b7d0516837948802981e521ef21182eaffd42151d65d1a57753c3e09df7eff41a81951be64a12bd1cf9335c92ba56f81736bcbf188823cd2f8880b8cf69ae33da9417e33733b8dc7aca5186dedf838060ab65e661dfe2fb36e0649c4b386497d587b4dceac0128d18cba0aa53989eaa356526a82807e6239025cbbddb4e8dc1347eb9978a7fe75c4f8a3a2aa37f3cd562aaff43efd51ae40a70c97cbbcdf3298a29d01834069c0a7e7d347c35b98b8f5b41c0e50fd3cb32b3993b8e1ba4ce1b4a91d58c1a56cc77833c8d1ba3b2b0d1b640af3032fdad155dc3f7b7a1fa282d4c6b91370289de9c46ced4c93cb62919ac6f90d9dae0701a21e95e58e46ddcc24c04d063c5cfed602f05e23c834d22969d36a4abd9feca1fd886bba2f218a8726bb8a088d0c0a6ababeb2606ae73e089891f8566d7823c44a24c2076cb6f2efcacfbbacda6f6ee5faa4076022898b51197e604fdd049027d63e3b8f8e3e9e32078f24b975bfe712f24044ce90f711ddd911a6bdf9338f36c41c1beada47c38da1b9aaec71b966d608f13eba2b69df4b8664db72544e24190280e8490eae5854c4481d0f3a13b57b06bd6e1e42419f16ca8d4c8a0da7e5cb7469846db85c68c5c891045ddc00a3577b7fac734947cc6230c1a8e13a23165188fd8bc84ba8c8afce1624923deebc310784147d9f311a079ec7b9d17ba9ffcf33a078f2bc22e321d28ae3a427412c559395bc17165b6da57c8bdd97a97d658e741402e1b4ab511ae9e3b41a78fcb0cc186d424e5c18c28f8c00804367f82e28ae5b7bb51f2952e4068f031d4529c2c057012c318108088c41c7c5d761075d2e929fff1f2cedde2ac0e66e84d72b0d2380c5f39681296bb4cc54dba96b77b89b452584260e82b459afb929b55b8983811f405bf27b9112235c97aa84e3a869ccc6d1b9842bb7ba5d50103ef3b83593bd3c4806a92ab652ad6da078cd02f1dcfeec92a742ebabfe843d29fc27243c9f66c9eb33f66c4f6bb3ed0d1d209822876cc7877522fc74bcd9688090361ff3efe6aa739191892ae476f23eea1baf305fddad3958b73bc0f620d1e7ffc61d0908c8187c1bef9d75d68ee719b9ea495ef867df3612486e6a13a45f456f24764624553ed79bae40bef3c4bfec1b4a251b2e6f139b43d0084192a894f07fd2f7cc361f8098ebffcd868e6bc2f04fd1561b46697c62516604cb98e19d6be70736e1e628a10b2b3e49cc0d3d10e5f24b6d54b1bbb03e5cfbfe76978a1168d4e09a3769fa1bcbfad51ea7cc50cc95af77d1165b9121b9a6c7a7104346bff1a82b81af75543c9627b3f0928b36033cbf21e4d792b747c5bb169fa6c05ee66f3ed11e4d0023879d57f2a7392fcb74220fc2014d55e255ab2e44b077d1afe815b8847a6692f2311adfd9a58e590168f46a395c62e03552caf980c11be003f6ebb0a9c1ada03ff5ab61dfe1985f571d9179010168d36ea2d0ef31f04f42778ac96f5548ebc3acf21e24441cdc3560d825bfd688c7d57dedc6b25dcc2773b129f14ebce634b5078b7418be4bf630c03105763a2039b77c17c8907c19e615c531019a2928fcf06a971136641ede93ba8aeeeb33c2c6428df95a105eae28386e7abecf30e1c5f9aad29dfd6928bc082a2a855ca160a629cb671ccd47a4935aae18075c6eaa6eb3dd5bba2b1e47e29d2daf24ef0ff749e561ea0749fb385eb44a983ffd4b7e1a0bbcb890ecc6fe200e7f3a2fd9a0f976ae27874db9b7dbaa582370c95992d119352eaf8d12711854f078e7245c1dc71754dd586ad3ead0127329e58e7d8f991bd2b0cf1f7697a69adef6c3fbb6db857325eb32fa77bebe600161206c792a24d5100e46153317246741a888c256e21708fc0681049c633eb6d5c999a2c59eb4753cb29b9fb1dfb05c2ca28008d42c137fca2d18b9cc41803fc470efdd04fae158ba0386ed99b80464b857ce3dcad64fcbf7d0c4708e68d1707535dfb55561f61c102de7a42c238dbfde06da5138a8c64a0fe7db03450c8850358c5c4fe1efe684e532dd9a6dac24b73d9285803c265e5d7523f7400d4e59b9888676bf833527cf834230d4d60aa50330d6b31a292314b5c49660785dc6c208b3da46a322dea8222fcc59b1e8bde59c691a30beb75693675074c202b8a64cc77e9981cc798fd754a86398c84b75e099847f1e34d593499a249143acf6bb8cc5db89242f457ffe1d712eeed533d54b6a9d7415db001153dd36a3c14f687d73ff9a5707fa372e4683af7aa4fca8792eb14c52e3efebe133cebabe63111a802bbed14bfdc82a3e1415848b7479a80fee65d3cb9f049d107869a75eb59d3178ac8c030ce44ad1ee8b44cd3cbef07a0ae1a968ab0017d210f5f838e7716954a4f63a4a42309e354f89f76ffca631534adae4e6662a4982fb5dfb07f2338c9183b7d11716f5d52b5b7f4d6737445bfb026fd5a742f2e8658c4715da1fd528187fd7a32ea9adaefaf9438bf996b3a491b761463b2c19a77d53e54098af53a024c58b31d29a0fb5e7a2a06d056f16b9ae2f13846b1ed06d816871de1d9748d687d69f4df7997a16a59848a4798a205aab6935d4dc9ba34cc5c5133749cb6285d26e8fe5690cf8e0d715607a08966b9fb3096af2db2a90a43af9252bbba9bb0d9a80806dc8dd4ed0a5d40527e90783d2c60eb0ddb281412969743096f37500bfd25cb34be2101c81614b454f06723c88d88ff892a8d6bfab93d773cae2f12e5ef5bca268d4e94918ed7298bfbf4539ad4ab93e944a12f15ffcaf9a2f4d9d02cd9ab3d6a339e4d9ac131e0897baea372d3a99b29e9d4b1d96134634761fd2695e239c507e1ce409cc944857450b2db16480cf4f77ec61c5f6bce4d0c4d9b69154d59c3f5c542e934f3ebcc85dde13acfb7cf7336f9e3c2e7a14c2a2231c5206895b67aa09cbc8461b6712589d79ffaea5bbb567c88556a51890efd93ddd8458da8f1e469043364083fdc72636309a7381e1a3d931d9c27a433dc2ab9ff9c0f47289a3925acc430708512f78884c4e08ea5e605fbbb8d5b8b61745dc5f8f7e99ebfbf699c4c171ac6f65b5381dbbd074672bc98679c9994d2328e7239043b745c8788680b73ab829badfcdd6cb46f1fbd0a204d3c1f7e66ee66305a6eae3b06917a7ff5a31bef05613af05c7c658cf6d337f0fc158fdac6732d80c30f9bb54910fb8cc1071c19a3f1fb049575fd253d6e5112f841b346209b8669799c9baeeaef65cb33d25d176f0708da023142adbdf79aabff4332da18c633c35ae915d73e0bb8a9c70fe9196fc4a381811dff615edbaf792751c44dfcf437d0eb9cba200981420efabfbe84242c4cca98bca30eea0b001cfcd63520d54de84099b28b052c366f6bff578352e2a64e70ab233229e07c5c6095f1396983998c15e7fa3239325195f8083ff9313b7ff1b9437b7db3b5a893e44bf40c448bc88f04f098a8be12bcdd80b3544be8b6672c404b83b874010c1e9fa1e5e6fbe76c4046cc84299c7aa6377a566627c284f2f0e5100c546607a8da208d003e64ec3a21e8b0097ea733035d564ebba81d101803409e7b2d6ef882dba1bf33d9061c8dc909db469024f50ae73f098a125733b561ef034a0dc00423422baea0b211f4e355a5a4b0523008b672b591815aa2440a3fb0f286758010182c701e1faa2ece5e68c5728ef631af46d5b48e40839b5f3a891f2d52c0827372af27b8b907af6243b94a877c72cc2cbe606835dcc6971dce02791c648e3cae46e7f9a32eceb4f27e37a322e9580a9c643888745d91016631ebcf18838548672e10529170dd43ce1db914cc98febd8843b5e1b53f4b491f353784c30f5f1256c7b4a79426e564bdd13b7d0923f14f5cabdab6eef5f334508d59f628794686f831ea10302a27c4ce70b5aa4973bd7540afdae75b618c98b0519bbb3cc687dd12929c2f800b0a76317574ac4649068b0ea5aa18335e51c1bf7d04d5a9108ee20193ebcbf78c7bcb0a7e10c0afb9ea863faaa744c637c61d3671d4aade053246d9e196ad4c232360accdeabb7caabcc7951c4b72132c8fec0e728aceb26658e020ad7a86387b64cc29c93e19e757072f671eef787ef41bfc75287ec50289e0f5c42d1c0079f231a5107ac2ffa6d0e5470317a30a7f3622c59c1e085e24d8e2dd7619463e0a1cafb3f010d7f4bc7aa971ae331074610fe869d9463a5124503fa04c2eb568ef3ea38e755c4910c38bd04810e06dc4d48f5e647972283354037c67e7afd40dfdd3bbff0d54a2d87a8f4cc8c053864fe53b0ced60dcc0f3cd96f3dfc9df8a70010b9fde809407290fe618ddd401cbbb2e8dc764e79f347238c453ded08815135747ce4b5ad8abc24b432166e826790e856f414b603bf0e446d96be7f177f610669e5875cf83cf6bde2e78da2841ce6710c45f8c6715241823374b737cfdef19d95c5d4f921c51be8f83dd0f51964c61dbe0672e57f4dcc430d4af051c701120537d5eea11cbcd0744f194db5bab0f40200af9aa4385ac825ebac475c2a8d30a43366351cfe051dd7e7d8c87f0cb306d3501d77c7bd9e115878730ff728245010b6dea19c24d994416fcd25531863be6d57484accde2511c3cea73f69f60481d113cad0f4327e094bc9c5e8ca12fb297a689ad65caf9af5ab12c983a6ea8dc99777231a6962943710a5b23c34d5da2d3455762dfc75b762c66a42dbeba417ece2a112c1afc30b07ebc8e796e3e6a4956e66b2121b5a75ed898429ac12f199189abd1b36c19c066b7ea5cd4df81f7c88a7347b26eab626acb8505b89810a1dd9b7c9c43a2b77d087e1941c836c866eb0181decf64be737bfcf285c315a44f1da762108def05b339d2268c61fca05ff84cd31aa310c2a77cfa8d6a9af34e1b9781b5001bbe05d0d14840e0ca62e74b99ae24570d8c9d2e1a6731f249d98002ccc0daf3c3b254e49ba940b74363cfdccd53e4e5c2e8471c0ae03c6fedd08f87333da1a4ec1f4528a7540eda66345383f00613893574bce9e498c8c7dc4b7f2d08a19d809a8d187427d1d5eac0d275f9ca8e0cfc4e9e0af18d27a4f32a0e79fe2354d82b56413bc6e52746dbb695ecb7ba2b016bfb0c25f52b45be80749b6f07ed5615fbddef92c240eb4673bc0a477de5e41ef583a63a99b185c911fefefad951debe7ba8024e2dbd0efad0da9099cf7d97ee30b99c33e4f547b0da2209020ad6c203ea361b247126f03ddf866e047317fa34a5edc263c25c60b027135c2bb2ae140bba37bea31d3f344ee6c75b2ef40b06bc4fc0b8a2850f40c5d3e2e162eeee6636117950fc62015994fbadbaee22ef78c520b7b3c160c59a5dd1e785f961c4a34c4babcfae976f1f338a18f8f09013608b7da9f1c5eb1bcd37a7684b06aec37b0f76f3f0b2b5f42e5ad2ccce0f151fe01fbdfa1d66a27f85a6ee9a1bb9ff44c22dd44d8fe9db5494f969dc66844cafaa31ada4330866a4716dd3f1e44081d048bd5dc09328cb2dfefeb45b8bca130ed4b60f12a6e4fa8df2db99bce5bfe69a0b673a5dc1c76b07d9e0706870480ff6fa4316c9da02872819ca15153c8ae111c1384b150f5946f4acdd2c80c35d2767f4e1ebe53d7a18716da2311e1594ad781e9fa25a792209c25e00cc26007b058b6d882c657bc21f2e42b169921586879463640693750206ba36ac08dd424799a93500683ea7e4ee37978851a2d5672f69ffae715a71a3b4f1ce2dea6614a8952d22d4a44e5e0e3f51cc650d238a316c0560e4929507462c2b4b905462e7b3f1b8be23d9122a5fad8fa62cd70c35df855f3f044c54d5f4ae457cc0782d423568493b9f7332b1c739546ab97c49f7d0c841ff88358418a81a3ed1d95e1dcf69fa6c4ab0d3d97e79c5d30b02dfd25d5d0647e619187382ba78bf37536104cd156986e1d7cee46ab59c2fb8bc7521c47b15e74f647421e81370434ea4b2089ad9bc210d952dd9ecbde81d3908c8ea25564d932141fa64ba99d2c289e5d4ca52f4be584bbd2bb1e3513b22f5e0f52ffa620ce316e412db6fc810f630e01edcbf437f471e0bfa759c5b07568e032d9cf16032d0b63257ab4ad4bf254ffd3c850b5fd290c8cb3eaa216f26c1613c3733b22fa5030489161a24531a8b0085afdcf91ecef2650190841d4a52426909ec4fea09ce313a22d4ea273c7f40baa9482d9dc34eebd102fcf9d8c6ae0742e2baf254161b872a14c592a9fdefc16fedb1acc02c716c0e9f8398219b3b9759702260899e7b24094c553b7d8492fdb788e48183471aa11c0e9a3120cb6467837f3b0fb07be5abbd43a3947e5331f4539eb11bdee870c17985a9546aef730779752ce87672d1dc083701782f35bbe9b82f3dd70bca5ff7b11a8e99d84c19d806adb5d2de48bd401178765a98a4c02580f97ef612411653bbba17bbeb43ea53e727a794bc59a9e728233c8fd4c5f943de98c0e6e49c88cf0a618b81caccdee78ea86700074b9b34ded9425af4a9be14849d22d7d0c1eb669565227d228cd25b6a6b6f734c551dbaa95daad21ea58e0d8b751a9f42c03b372fad8890758c05cb5165cda4e93bba26cbda4c951db18b494f73edf407a5b8b6fac35e4b9ece9b2c3223a3c3dbae844127743247bc31fb19598738f495af63f8d96db91495b31e6c2f25a5e5475091f638291ae8a5f3c826b8aa6af4087085001405a125c0b8f711c1d59ec511017973677ff7f4a9e9011f726047afd80b2929a0139353581ff97b8b0bebd11b69da535370705a93ff57f9e62666b77a0d9d96c9ef00abacded7a5f75afe8a0ae9d4ddecfe6037fb54a1c44c2cb43e6db0f5bb1cc9ccd94eb0a981cb5adc31a5448e4e657d546fc1555808a51bf02c649088efd07ed069721c6ce04465a0c62d5eaf364dfe100d15711cb5cd612c8692ff710f20cd58cb082c7eea784df6564a4ce5e671a30b7536a32da58fdc52214a0d0f363edba52f64cd2628faf6a09c59ed62ec4b7dee675da1f2accc7f9dae6b73857ee3f76c336b070a1a07bcf5eb4cdc599d107618d56c3a763b2f1f1c0e61363bd9995e5559a7ceb661c3145d72615486b5b8531d7d5e6ae68afd3432f69bc05bf040347d54b8ae1c7a3d00d6c45222a4028e34426490b11461f23019ead2a7551c7a85f422f6810f00434672bee03af420120a03155e7cf75f3d945a8076175b68eef540f84b20c78a45c7d7aec2efbc47507e8eb2afca29e60ce256f91811e6a6a0fbd8f795456014d2b5bdd86105c625ec5f45db68d2677f4336c025c2502f96bdf5aabf0bef5af25cc37440f51cddcd7f29405be10a15e53a26219d69a8b6149c2a96c281161a5e64005bcad8ff53b31a79c9aed1eab78eb66953441113bf65dfbcf1728b01688d2b6e6aa9395a7eb6f354de545da579d267ac07243a95d085edf3033dc851ef5a9006a91d725c3641a0eab2f1245ea995a5d23f8fad3d358f2bc88951e5a2c17ebd4cb782ae8b5973620e2c418a9f378d39eed499d43e7c2eb27f33401ecad056bb4f44aeb5c3485f602066d98f593b0e651af1de1726f9266c4c276246ba763f2166979eee0c6756d6252fbdfe526f6e7f78969701ac43e7d0487d14a5a6ee7b69d00879fd72a04596d48abebbc1b1f4cf9016011c75b52dade4c0c22e3ed8aea9dd87d89179e12166072db176b28359856d9913c660eebcabcf73c1e4bfb9e2b124146c402699640f92036d1d038bd7ecdf9b8878b0522090b38af865387922d4e144e581d73f9831b4aaa0bcf613cc4a826b3135678680559ced5dfa6fe3a1d68fc336314acbf4297af673b8d48ae7f632ad9d1abef787a3c0f93fa3ef2876d260cc2223403b0ca2336cfa869801ee9df7d811567054574b79fe5ffa0a5cde0052b319b133108797c7c8755672ae07cb71b9cdeded108abe9ac2dce90bf81bb9335b685165cef0ee15776849bc6ca11a50efa21d3ae14ab20ffcec867456db2025c0b696598094e3c61e7832133addb8716cdb3f5744786088da6999ca382c51b16fd6308f3169e6e01120fa74b8920dc4fa6f7ca7f6d8184e7b299a87420af2f109e1c8cd730ebe3ddc3318a2addfb73cb48eb8967c6014cf762932977b758ed9c4a6d2efdcd8f97fbe31cb8283483bb4887180379c83bc4252898445d01a230ee96cad7285ee097a2965f7fbc96800d0bc84e539e29cd8da0c55cd9d918ca98a57fcad698a738e1044d9d772b0338b9c66dd8ac81c615352941f1f01f20aa294ffffeba98cc6af81691beb398fc408f3f3ff5965de638a4e4f6d492c8cdcdd7066b3ec2532cd91a3a7cdc54460bf9647a1f699d4ffab59f496ee31fbd9360ca926e18479a350341c4009b5cb4041e60704b862821a585546368a2af7bbb6cba16d318c970d6a52cd2c915afbf2601c4485dda6930f53e35def370e25f56842b77cc83b6422ff12af95d4ac675d3f12f6dea94d017f182755f5aa2e97b1627e14293552c8964e9c1c59c104bd0aa856dd9f5f17b15389f1c372cc5cfef8e920005daf5672a28cdc258c662315a7419390ade7457d35d02606020696d43d2e166797a6950c848f19d2f3665969864e3717bdc7438115564c1e355c20c3d3905b0468a0b027fd8b195f893b3ad61efd8918dd68c3cea5c630eb10591f95727d16a3f566db7ad1e1f670fe4585c88948866fecad10e4de24e0938cc6eb5eb6d27d9e788720644af8f0d633b32eb6056922bd1dda8ed979ef55811639b881a0da57f1bf2047c4b62acc01bf2db2b974d4e46478c79c4177559ff90e6a4cda13d30eddac01346be5c492886d601941dbcee839596d59239a7a5f744862e1b858069e6eb317e04aab0fc920bc486005e68ab404efb6024ee9cbe70c8d7468379a73d9ed5f2d3e90646d663f5d5921aa63da82f4bea9e87f3064560a1b08c7226b07d291cdf8a9926b862530b96cb3b8c26ee2001edc2e817d806c522133d1f23e6458ab2f7aba43a185fdd0698723259ca9cc3ab93bd65fa5040fc044250d3cea3ae0d44aaa916aed64d647bc6ab42e29fd950e1124c96fbd0fa3c21d62899d2d33469df878037fb649caf834f7cd38c8dc5982f8c4e38ea1a0b85fb9cb4f6c27643a91a71b2e6a6bf185bb095f862c7b3dc48e15d93272704ab5ac177d3f45db5c517dd9ab5dcbfb2d9acea4282f0e68a65aca66ace26e0df9701aae74573db1bcfd40a21555a15d72d04f250190b559d1a6c1b9729846a0c5f678e40ebc9ff75bf4902d5656610a6fd410de5eb9023ddbe48031ee2ffc13dbdb6d985001085b04bd95370a84967ebec477d6f3987ca5041706c765179b966ade339901842e73b97169924c761dbaa17cbb48f0b9014802a18cc7ebc3281cb0bede2c97dbeb3591ee15a4e41cf1c5cca8332115db167fddcce54ff4f7acdc06840a8aeeaecf7f1cf4dae5c448472643f9a48596446ac964db5bd33bd65ad00da0d15fd77526905ffe3165d9766c6c44026a4ace0756482c82ef9cfdfa6939dca1214d6ef1708d5ad23b4326b667a7489e197bbe9286431ef919447a151c19466b0a6747651ffd2104023736c3f0ec0ce3d3d663416ea29bf99f3bcebbc0b7301a70b668dbd94d87f73c09d9b2abd4affbae4fd8d6d9752c516c451873461ff7179cb814fdbd4e511d1d1732c6eefddd79d58b4508cd6ec0e9dafaef250d9fde0086db644ac2b2046fecd3164290880ef0c766a783c9617f6957452c833b4c9b555107b4badd923c6b3b43bd635b99cfe6db6d5e1cc6cb29ccb4e6f9fecd7be4286911d90faaeb9214b380baccd4db227f82659207674909982df04170f348193bd7493166f61c19b593375100e8f9e95319c264527b1c71908e4b60841012f535f0ed7acb266960685e2b10ccf026636c0831f3fb5eacce07a8332a83f0fd53fa32b37fe64c7b12ae25b1c0b55496b551b80e2a5da4024a33c4b7441d3f6f073245438ff99cde19878732a5c889038e997bee66c8524cf6218b3bcb3e6493ba974bbbc9c941825adf61ac075cf929cb77df17eb8a1b5c92396ff6452040d9e6a678293a35993bfa45e83218ada6493a29f7031eba212ad95a7775f00f1374d8a59014c18192413936e0671b66e2aa63491ee19f76544e620ffe1650ad11dac23b4ef4ceae3eb1da726f07e96c9d1367655e142c8fe6861263c78e1a91c348e50407b5ee53a2091d36165fba921e53e85d1cb24bad21361c4ba1646c9b4f3cbfd6ab3920d67fb0423825d048fff7ba0b6af206d875aee38fbc9c734f6246f954a644d302fe1f6caace3d503c711a9d82cda30e4d476eb77cd4cf6c917a5006844254cccf3816e977a530e8e9adcdd5a793e7fdeb7f2e01a1b37b51ea9463af675424e1240e9d39ccd5dd3762ba2640ace15fbaad75d0b2aaef62fc72a9c683cd57d6c39e946e4f8e5a8fedcfb74d3fd3b17360058afce007bc17966bab3ad4b97d3a9643efeb83c325a551267d8637bf09e9bc72031a46472c0b20527f123afcaad972bd1dfe11785850bc1f487810cefd0dd2e49610c2083231ed482de210d40454e564a95999375f891c8c1fa2b98513f759ecca4bd77d381f26cd89b2f5a4bd31701bf9b5a115f56272efc042a210d5d93abe9e93297a7fa545bdce534dbb138805f655180e464f5a5470333c40caa7d214061f91031498059aafde7ebe20b46090b7ea89af45f34a6c165aa48cdb3775bb88d240d4d4a412c9982cf2b380f3a56b1b486fb292f93af6581fc584e70a5fb5962828ad793fba4ac2819cdc1c8f72e7ef373f2df5be4a46fd1348a9c70ba4bc4e53dec54326afe8101c35acd1e707ca00e985965746b140459b2a805821553eb0375cf5d81231439bde55da2ae959ccd1bdf84b2fcdd4de6dfee011562291e07d5d923db45c374e908e49e1870e14192948de0c75543dae7d01c06b48b888f7ef08f73d264996d16d56457f503837d2693b357c2d20555163d35d1e80039e0d37e819bde52a11e88609b23a3943f48cbe5fd4a3816fa9fe63731cbf22125162cefecd2530b3ad82dae17ed6979333cf13d0346e67efa3fcd9e57867477cfa699eb825057a1607e339b234e0c7064e98e20cb72edaa9832b43d20b3523b76d46ef4e508fafe6d122bfc6a5279ffcd7d7caadfa15a373b27c9f6461c745f17c572be9621805c53bac93eda33df26a03f2fdbb4fbcb11a72741f8cefc5e58900a871233b9b574d54bf06621bfa282d0a4079ff5fa004d742ae22f17a573d75747df8e4f3351ddd36f0f3934ef9e201468f10c9eda021c05256863c2ca9f61f76cc3fb0d8aae5173fa57905b41aace8179f4803ed811e49bcae438a706ea8a36dc013d75edf6a4b2c9cffb824fec0d184a501944a6a3905588c5e21815a625e75375f3c6f5e3115ba628de99e8ee3d179c9f1586d6801376cda23cb17a7493f2345ce8d36d5b8ebdedd7007de753f4cfea8ef48dbeb0eec9215b8636691455a2faf401fc5973c6d93c334336ea15ec436d20c8474474ca7c5cff57de1dabb9413710cd202190496bf20e7b0aec62d4aff4d805c6535cedb9b2e9c34cf475d7da83910b2de764340add4af0d6cdb10b2101061a2474f3cba6f6acd82d42002a173e995322dec8b8f2913b7f1579a070b188930bcea97c9124b52ddbdecb496eed31905355b5dcc2951e35bd8a904e204e4183556e1f1bc4d34c86f0a50d317fc78926543f7cfe15fdda1ba28a1db8438d091a4b242a489bcb140e7e6ce529f3e9ad4b1a815ecb7a7aa7c836876273e84e2be9ee404dc77c51f452325128b80873391f950bf83f9091bf9208386b229eea2dbf77ff42bddabc4f58eedffda74582430549bee055165e96d2e5f288ca5be9218d3879504e5f662807fa53f21d8e06f4ba387180839a9fc863e3caa87d6c44b1b0da6283fe9b869ace2d27744f5b5033125eae3c85c6accc3f19269bb3cf7bc7b76f5a6985a39c8543d576aae62985b64d78e7ba18872f2f75c71d50458e2ad46dee8c53039c3d8e4f99b84cb6ed8101ca976db962a2e97cabf8d707f5c97ae7cec18fc0a1d9accdf068ad28c9c2be4c37d9894ce621981388b899511f70cde91cc020f8fce57815ac56684e62f051978ffcfc511a48eb110b81ed5e021247defa701e90b6f34ae7e47f06053cac3098c41e4734fe498a3766d3143d708c31872f00c562d31fbe509df528e4f1746b40733d0fd195dab7361f7ffec27feb731ea7a05cd8ba1ad00cefbd115dc88b89e24e1b62526dc60711e0debffc07e56ad9c699b9a1c7f171e305a3e67ff23aedcd0892d0ad0a3a4decb6790dddc460006c2e6169abc23d598ab8193ea405dd62c2f7ec57694dbe6e9dda8317775760c2706af8d27ab7c8eef4c93565f2e05e21f17d5ff2596903e439a90a1cfcac5be1b0b6c4f1e0e9368e034689062d94469f994ed14ea1a469b06a0843170dec9403de4de92856da4efb018007df556aae48362c139350c1668175c6659f2f948cf579bdaad0433cbfbe2c43f4a483f450b6176074d257961427494b9137d259f8d53ac0e62573237526593ff7f5d30d327787626160a1411a67bde1a283ab232b14987a108c21cf604a5f969eb534bfc36043cf77f58b265b1942606de54b2f8f692119ba6f40b4cd3c2faa92746b59fe06b57577920ce5862312cedd1087e099548231f34e3fa9b0c96b0e91db817309e7d898b6566068defa9039ee6a42f451ae50a78a0d849e0b714c8a0167c1d20a0e24a7246349cce1e0394f98c2b2508ee2e05f15c713db3012f053336d2fa5e449b43860ad2f523bb4b19fadb6684ef615acb8078397de7322abcde7e46e3e42f20d5b0a8571d6e8cdfa20933dcb2ad875d0244966196b0b10d57fbb17a8f4f5d5390ecf3b64299728893a86008dbee3cc4c9c7485d795bcfc2d287aa9abe8eb2c4bcb7506e355d8628246487b26d4d1fef2f249c44f896641e41090bb3afa0efedd356b650ec8d6152e01c3bf19c565a8cb9e6d8814c4c7e2948b95883a75e08f204e7a53203fcf0f6269d90b2df9e8145e0ccc9ab92079da4a104dead71593b1cb24001d736e5711aab7f193571e6e811904e0e6c49f048ef9a59ee58d9c577d3f3a9eb29e8f47c54f66506e5d14d2c59abf843f8d551fc339446656e3079e4f612591eb39fd440b9e37ee97d429ada15849710f4ffb3a6f685e87aa7cbf599a00ec75a2c854c4eac215dd3264c8a73746531578974834aea77a9477c8487e11b19d2e453e9f39707b41dc47b393a2cff09d12123414a7740074f91df37708b7419b02e66bc7187f373deaa1c99982b4322200a7f5d78a27be758bb6846242e652ab12025154d4ac78b846d67beea3000d4d12ab820bca02b5caff42e1697609219d2a6f6ad4db84afad2b2774161d65f38dde385c4eac1e1c0a1fc21909741f1eb1e6e6f646c75bd15d168647bbce165da87fb27eb7851180135191ea13cdeef54e5fb91ba533119028141f80d0155c73c40e66018837dbf20c21852ba61497641b788255670261f3fe69fcf583c319a51483f6e3466e69fb4e478cf5033761f4f3d49cb83913ae3195cafdb06591ddc0230bc260be858c964b90c465dcb271951d90ee4ae1963b1363c9fc77f0363a2ad1f2544cdef74d2706cbcfddddd87f2cd186aebe9be2e5c0cd53ca2759ef3e1a1f21b678dce42c42918381a5275364180f7b8a86bdb963d67b0657b59476722a321bc91c517f92a75f3b71541bfe46cd6f1ca790e4e7a75e7290c5ae3ea271b6b7d5dadd00975eb4cd02329b3e8cc2172e70406d0ea633b7f55c80c8e693a2e28b9032f6f86272b3dea3b09b50a99e2accd2edc527b7e6bf8352fdc19cc57f0f8c44415459b78ceea049df9d8268bf20e3255d85c4fd8d06c436a130788487271f8af495b9ae3cf87dab9b7b133f858d68c68b2575d5811cb41c9a820d4b3cb1b0d00e79dc24df85fda23295a39680f786384c1d1f2068a5967480dee0cd57bf4e8a1045a78594f4f37bd09f07ded83193b08d3fdf48bdc2ed3444f4129c990ea385a6fc80db8f04946ae0f13c28479155becef30b10469b553d949ab3e662a64db13267d1bbcd1d32c381c698d90f8795ffc0d653d93712d7d9d8f0516cfd9ba1a2189f768ae6885dfbe191a8bc171899be1f5ba68492f2b10c11ce8cf3493b8b779e91665a7158f70a41855f6fd6d3c4c271af13b51b216b0bda1359978631fc4bf8c9c2eefa230aecf15ca161a26bb9aa2b6f0097501aae918658e6f9fdf244d154ab42c26314011f8962fcd7325e66ee8524110698f7689f04fdaf54950f0d50c9b6837b869a7fb10f2bc2fc3aace11bbad93a778c65e424bb6c558a2ef1ea23e5b2a505ce0e3466ea6ae36b2d23ca0992a3704791d19a467d13d43722c3fd04c784ceaa22287227ca7c7780db8294ddb15a48623e3b36183aff6e252c95944608387c3d412b64224daccf79f9d44740077f39b39af0de8d491e7fc28db6462686c784ca4d1ac5e3a4f06fd0be66f7c23b4632e14335844b14ecb115f1ecc98a7ae1ea173a836ff89f6c2053316428ab3fcd6a580f59e5a1853a1928fd6f2ba56dd5e11311c349d5f9fd1bffe5e8702e34c53780d60396c214c2d259170ab2b69ce7f390c8f1e10a71c09169d33bcdc73de824a28665aee015675f8166bbfd7bb98afaa859d7c4cf94bc14c814194c5cb0633a0aff883b0a8cb4429579e6b00393a7c239bb16d723390592ebcb776117ad5d71cda6a0d532777aa3669d7e0d5ba015b58121d212c7109b0477d2bfd7e9941055495e7a30d64f079864ee0c1e1b6c7244c246a5f5c8475ac34cf8f76c9b757f729be18c5c5cd6ccaf9d679a941fc9793380b78001d442db6fce0a6650b2da50ac4d42b46750c207791d380a3b10eaec9063567720d4d243b7f6e0b00f10c1142090e72cfe0e7929e224974b066e88cca571c4f35f10c2a2cf01971b2ba1b6ec3b2e4f35a9ede02241b95d645b92c8afd1c0a36619e33ec7f64c8029a0f715459ec8b72b76b27f22f56b66fca9f155cafc041c91f3ce363ab6e53d609a0b23cf184e57b0ad5168dab002c4b1cf9a51890de59e22bd1396830cb21450898106a1eafdd674b2b236fc78d1c680ba7ea9b348cc3fc59220e1a0d1d7999cfe16deb95514e50c5ca009f75bbb676dd78547a9331c25c0de1f9bd48ba9ae84f385edfb1e7cb5697b1904eeda6fea9c9e5747b89f155cfc1290b636a88fd21aa09e431eff1fdd536669b082af178997d97a71857a2da988352b76500e93654b529183e73f6646ee92d927b6bf3656a99c472a220565d5e7409e9771bb664e87d5425198211b0384c93d0fdb4037ffc325ba87e6d73ea934ac34358d14e2a1dbf8041cf061604cace3c183d4bc967a250fb9168aa5d5640cf79f6cbbab3925a6e028f860d3ed620321341483ba1450270c1b1bba6ad42b55fbab7bf73b36910307d0f4f4a6b3c0312b85747d4122f1702e1e19817fa61306e04c5ad1e27deb0e2a6126293f7d4d1574ca4c3295465f0ec0edd2414303c390078ff87e03e7f0110dacab66115a854c286535690de89b705bc7936997bf5f42fd47f6e62dee66c7d78f11c53c0a491671abcf1261342bb9b1f228a06c0d997a9c53b6c5109a337a11da581ab3d9b4e2c6d7abe49effab7ee1153f7a0a0ae923085964cdf5f307b26a1eb9dcf2cd77a4f9edcb21cd1be5cb2b62b340f190e1818c6f65d3fab17926be98111c927bb5edddc58a0064c4f1691f7576adea54b1cccf4bb9e2c7a8030018ee3d7c8f3ff193c0e0870330df57ea7cedf3ffffca50049c52c6363cdcabc304ab4189d4179aba2c45eac11d0b76d0461a5fd4456c7b0a01139fc24d4019c733ff58f3ffe81779b859e43d562f0832ee99b2ce743d61779082948b5b8390ef807b02d473372048760b9a3f4ba66449c71015a031b99788bab935fdf7c2ae7219901187577c06e19d5a1edaa196e333c5f25b218ec6d62259e36c26fe699e705f6aeed29efb0e8d90941037640b13bedcb85c88b0b19b3a8438980b67b66bcefb1ad02eadcb373a3528da09cb5a09706b14d33232aac530b2e25e00a9045b0de424a53e3c1d5e326077b85a118b5cbcde17d978efd072556dc5de2436086ed0424d615610ee32766a9c1f3d386f7d101d64a62da78bade27c9a1e53e3f6cc548c80726566221c33b5caf68bf0f8273514479b3fc943b7930c97e535ed2f1d2c45c7600d95dc05fd0a98488b939eaf0e38cc3eb4ca9ca1ab6b963ee065e07f7475bdba7a4016463249a12a65796c786cef391e907546e64147f79db20229e1cd3ec0399959f6a798c19952ef666c80fa0ab8f3dccf2d86a36532df699c6126b201c9ce52d5bd0182faf038734510602d8afe68720ab339d1f644654e2d7c80d18d83da9143ad2258983529574238d496d6f2cbeea02125a63c2a0227153090059c66d4f93c4b2f1e9b65d455da9450e45474951f70810f9ad9fe72b3e44765cb6e56f8c18364205f20779d65b90b647bfcce409770ddd9d3bdde86b466fa587b6001e7eb63d0c551ecadda31d5c4d1d2ae820f97eeab43f5dea5b0b0c10b42d13f02925a24e4db8362c7167156b21764bbfad0a84dc89eef9f2350113a5e5723dad754ab78011bedbce9cabfb0a63c7db6f809ce5ea60850a5ee55f65cc9fe7d5346beff3001f4601ab333e4b9de824079ab6ff62e0a2a71c9c0e060f3055c832397e9bf8118e956e489dfbc2cb8bd3eeba70ed9fd3631dd245a403a28231d2dfa441348c9c30f94044c136e4cc48212757adc9a33fe360618a7485b48b987d1bedba9019502b6a64276d5779b8511338f56e055bacc67512ee85825d44cabf5734dda32d536213208d2e060cec878e20e8b61016e7d0f0c358ddb7bb033e8dbc3c56edfb839b088999db616b7f0dfbcee91e140acb176d677487b86ebb8636a32a17dbd94878fa0ec043482ac9e007a7851b19ce99fa89815bfff81c44d2611a1743e9c0f0a834ebd9c2b020f3b31f0453315fdee35af3ad974cc6eeb955d366726d99d0a3fbbdb78cb074e79e7254b1ffa65b1b01dae31870720a11e9386f7b3652cdc9e32804d700e6a6609a2703702a7e4848191d0703880a9937cad2df86e971ac929c7f1708e8d3c82da43436740dbd368ebc59a044cd2480130f491364703bffb5c4d21cd9d3e2c036400ed8e6f8524ff80bad5fc4b6c682e1bee45328b1add5e18d4a462edc1773259ced1c649d214df871ff5e02d3a373dbe2ad1699059b626725fb55bf953742e9832a267d26f541e0ea76558ea6e99fc54220bcb17df82fbe0463679658e18c37662fc6362575676addb92e8fda38db62310b671ff2e23300d2327f95d65c6f04693978884a5814318384d93ebb2507fc68eebdc6e643d96ad0002bee0114b00c8e5613dff164ed36e1524f6906da21e0f765e33c3d40c2e0e0c94ca943f3c14fe7537c9c7b08115c50218df109b200df3c77324cb7dae56612620821022a358edb8d9e94778f4beb9585734c12efdccff3d053744974ada8c236ab877705c4d27ad43f909f4859118ca24a0329d9c108525011683d14e2734e04515ed8df66e6e2d5d7cba11cdb5c5ab8f43523eabce8325de58d24a28c3a5261fafdc95d7fd8b3c06f77bb64e9413e53b01300dc694f54efa37f9a7b67927a41c4102974387c9410b338230a2099e35aa96297b41524a34ec38bd70f23b6e4d1f11720f0ade9c6b6c6a74338961e4d847efc97283dce97e0ba35136b8a030f5184e68d7b4bfacc1030d0c52b73f34ce84e4fd6ef9bbba294a6b4b6db877d59c42e24bc5718cd1c5f11b46be8b8198dc0d9dceae45bdca8c1dd715f1b254916ad8dcc65485248e5ad45c8badf6cfb278db8b9200a34279086748cb1a5e5312a1fadae0d69cf8fe261727576d27de3d024c210b4cd7014dcffae57fa2645233ddb11efa4e61b8f42dbf896ea76a5828c71339e6027f0215ae445d8a22cb7b27a4eac8481d72d7799e0cefd05ae0b6cf9db50344069154ede5afc4b72814093e29956bca4abd639e6c3a8ed4451a09928a7d13af9dd99a20f1b6f9ea0dfa5222903c1b351efce0ea7738ca16be3f7e605bf8c273d8ee8a4442b35f4b8e236db2771fae0f35956effde9d8c410aef69250bb8c0a97c764e26fcd62234c636fe1ddd514885563e71ce5ab09f7f715bdc12319e7048a3e946e1c6d3f3673a6047a00a8bf4826d96a99734a024f3c3017fd0eed70ccffe5a65b7aa087eb210b77901bce148fd12eadac31187ad54ae228b0be8fb12907d63f457b8ee435491fec32429d3e21a58194617d843404ad162faa3407427be2dbb3c6fe4b5abbc946e8114fcd0f95478bbb06ffce0e4da3d0f5a1902e1e33668d2b020f024f57365e7a03b58420d87efca2af2d0590968f2812ac68de4a57a7f9371c2def749966038f84dfdd85a846fa9fb3e37b94b72f7b06c8b1979afff95072bead955142d96d6c302388c31a37b70b2d0337e2fca7553653387348da3f1eb09625a954c9e600cb4a3e6629153b63fd7c2af1efa65de8f694a85797aa86eb2ed3e58c71d9c3019a39694c69d596eb04c378b5ec6aa10a183127b8a1c3f66bdbac8875f6d1531b4305f73fd3693e45f26a623d22f02919f9b6795d0c90118581d98d7fec43cb992ade85da50347c3bb29b958420555e399c15bffb5c0f40d14318074da47522883d96bfb2ea6fe7fcaec5ee44bda970cb3121b8964b50b17532a954fc32e357199a22aaec5ca6aae0ef1d7d4cd67248154512a0f683f988653ad3f1898bbb8207c21c885302371a1e691897dff1283d700a0495aff2c55d6cef5d51ef905fb5d25144499f27d69a7089f3fe984cb7a0834e77907de700089ae415ca90f5e7af09ebfa8ac7aa0cd8e1182b177aa0001a562dd0eff60c0dc0e95f4d93ccfc9c6206dbc62886a4b34a34e905271df557cf3f07d78cd773e08c3e3758d1bc3b7e40c80ad15c6bbc7c0fa8ad07af3b1506610721386666b685020f8e12b27e46bf61d6ef697025b2690d148d7dcdfad9c9b3b3fb02d8b78697505afc569990cf4676538981fcdd98536c8da6f5b2af58518bdcb72a64ced0c9a0c060855d191f2d88adef2ebace3b655b17cec3260e24c1f483c7090bdcf761c5d84005942af0448e93a02be36f259f27153c7e1bf33d3481c2c8da33269cb8371d24c0ed4fb28cce1a7c5622e2cc54a278c781533d0b3550d0a23d254ec5bb660ac7a1a3fde5bc103e5b3114767facd7e6876b15669744ea34e6b70d00436d55485d1bf3afcabea02043625773b623154d6a9ff0cbb958eb2700309c4a946389f95aa1aacddd3321bf7b332d81ab0840a057c591e13a587d6c08732821ac464989662fc69fb9eb2df83b12aa5228ff7bc00a47a7b2eba1a231d0b16bca77552b9325c01bf0bb6fce1630127678c07f21bb97d5d570be8350c15ffec83d22e0f3bd90d517217c77b848db691d7ade5504e5f3e2bcfa52c4590840d325b1adcec3a35bf489d0807462aab7f0f0984f1f0a8783240b8f161b2941c48a52d9a2189721c7278157d507e6d203a676de43d21017cf17a31d0ab64b4b07e6029f434b13a8e57938b91788b209602d6a580a352537077bcfe87eb5cb797788564ae50aa667f27d13d357800f90b84b4da42b6409f5ebd287689dac5ae29a7af166d48af371ddc7aadfa82f6fa87d6b882ce74d0b0401e3c4a156254c772da9a4e26a15952a9870e89e96079145d51a1bb981362bebb53ae563741c6076d963a628cf8827a1fe9634b5c72e748b458adc35463592614ea5211ed9815df691dd5a3b29d4a9ae73d9b113f7001a8c52124cd093b75853e4b8113fa4b97c9d06afac0ef9802bf066e0897b168b18eaacabf1ced6fa9c48d24a4050dc1ea2cabe55f5a49f25cbf804d2496edc12fd4ada754ec4d392e92832f6cf3f747a68bb29b075b01af1045543de4e1d388b7ec3b3305dff3ee8eb94e19b2f8afd90fb7858835ebb6825513953b3422a262180a7b1c4a25b34b67a50aaddde351d93c29525f868180a9712f64608a84a29b2e367bfb9ced2c58cec76c2a142d3e5adda054618ff1d9931d58a5f1577203a512c683dbfeb71814ebe73d59589f28c7a619c1d56f46d8270dcf20818cad959c5f0f5e928209481b489f92ba6a0c18e8a8ff73805ce4b7ce5010505d2996913091ecca569fe8454c94b71568737c436e2a2e5fddd9b437ab8e300a6078a6b1fca09cc6b9d65ecca6dfb9bf231d5d7b07b998c7a7a60a3802063234302c6040fec54d65e01f2a586d36f60c25dfcf539c2bbde3add0989e9ec72dc420eed535a069eed8379fde5f50397ff4e0c6066c417f725b3e9f98663fd35802ffe0e426fe1030d9a0a30ce4059fc28c1002853674e08b01de71e2130c96d8d4ca6b8c665fc775c845ea0d391049fa96b4045f13d3176e5a0039ca4eeda38e30b5f10f1ce0be1ace64384980b8818afc398d6aeeefb5b5532cc011c8e4d218093870248ac740d66e72c711bb13d13d926169d19efde0380f2b81662d7547ba43dff7d53fdd0755085ae2c5e7fcd1093dc8e806fbd7f516aac9d2f955eb413729eabc0607970cfe492040de6f9ba80703517240bb575fc43bb39186d91482e6323f6b3d3fbd66e0b348ce9b775d52685888c8a1ed7cd9aaad7ac8b35067cd432d24b5997e1377a75ae489bb180c8d1f3130dcb6ddd897b5335f93c6f25d27310069d467f1600ecf9775e0d63dd8e612d737bd5fb301c5126e9886f59c661cc384be032acd94e65a0b199ddc8adf51d9a72ff46dbeaefab12c7a71fe65c512f4ea28d92455074d043a9e58c8a91235da785834e95d9e0442ac604bbdae5600d0a8622441ad679d3f68ed63f9eae0e7a730937a1d8a1be17ca8035691fd460d52107a04cfa9b97129446220f74d5031b92cb0e7f6275ce4ddc9530e47ee0b62279330ba4db8bbe42242122e80a3420c8a2013ec7538749f87c87d3e058c3dd3bfbc6ee111c9b0e0c045556fd9055d9d1b3fbc41c6459db9a8b325d07098ee1e0b5d5d41972ab60bcf33edec6717e7687ab60de1aa5392686f321f37b0490e5690fb8d0f75edbbec97ad10193f3ce94bd31b18f99c7cc3d02b5cf1cf7ef719d353203f6ac33afe724491baaa6c6f8a0a4d3221e02154d2949f95c466e3077d46707c1df1bfbcbcc5b8078e6ed27be5fef5ba297b38e3a6851bb693f4cb8a4e8f75a6c2935db15168c0ed6673c79e3694999f14877e5946968cf7bbc3913e2490f036ff533bfe39d258569041e71b6a91d0d6245053b5c119d53d4d575f207fdb7240168ea5ca2f27c1563373744d0dfac3ca3e698df0139fad2a987bebb351fdbef512a1ee7b0d62d9d6a9d356a421e749e7844957da62d91a57c31c9dcb55b8f6cd3eaf4fe79c14dec2b7a50e0ef4ee35df0bf39657f2e5457a2174be52e74b7335cd770b6cb5c6eafd132590b6fab636fe17a66ea80f1c01f34e291a9ddbff02c9328fdb5390770a711d15b9b7244aeae0cdd7006781f5bb4431eacef1aa5d1abc2960ad7e45424195884cf12627d72013a1308b011d3f6ff98bb21697d257886c1ddbbc55b8ac09eb31e57d37c8d4cb80f8a299bea1fd388428d3cfb7e4f68ff193fa27b682320c55d9411d29c010ca82f752553a687848ff1cce097aacaba9cf7008aa5864ed71e20817a5570374ab55a4db3e03883af307295f528fe4f9b46aeeb56466f6d15b760a3da5ecce450ccf84c166e020293138f665d505a2b1747decbf0289e98570e545c48a18b8f507f68f0519b18e2626f57a67efe8fc13b0552f33ff3f519eb0aae3238284813e7c4e67e6e741d7972710d1b5c21da068595b3b739ecafee3f872e4f9d0f2021f690b47dcd3b500f2f4efa723dee57039c7921f1fcc6944b0b6891019aef9fbbc9415d378e7a1861ea8604bae7876a5765297b7dfe0414153a64bd3bc0dddfba8e1ba63b02092820752e2ce96503afb35f3ff7bc45c12f67b3e88713e225da4a194cbcde3101757089360056610dac5c52623615b9f65a11ebc10742721c0569fb30acb570cdb8690510b73d0ab230ac4b468f707a93581444753ba774ac436bebd9091c02c5d59af527d43e9009d090ef5ec1b953ebe1274374bbc4fe8252797a01f8b2cd0881816f0b2bdf2acbdb0b161d9563335b5b3591934b3f48faf10d1bf667db2392d2143c3ca005b208beb2b9d131e2b82f26ddcd6f62634b262f77772d823d92a5e93b52003f7fc694982f36901a30d1202ff131aa289cf5a4585b187ca443c81ba85321b61a7b8ad59ebfd6a9d44963f430f7012705ffe7135e315b2b489d8b32a3acab77f90fea05b5b3b49c33ae0c1ac3abd0b786bfc74d93c4b8676fbebcd7cc0c7116f5b173277ef2e58f27e6ed51b47b91db5eb91ed112b45bc1ee5d6ba0042b29ba16bfc0262876fbb6b4f986b0610a7b7e2ce9cbf244f406b904f4faddb3354eac70f96df4f3cbcf366bf3a391b2adb9e308f303be634c4c9336445783d44b771a6102a494ad93a5655067594c22f1f122249be1a32dd812ec56cf38cc460283d977e22f6706049680abe910bac81393e3414d865c5b876dc4ef5d79d825e29173f0872b2a0a684f309193a5e2a286f93f9296ca9bd79cc104077878addfe8d00ad13f3679b59c23775a81905f757161fa35a87e7b29313dad59ef8f4f310414db853f3dcb0a9166e26c552548822ea96db8da3abdfcaf9d2eb75798e5cb9c4064401fd430e1cf1c19dbb3deb8f4cde3e3ec26c045355846a51b857d1854078887785c1d550eedf9216ec6ba47548fbb3ce8aae87385bea6f08415e320a1137221add542837b1aaffa98baa7f78a6ba40d432728351148f1e043e7d8349d80b7eae84f7eb22b97895ab13f007d088cdea56b859cf941b421f5b79c9f47ed83eca99ff1b1da363b61ba890d18e555d048a0924daee9918f5363f91d7d4d9244fbff2ec8901345eed6fb315ad8f2fc4b03eda72d41ffbebf6865f215a1750a606e59ad445740222ee34935656cc2fec2e17cf17874e488faff03ce72190c15678bceb8b9134a998db40c25effd853531aef8ffa4f3b9d64cbbd96fe8d6f51f5f3dfca6e07e2ae430de0aa2bd816de5453b2e29cad626e495d688dd6dc1f8913fcf1fce536449a452d6115e18940b71e3461d3b291354df090edd1fbedc41997576ff9e149bee84bfc4bfc14c65c99d9f2ccecda27bb533aed4d326d97cdbe161ad69ee078eca5f59a148fb2cb63b1546685c753d51c297e1da04a9818a059cd85d551c7354d7f997a2503c8ce46ad855e8de18a463ffd5d1e424ccbed7a186a1dbecbd855a143501fc35d6c607602a7f3018dea3c1fc3303f19ca15a842646fbae0d0fcc99dee1b5ec63b5361d890f2a4c186daef9284cccae6f276472c82c162f07b7be61e730e51d8975be44c6dff2903aca5bea9b29da44e78ab1cacfbc95eca15b32b00b255751cbe9430d3291253129a8e3223b77ac67c5df5691489a12ecb914e4198e4ae35f527d2dd1f651469ed0dea6d3d99d15c0ef5b2f4a81559ab0f511f5427567f987c441eb22c3bf8bbb6ca7a44e36bfbda1d26c5cad09742d1664ab68e710cf190c8eda6d49af8b5fe019590418f795a011f4a8bf6b23623ffc0aa5a673e78058210eb5a8cca5a1e0aa93a4918609658d1c71b57f03d7823ac0b0a4fb2c8e41e12d03271612a1fa62a20bb937f4034ba87d5fb487a41e69fa876ab816ea2d6a7257d01d33095019eaa9414f8b7e6d80e8944db60725d9604f5cb9538d5bb641db61760201e8f3d0b342485341570d4299ad5af6f20577d27f13405857e4015183dd595785f7885ead9e9ec8b3a4a36adc94a15d7f8793b8c10b5c9650026c49e3a5305e48708c0cf9828a8f61f2ad092280efe7a9cdeb52c2b52ab1b8469cc308b0b0c5d666f1916efadbe56b311997b5430109af14667e24efc578b4de87521529daf742c8161d17d884d8194903be30d9c01e94f51a9efa2cc19cccabeb24bfe4f622a066d083b309ae5e5437ee1a01363d52a0ba92cd4e7631106cecc0b5cf1ba9808a15d56134ba34822ac106b4ee715606ddc3a8289c2eca9763746edaa47aa3af9cd623074044b9cc4db360648d3d58189afbc65154b0d8c16675b8d3ae7fe42df81532f8b3f4a92b95b24aedcaa8fcc3ec5a6b035bb728f78ed7b0e4f4298f22c3f6f4691b9df3a07a9c1be3653991f1287e29668a9b1b6bd6212a598ff7baafa6af17943bf641619a771b35715138126b499a182e5b7c709ef26ed32d34d5f54ef7f0eb88d2f97339f469a2d7e58073129deb528675da1f2b08402f8f976dcbec80095baf205784bc7c0408d184ce20421a02544995754b24d95a460b3488edb588454c0dbe58562dd64d166fac4bfe61f75e521d0b047416bd0a690ab5ce15637003fb3519643dba50b9a6157160c69be43b7f2118ded0f2a4866b8bed3af5b26bc1c76ea4ca0536887b9cfb73e7f75e008f46640858858a631dbf9eb6148be43c1c7633189bfd97b462f5ac299b90dedbe8da6b1d55cc1786722cea1d5db13e013920b185682c768a3872877075a4928f5e48af485d3ab6bc4d6a29e40001880dec5b4c39f15492ab4f084177fb7fb5dff1bd1db6bf35e6d2befaa64b6c6191da2e861a6dcf0e4da71e2061f3af08c670297994268c6b0b38986ec6365ab0a901b76966cb9d212d8b0a7fc3a7257c080ccbc36a1a6d57ef17826d7c96f8ef2649c07d2fc1394e5d03c39c163b6c8303f4e9bfe8cbaceb3c0328a7d228bc52dfc05bcaa6a00dde290bd20e5e5ed1cd0149bb52c9f9ee050228432067877d4db4eed517b4938ca3862d41a77243300b927de25abf8169a4371a8324ec85b991040b066f715e4a92875576dd7f63492b08db2783eecaa5be332c9784a6e00e73354d9818aef158b8e92eaa1a8395dc3cfb15cb6e6f00153c3057daadef3a53c42f48a5396ccdd52d88169a8dd3747f24c7f2e3b0cf1b01c2d0b36397b56dd9a9e15628e46333d182e79a573b95176aa809fa82fc8dbfa4f996827d76fa25c337bb1827f27c29f94ead8c571d4715b4fb263d843f9fe1737e6bc4c4aa52d377f433fe9b4a574cb2c9229f223f36ddbb08c42a46e6ee45dfdb1b63735868e69a8040e5753fc67188bcb69849f8085db2374f73b65ad5c81ce1d6e996b5795f1bb7e2c53e8c501b21b57b137f9133f7845375fd66eda679be18afcd3525d7ac3691986e3b1dafb81917a6cdf8dd83c99f7caaea9cbb258522612046a5d967d988fbe850162f52ba7b91128a7a94984342cea5ab8bd10bf0057a84b591dd240b8492da5d6787dc73b086bc80ac7d650d6078e1242ebc8421c4da9ca17b119a51e9ed7df2aa7ea3ce1e29485f9fdfb33d48951de36ffab13d0d4c0b3cabe16bc41cbc61d4795432ccdc697bd99fe89ce08cefdd5f984127501d8fda7adc36cef16e6f1c3e14cfc7d81e5abfc9bb3d10cee441e7552a556f46bed3f5c0256a0b9db5a8c6fb9177d6ed4e4ba2e19a3b53d00138c625a94684ba9eddab16dbd200cdcddb7c9749dc8484f5cc57ea8cea7d1a959c4a4af64d588dc78032f26fdb9f2ae1e3e09540232c58dea23395ef20f80efcad05dd380f4e513a8c209099686daf885e97c2b6dedef822f131dc4dd106564f88d02a810fc1840251f3db33c405a426aeca8c1e86db2d05276ff2da0b3944cef044589a4d7c9c43b38be8aa2b93ee1175a673142e1a28c3a4afc42acdf990e0934216bc7041de0d1aaaf8ea975ab16c34dc82271ac784cfb3d7094a4e025d28f46c660ec3430b04a27b1719532e39c07a21df848bbe1f172c71e3972ce3885edea96f313aabda1bae08419676fb3bb8b7af62d8bf14b63e0c2c0e036a7f5bf332a5623b2416440351c31a0f7a3c67975bc249f46b8f58025e59a28cf89078ad3b4e6edeab159af410fe3b9a92843e3f621def111e29ae20fb6e64323e1f27c19058c4a84aab740337720627bff949e5ca8398bddb91067c2a86a5a2c00de819c6baad906cd36b2c32bc0ebf05d394cfc63d53bfffa93c5b98e0b35bf28e5cd0b62faf3efcf8a1b195db145c93e27bb29a9ae092d40969f4df5cdb18eda97c064ab6bdc74b7b1e8ee1b48c542606cc89476ab240a87b64abb701f6aebfb91da4f91383c9d131f809938eb6a069b430071183f7a91e7d1aaed47a8cbad7efc8ad384bb21a7fe24fb9f297477aad78f2efe03db89957c2abd8c59386c1f9cde12f142c6c8105cb1a70b45da218b86d050458f5022d16b1600b59c16baa2cac1cf80ec3b321441dbdf4c4559f8b3fc76748168d286477e46b5c999833abf737459745e3f295327c747b9057e5f6fb49d0ce65ea08efedab7ccf1752a61154ee54655da58ee30f809234a41fd2af27cfac54fc756f862987217dfb4f4d87bea4d9819ee28bbd4e378777cccef2ffb0c67053f99b7f6448affa53498ff3b2e502ac0adebb43c7b4a1bb54b8680c8e57af335c9f4d31ae318ebcddee3ab6598e02f6028b1228c966333c98ba54b4af6da09fd88881dddf3f4202b25e18a561677489a4450e98092855338777df7825fcc22f8b41afda8e721ff080f81af357b27c8615e69bc43be7e433e22db9990395d4be683f542912372aa6df09cfb65cd71252bcbce8a4b6cfb053f6d07650c938a665a52e7230e4779f8240f0ef3a963580dcab23e7fae579ff15c7580ea9e84e9fa90e660e8acf0a6c34c1c7d4de8dc3e837e22dd067278209269b845dea40d938fb0c22b1df38c6b39db81dfc86185dbc7e535e9e9085c179e175cba48e4db87f84e6d5e8d6177e4c862e51899dacee440afbd5905203a0f2b2475d06cc0b0c95471d7cc50d71e9401ea7a6c1a247081cf96c55aa07697c883cdfe60cadd87b308d02505b2763f94f50ec978e4d45c4b205a2c331dbc5f14352e54122b9c9751aa3d9c345e8187b6be4be93fe3bca58ccacf7a8332e17d7a0c46d59172d4244b0ea23d81f6332f63b729934c433a41c01a49e32fd0bc8310ffbe652a1c3c62e6337f0310c3577999923eff0b5f2c1423ba0f1dfa2aefaf7765e727ad70d52b93b7d476f2fd2e3af1447906a4c8fa4113b6c6ef07b13ce6bac93ef88b55bab7b4fcf104232e263886fb21ece27696ce6154f4b06a29f110dff8544999b3482f84eae06cbc2accbf220f5bb2688a963e5afc2e23675d4189baf41ac89ce5f5052f9a697871096e274d25a5b25d65328d4477a6fef1914a164709e1695f67c44baa640cce651ab9022e3bd619e19e6d73beab8d2a9f575fb2fecb9e2a79e2cb7fbbf3b8c423f3f8194e7297deb8189de9bfef75e6a33abec593f427e52b8c8ec944120dd8b9796ccd0234ede72dfd24b28b9dde06442f3f4657990b34316ad25643e1cf47199a47a1913c03dd02913cf48a78360f45620f21e2093b0e3f20f3f0e5b32352d8bd7f07ce40d577ffb37bdcf280d5546e6ab4d93ab881000676a66ea52b8796036f59cb4faa0c372113cc2aaef8d44b72edb28ac088a7278f570a16c712538b467076f566c705fb5cf2720a184400ae4abf338db16e56682019c2cbf1a3167e87058f49d8383a10fb9a19d8dc30c3de0a17f455f2c47bd6a5240f2784141361ba47f251a3f4492adfb0e86645b5e70d798a27aeb91b77db196379c25f128f5ebde7d395dd2a149ede1435d40504b499cdb7d0112016216fddb1802a2c736c5f2fbe90f6422031fd160c428a27a0aa24df8a490ae2e3188a81601f6257593edbe5b2d04cb4b09f50b8c94700c04ba354742a2e6b13d399c9f9febb13241f0ec2e64d03b0e1b224b4120995922ed758e08528b601b45f26bd3b39a67838385a27effd6c10395eafeddf9877b7e8abe2f9a82a51ea2d442c6d0f10fb58b2a6128d9192d01766f61e7e19b685487022f373e711a698b695a3be9de94ff05380539952423704593e9b30daaa47ba1d183ede682bc4670fb5bd9ee6153f79142563a8493fa7ec0a11cb92b72a8f64146f101035cdb774070d8e3b9477ec569f1d947e561821bf16c6d95956113518d04ca69ac6b2847a52719108fc009a7c28345d69e8bc35b77799ad7ab87785a6285751e2c5eabe2026f3a1004e0f3f5834a5cca5a91bd212983f010b01dafabe992b63312a7c62ebb0d16772f1f1de39d2c1e1d02f932ca16f95cfd54fbb25505cc4100d7fe4b4501bafd3ec8d9b0626f214667decc8538c57e50ab6320155c4627a45217dea01281a3457d58cf1d0d59b918ee5817a4d1656971cd072dfcdc5f6979ec0207f3c25ae89e0ccea59ef6d093820be86c043895431ea0b0eab4086ab0f2bd81fa7cf1751d4238836252bf09e7860b3d5eb2dab14bc00cf7c0d24aa9e9ac9be980dacbfccbe9b150f3e45d85fdfdd82d086ba92bf5edf4a43ee046f859d768db246d9be3f09c5c59a7c766bab4a3132787c97f46665dcfcc959f8a6518aaef03b4d066bd7b49b607b4f9d148fc75692c7b8319a9faa600dc1cdb1710912ce209c45f9dc2d12eea23b16081195c634d8e8bb5a1babfd799b5ccc527d0044cbde72ac8feda4b18d6001ab1092a6d496585de3f2f41f7d3662ec4ebc9c30542884d83af30b0b5678c7dd0e67fb45c4349d5a232c0eb9a8976b6c6b24069fa4aaa6bad67b44ad1ef1c00964d4dd96c486e71884a8672d1f28697b86a3d8ba0ac50b54bb32e7fd29eae9d95194cef721a56e0467d96c07ce8fef225ee5de5860c1fbe459a337ee2f8335bd88fae2457845ec214964d2aaec45f95bad28d1190162454454e082d9c6244d1d1be72299ae8c97230e2fb0b2bd558c8c9292d0175734c5c3a1f0d8a923be484d2c66432cb1fdb325aeda94cf19de687c9d01eff420fcec4d97fc143c8c845f695b2f1c111a71eb0584e0829aceb27e2d35120fad6af4516171a94d537efccbdb5df4c4aa51e3b007383dc5a40b6d2d427540da164db80c9b0f3f0f14bbedf0e33f7a9ea5518e35df8de7c2943d372aea59cf0acf30e2e8da3a23fbd66ca4d9ff2758d3f0236c20bb9832d307f78dcd4559e048fe144ed13b491c3615b5758413ac5757c22987fc3897170af758e7cd5df9a059b5ccb7ad8e95490d0f352a57539e35ecb3f9c2f00c4a19a0ed5d270cf145377b0e86aee820e0e04186b1fa7d97d9be23c75ebce44c43340cb08cb919a6347c057dcdc483f31e17994f42d439e7b4ff769f8d8a0386da94974a686d88964883c42e491c962267926643269c1210c4f8f468c108b14a233c4760032ca4a5117aa1e80c8c632049c50f3be6f4e5feac09a8a0b95c865cd0ebf729778d32e88d690bd84f3017da5cf572aa49e9a25381b2e85e30e8e9b5befc786a2b7bd1e0b0a88939670b2f62a0e3bc3e5eb5fedf72bcf23956a792e3e7ed8808d00a893ff677bc1b48ec004758a9190f690d8b792b12a7637c7e67169e5c62b74eb2d3430b951edabe4eaa4ae84270c408570945383b4fcf3b48448586c8b00ac39a61f8c09975fa1f7cfacbf2eb1579ec83b4ceb49f49e86c3c1068524ed5a3b48a384a4680c0d8b311679d8a045f6e27dfb38eec7803cc260edc6f4a567f15b521fdb2e3a3f6db7272c74ebdd93e950d5bb7c54b2eb690c072654a8db5bb8fb83fefd85168a4144315b9130e8038a20c754a6fa182011debb6e9a2be0dd883edcd06a273b21baa54d26f6a3e53565009e574e1f5ff55a46de853b0aeab1dbf9dffd21368b0cb9235b2f91815491aa9151335fe7c3f87d6645578157484aa9391cc4fcf81f314d589d1c4bcf4fd631e6b0998adc7aafccf2ae2368266fd807bb78b6d15769cce1b49338d3d3e3e2f3085d6fe56854d3a3b57db7e31014c370735aa16b62de0209fb1fc6c699721ee27493d0c25bf64ba70cc84ad4b35a13af0696012aca3d643ac4fc42176f283d659ac8f4ad6ba4e61269b6e4582f11ab584f32771d665e8758fc83122169804d706012fbd1af980ec2d1315c4eaafc555cdefadd95af8f1f5361c54ad9ded841019e62e93524ec07f85fe24fd3ce35089f74e2f562458a2dc508ed9dd6e917681ca09168f2280f6fe0bf7daf9a4695dbda8835c57b68f02d01d8f999bee95f81cca73807ee02d2fe35b365b0af44309ef2c59c1e5c7f5e67970dc4b8cd01d46b75685c0dbc58fdde7b9296470ffee403b58aeff42a950bba04b553fac5986c90d38be72af1b67afdbbc4e582242cf7cc9a68d666a7d7f992e23687038ddbdb86562df58aa00e0d34cb47905bc68cbc7363a83daced17b7e4a2c609fb273682a1215eeb15bb33993caa2d19dcce5f593394c3974f604cad479df2999e90df7253e1a465ecb95dfeb5b81c78b3c4e98c79c5e7ef5c651003a4f418a04a144b88a2d077ef6454a87ebb15cc978f59f48d155b79cc7032200c07a7ec14ee8ab78817bec38e160194b7d0b78d24d9b04df7b5bb8de1d095e04986ebf7b724b9a05ade9b691ebaa8862a247cbeaaaa7b46c4875c7093f885d5fb51dc1396f19d4b49082bbd78ec4046556aab57051df8335d08fe1f4943442840594df0dfd3413e59229e09721bfc805ad338601c43cbedb35c47cd8e9e0a2ac4219a931030dac5ac91a309a3275b433add4d77f36e7e5270bf8561086e8fca04b527d26347b96ef867246e0385b4849a048c4f089a21c22cd8b5f5330b26211fc551d99c37908d07a2285b54e4b39c1f96cb2b010b36a4c20d2558fc8ae5e7306b7ca71cbdfdb95002beeb5aafecc654220567ab928ddfa625e98d1bb30afa48210e9248b1414d9af972a9eae1d8a2245681f049fa3b6156c2244a4d49bddee7d927bf407f32e8ae4ff316c00985b5e13961c6337d119cd2928f7a51267c3810570babe6083fed217e98262b77b9e7de180f81c26756042e4501bcbeb6b652e72242a533b65c0424ef4bc389e8a70398932ec99035a992288ecee1a4d3859be5cbc151dfa83e976792ed49923a043a6a9e30dd08c9f144dcd79f71ff1c092497467c26564e1856157a8d086ee7f5a0dc6ec60d4396559a79690927f83fa923ec9605f1ffc97ab3d0712bc6a35e7fae78990c4034dbf91b141a9571f9d2627419233a50d6f19167ccc15b3ad27fcfbe32e26d6f2c3f2ace7ee01df190b1481309521b384bd722ebee8f24923ac270936281eb28a5732bc138d255fe2102a0a35a576a707f1633000791a3ee1ad370ce4bdfae0c60b09a8669630f24684f53eaf34ee314c9652d2810fc8fe2d8b46a7936088751d524bcb1edf96a9fc95ffd52210a5d128a1fad2a87f178f8fba8d481175b3a40f2b8835b73255ae479b4776a46307cb056c0d792cec8b42cf86e1234c93463b827b774a2a60e02196cdaab631e951a046c93502ea3f5516ec8a193c7d399e4a9c839ae276eef669815f761948ce6fd7a76076092a077fe24593eb5d2c520e29ab8759b8e37a67270003beaf7fc13fb53a865dd95d505c7a5c29c34d11c9f8c256a3cc80154b6d8e42d24caa954112e2020e5c6d26868c3be5366c1a5bd5a03752d4f4bae644792ea48ba096e72d77f4a9f5439284db241316c52a311fdc1ffcece5d1144d7f5acaca82e582a3fc44e8e90d4a53a064dc63227b4773f38ecff0685ca147a7286ef0b238a02694ebcaf795e5bb9f07c0843e8d522c238fd642a04308d3789f2ce6717af52a2d80eb068f019fe66f4e3a03bf249c76a6fc3033448e7cf05adb5654d98d7527dc98a6677ab7037eb36ac6c2adfef174c3734d22663d81e21a778eac488fdeccab3b8afb1b5f3a66b6989245cb4b90da1480343077a7545753e23faf7976aa476b05b60ceeead020c8386f7a56bd91b7c3e573bfbc7f332a1b9a41fa6a633c682fe73b9f0890a35ee412a5dcd6e4b746e1ac2b0c4181a802f66e053cb4ceb5be17f3f1da28e0be198a1ab57352031632ebfc9dd174e5f705317aade385424d27438ca330059d0c6ac6636186954fbca5a76004f93e05888e0a0d2aa053dc4fcecd50ee1c7196cc2a6973f3768d4640b716bad4ae65579131c5a445f8bb925636024a7aecf520c5fa900eaf22cdb3cdda92d055500cd1e33d9a32fcf3d2e0bfed4950c8b765c6f38eeaf7a25c19b01edb832f2d2fa79f150095c47930a3861d166c1680086e1aaa48c78dde55ab94a3e5a506d0dcc1f920e8c690dde1625d10b5ee0f54409634260b872390466cc03a22f456617b997980c93352fdda17723965151efc6a4b38d36787895d413e176c9af313c58ea50e2225f2bf2398a66723c661612160c7b6fe019a98cf906fa26c0cf17c6bc7ccc30e4753eb08a755c2cdbd555ad4c4eb65b9355e91221f58a7ad69e60ebd0e71f1887af6264c7e077e6392dd8425ad332aefa285e49e5a4fb35474457cc4397743d8ea6104713710aff7d1a8eb7d10e155195eef362438228c621adad7a982488e5c923c67a6db41c105f3908141d1f55b3b11bd8985a20e35a43c0f74105b42dd38c1a85c81a22e7318705fdd438ca3e0f825c002f656370d1681d2b80c395c9752eaa526cda925a0dc9fec6cb588bf000037ba1881dba086fa343270e5273d4673463b5f1379998be1516e28e18afdf6cae9ab84c01c2576bba7a2a386cfe5d3dbdbf3c208c1a77875cad737a19ce51b634d8829a24eeff4a43025946b74d86a20658f9f77e490252becfc0403050e445b649044e874c130a7bd83b9568d104ca28dd00be1cabf802bfcab10a8404056037b185541e529c162ae23e3e6610a02b614deb18d01f4a9c5efc56284145794b48f00f1103aa181b1d5c3050a42cb8928626563dd301a02f447d6151c18fd8c71b71f1d07a60f698fc45c697c083be7c8094622aea4ad5a06449a7754bf40dd485463f50c8661b538de518f81e9fb8c7dbba36f6775b7738cfaca21dcd42d8c49cb9504ef9586088a3982353080dbeb0d641792a416d48068a05f25a2f644cabf6583f3222a311fe52eb46028eaa80227e40da306df4bf13a85ed74829e2510bbd626cbfb99f6fda254fc5de335cbe53b67689cf983188660304b91cb23923e21090f8d9060d2f4f64df49daa231cdb5488bbbb50b0000d344f1a1c67436d1fc78dd4c5bd243fa13b7555e27e676193351ab2918637bba9e23eabd21e73fbf9e54e992f6ceedebb0aeb1f4f1f4439dbda479915d4370fcf2061ddfb4e63224788ce63a2d69961499f91cdfa78ab16c2d0bb634dbe2ab421f736cff02fd1c8decfbee7753f154f914534557d18ff82c6f666556a17f26baabecdeb56badf058a94cb6c0a451fb13fd74bc797995679602218dc25712b5c73b3b40c09b6e143a8c3e3b4f68c2a84e4496edc3262548f8c87dc5432ad0667f4036b2818d3180bad9951aa5252e5c8b5240df9c63e783b323a3ff2cc54df32d7b85c6801998f7b3c3299404e937261f2f8547cad6e217bc627bddd16155a6cf4fffbde6163dd1d63fcd27f49715e7527be06ffaeb552a1f83c4e20ce3c10698e78eb5020cb0a5252e0cedbf55b018e13c4d053b0cede5783de997b901f10d7023ccdbc56e063379cff66df4f7bc017d785a388366571378647d703a899e256f5eaf45457524bcfe8b75dae78abed24a9c488029487cc8669377e8f3a33a4a66000b9c3d761f2db209dfde30e6249a61ba79eb6905d36a9b2c82d4cf370da056e0b4a68cd95b58e992ca452851f9b04e93be26e4719fb005a8fd52b952b881f67689adb29848b2cccc58d26f37cb3d92704a4c1de91443a5318e2424a7eb8e31e1b2c2564fda0bd5bd6c6d69edceaf22cf571afce6a12111984e7484185b8aa6b2dd4cc2db27ee54248fd5bc8460a0e6d9c7684f30e6ecf28a0241d3574ae627984593658fca63ed1c767ed3b05c5d3bb44694448b15ce8729e2a90d903fb73acac3a050efe32e18196b325ebb017b7294be1cf107c9c521e1d85ab422af649b4f498eba04eba4a0250955d0884a304eae23e4cdd880cba59b51354c74b2d7b80f6f287c3da3ab25810d5ce0acf5ec8aede25133bb584d0d3eea1be0c81b1833b505ef96dcde9c9a823430088faa8c51807d1ee6efb04de776a8787c3079406460cfa0a8faee28c7cf5cad099feec6c4311bb288c3cd92d4dc89aeb083d871d529cde07a16e3a9c7e36a666599dcff78941dd021a7af1a0a5d892813705ef8c474ca057435fabb8be5a626e4dbcf69f5fcfdc09fd8a54bd3f97321be940222a41110d02694e75956ce8e6710ca757c384c004b509b5d280cd92691cdae02619c306994c2e6cc2d40e05617b841f721318a849169007024a7a210e5e2d5de330624e4669d290c42b36c9299701f2c9f46500162823fb014292f104ba2c8f0f4f1f2f8f2b20153bff1eabee010d30a28cf03f18c189fbc1c2c860a5f3549f023a0bbfff91ead81ef8b5eabdc3104c64598e03ba80aed9369de2cd2fcca89f6f309e620e4a5834f4feb0df63eb3464ab062d3e8ff1d8731ff81c439d111a9d2e85726296b2c07cf12cc881ac43adf0d0161241f8978f371dde9f86b06bf3ec116694feb293d843323bcfec9c4ac9764b1bff1c2622809c24601bfe2100ef8d82750f519c686427a46a1e587d0b4a3c92a187a36ce9da85a0146e397333a44bea2d5dfc27d5a45d9d331f0c4774395d14bfc360a922c040b024ae848cb0511700ef04cd61c2e9c15127854f2380961b6a871c7a5678b78d30d3335e8d133973f77520364c21d43231f566247b90073affa6eaa336c57f5d285399495540f374589dc3478686593b00aff69330fe2d287a561c9598c91f548bf2b01b8c7e564e4ec2631c2d4e8437f9f79f2b5e9ae5824f7aad288891d08aeffebc7123e85a530d2f9e76e73acf9ac31214017cd27f1ebc81e64ec8642e82473edf2ef88c79a45a0d53be1a05b684daa262bbb7255f24377850546032d26c639bc09ba300120e48d6c243212c402f8f20d460897028092b71bf33cc46d5f88f6761f9f069276e68025a1a25f583b2bd62b549d253d0079b113b8ec7e7807c7204b3117c95b825df29a7b692fae54a7632e104fc1db9f38e40f99bcf825c0a9b701d567f6411669bd1ada8622ffc122ea9542a81af1e672cdaacec9f23042eedc73377f71ad501daf28a28770aa17faa13328e6645b5db8faee8f6e71bad5d64a0d6bc00e0b080766faaa6212b18bbd36b028ab7e393f972bdeecafd8c65a3f8f52069b774e472190fbbfd9773b06be7708dde1106c411c6eee789ce5bde11a74571208156bda4dde4973080610c2fb990a668afb1d66f7ab85c5e5c3ebd3db03454946dad9eaa21437c00a09207d18a0d9126ce537e1b6f282ac3e5e00ac8826daa46fda17ac1f77cc7de555fc382faf6f57e5a8e80ccaddb46a488f8f3e7feae83cc28c26189a7243a11a955b8bb351151354a5246e3eed618a18089292282ad575b6840bbcf7c989509311977ebaebed26d0b122c96840c09da198e4491f661a7f3c2c4f9403c0c2c4c9f22d024fee685c328bdd45ec1f7fc98805c5998a59c421bbbf7ff972aec5fc5cfc41ebb3a9d09ba11c94a4d1fe3d1f32be5ad3af04f6dea964ab9ceb3a9b3f78481785b5f8bf7220ef4a82a01e11c80d952b7cfca7655ee0b4eb931ce3046fb8f59bb16c61a6ead46146adacce01ff6f13a4f60a70db2cf581d5763ce9f229796cda9915b248af040a958909538cdb7d2194ba6361b4ca45e5098b7ec20bbfb3b287eedb923c85ffbe8385d5db21ecf881b01dfd68640a82eaa60051092befd94077b8beff6e107263269ecf99a5a0f31011acbb54eebed443d13e769de07f56ed25b9ed1f9eeada72d3f368e9f184b8cbb21075dd6eba038035404b0cec9d9dcf2f5b427adf091cb7a66ec4c1524b6e67f38d37da911c212c7effcc93c70ff76ed732b6181a77624fff72561108a742a31906d82a18f1a723ddf32e131b386c2c820ef9c68954de5e00a61676c7f62592cb9308e6591e1e13879d140446e8839a3d97883a3c34054f10c275cb39bf61cf96b279ddbda395da3622c027db02b6603381eb9c97be588e5a7b895c79f593f06bf7c7984328a70ea6f9c7eb21eec05b384cf2a2d2a8eb194f7420259919d98e1c59387efcd8bf4f19aa25aa92d64ae741941f1678fb1eed2042b0701d212ff0e20885fe8d97dc90d494afd05ec178a0eaf19977e8a851a845814f527e315bb1dada3c3fb0780cb0a0d24face66e657cef6a492ed7737c5cf8f92de5a92adccf833e77edb39254f44a208a0c410da2c0b3720284cc25f46ebad43c017d01b41a41c7de98f71bcf1d23db2234baba2a47bc7ae08fa315ccf5cedef81350ea1160de44543572d91fb6800df9934fd8787a3413320e0bfe92c2b9de88196b5327a5046cac272edd97383b8b659d342c9da16a9b98100824648ec9f8a14689afb154c30e4a90c894a27a301eb1914ed48b674f823d6371cd865f8a23e4cf5c10039811e5965cf1275676c1c27394b9080404fa163f7e7307164e52aca05f089193fd46e59c06f126413a53f4ee6e43aebd0f24e9b6dc0cbd6a42b6cb3c37a69065279c2c42d01f89cf4acf09b562eeb8c6d8adb9fb9c911d4225134aa2adc3bcc67ab577e9b56afbe03221a22f22725e3aff15fb00602853da3e063470b8deeb37b2142b794ad37e6a5abc20d785b818bc3d03e2f5774f897f2ff49edd71edc785c1a2eb0a2df0563d7b153a2ddd60786084c205869853434301837d8019d9485cb32bc7c5ce6b484d352490ccca3cc7edf7bf905d65da02d84f1f846ae2fdfede06dfeca4cd81271055e65e1f73d37a9efa0c01d36d5a3ecb68761d4efdab2d109bd1f52c3f84b6d057dd388f65df76cf1ee178d85e9fa79793fc6e9db1a7b510657bbd51e459d40e9b8ed33f10cf1f44c7159f967649c24966686dd504aacf820538ca0e66fbfa466d02e4a6be463dea43a90f538f4bcab02835016763e09699d0c9457468f60dbbe7460b931b9aac476d3e5c84696dea55cd223bf0fa6a639f03d1852a78ed781612ebd273ef55e3fb1b999c3537cbc2b3f4b398b7f9ddf4fcb15924ae94149e075286b9f45bb841ff5c268f1bcd553f68c4ad589bb3c26c0b6b4da8c65fb6942892314f3d2633358aee7249fd474dbe5a199edfe6c014193bb9fcbdf9c8b037d1dc086268b9ee2ace64b0785267cc8610c1522cc087c50e59629cfffc61f483ac60ad3a046b4452ea50353239583c8fa9f35f5c1f453994fdbfc34d686708472bf6c5e8ad33422fc6e7fd53252edf933f3d9a408296c85b6d9eefb72aef841d2e46799f197e2dfdbbf12375ebd0477da162a001ceb9418e77cf00f5839c98c180a3f6211ae7f9e03c3f6c6609badc16a5924fd4103562a51a06b96b84af83a90082ed3f79c9d2ebd879bd424e755a15896249be155c6116fa9e42cb55441df688371362b33897ed54eca10f447b506d21a9786afc3566cceeb0a833ce1c4eb1fd5ba226e89d5a17b8c7c4edfca5a1f9c749840d47990e733c205cee0f5d945c10afd70d9282e16d7b7e7ad9253307724b655f5a10f1ea903576b0b6373a81cf97e37236d734280c450a39fb80008bb15634ba865b02fe7158ee5d925bb83b669511392a85d9dd35d9531a8829720e4d0a048d5fb03442e22e81003a5c3bfb7f41d975ba0930e3392b805c32433cc479c26a936d755524b87ab498114bdc8e4db97273ecfac4d024b898e129f71bb44343d0cb29ba89042f799f0ab1662bd7ef27ff04f6ff8f6e4395e0b0ace0653914c5d1f8416326b71147e19dfcef444be68471fd492d023d75dc2a71274b3284889bdf22edea7205b5af3e7fcd59e573cd414eb8b4255af04e60b2f4e5e3d9179870af135027ef2bc2f03b3e2a95776e6d8152928892abec0cd245aa53c1674b0b4ec3dbf1c8905ad13c4e4f0f7cee6c18b2cc6bdac467cc55c3162acce19fcb41ef524d40839136d406308ac79c983459a51129ce915ddfcc2d1daefb833b1749155a802411d35750607aabd4e7039ff17e0235f3bb16a8746acc93cffa060243d76b4ea78792cb69f4e4d36702751a291509fbb07b315de1c2c9ddf6de9e351c2930d8a5a82092bd6975066a3f6c286b9c496fc3f8d4693b0670b0f2bfa4aa92121e00962799210c3576ab148130de35b80983c7c6a7eb11cc2ed8b6b665318c501aed5a121fe0b326440f1a855e66584b76b2d7b6bf35fd4e3f42da7b17eebc5a1f8b29d0f8acfb3ce1e25d5d097c81a102793890da4d1109bc8157e302d99d07f9087d6802562d85c2b83d170ff41dcc94d81482cf6f207ce34adddf2c3cb3486b581b04512180157e8969d3e232874c94d54a609189cdcf31f8ae6f513923b9d0b329da8e48d369d5314cd033b08165b9f947fd49bd6e2f080815154e429cf85a1251fb8a7638ad17f10329877c0bae2cd1f5320fe2bf11cc9e5dbc6b9f8b0ca4b5f2acb44c4ebafeecea4a10b77abf1f6eb66c720465d4c3ff6c3152eba3412d9f6c46bede6adcf8abaf63d217ec1889e570c4b3e03c557915de86d197b347bd186ec26f36969b489afd495b65019216d0ad04b269837316eeb2101866cb814c2a5c76c5d0541d8388b5a23600f0f6baccc321c946c4a2023d95d45defb4c45325c6a1d31c42e4bc2bb6448e59dfa774f913fcb0519df903d8a691881fb9fdde1ceac78b007bf8239a341d4b7597550c7d567d161951664938e2c1cdb4b67bba6649056d53d8c2ddd1a416706e7f359401f762196129f30efc0a37e2c388d46e1d4dd35c63f924be3032c456f2eb4661328147119fa94400662e35d932bda084a2faeb61701e98d4fcc7d3ebc2bd8311411dbdfc7e91bb18dcb32c6f3f9b87b617b83fbf2ed35cdbdda98807355016f3641c8bc55cafe48368fd751ef779554de3202ff94769054c5e9cc084b2b7eec953956015cc8220ec3998b006c06616df32ea7aa3d742e53b719d45c739505182cb108ece16afacb820fda761d01b6f0f73d79b72eeec2e2ae82c6ef8db9b38d80f46985cb23d79f714e1dffab346aebcd8be74ef120acb98fd244e5f3d71a45c62e7b5f8d1b07312ae152ae1c81e503ad99d9058ba0c32a0cb22fadf1a7a308b8e7e8aaed4e531fbaef9e753c54beed6b1bd5eb0d4c1d2add27e9fdbbe018cd36059b065d2c7262b7479d9bf2f1cd6f0e0e71eb440abcf4377d572618b13ad932986e37417d2cab1879f9b79e30bbf49a7aa6f7b522a1de0811fb7de78e9d8003ee0df4b9f62628d12b538c6909a5b1f8fadb0cc547a4a289e5a5f428daadbe6792c44919fdc4bb662f26bac3a72becbe215abc6017b7f16dcc10cd7687909f6b7450caafd9aff9769bf7afbb9ddcd5530fb315c0c4b792b30966becbbd2e21158597400d3b22b2e3e84c5ee388f3f67ccebac4297b28f98f27630c7cb3b76b3c2335788f085959f9b0972d7b6b6d6924d81be60bc5d0dec5f4a3835f340d9fbde9c59ef5625abfb5e69d13f8868deb20e05797e08e7640916b68c911fc452849ac4749fbd83eeb2088f630ed5531a6dad24d3f4fbb10c1e9c274ffa519b105365bfb13a0f78a47d41489a4819c9a7968fb64e5e89950bd3d86cccf8207f829cb512287fea8f71a752e5bd37a21089475b8e03d79b56803297dc3ae23449b4228451919221b2ce21a96995dc1611d5ea7b61802e8ff2ad97090fe5c5cb824115cfc8038d3b917f52594cb4d0eb45c57d9ebb338ab4b956874226c3d33743bc4d163ca6baf3ed744e97c88feeee051c7d39034f5bcf39707dbaa6c4c45e5ea1a5dfb16e74b6a9c4143ebebd5bbd199db5018e55379af87994cddf4cf439e333ec34503ea028a9a1684c22ffda4da2924972c5c9163ffe006a3ce4a38855ead9b32049ece09f867d1c63fff79d644f98c24b5baab9e12f1acbab125e3e55b4d4c735d7113fa614befb4906b7281fe626f4108eb175137add82212efc286e186e9c094aa99b7cb0b00368ae6e3b6e179ac8877d887e5ecb2b1c8b064601e942f574b422d28d9364a5474fe6012193a6aacf3b8f60ccc0de498738b12168a4c3244c53404c6511db5baf2f544633a7df968afde2a42128d6195b5e51e9da92eccbc4891c7ceb52202cba5e261ee0155e73ff5dd7b554fba4fdd8947081f43680d589f9170a2564359594d773cbfc1d235e12359ee0ee924aacf156e7b1fc2cb0058ecdd66ec689721b19b92c1fbf52fc8d801fe10466badc8348855583b5a16b098d32adfe45b9b9a0c8c49a65461d3a781efa759ab23262229ae42aa3ccd6fdb008d91b46761d79a061df71323838b68627692309c8307d4ffa9c3f1055a0fc6fbe785cc95b957df98094706e97332a180353758481dde58014b8fc9133eb867c3a47fe788133952fa2929048a6f2d5c4df64a1dde9d4b483f9aa14bc7632d327bf84926d67be014fe82ac7ec552c7621eb8b7bfd60a77d140cc5cb8029090e4a30ef68bcc730bbdb53d3552281ee8a7ab97a7af8055a39704dc0d0934d9488db134e5c77553882766da7aa33f117ee41a62acceef8e60028a239a51f510d371502eaae294ab9c221f8e3d0c1f6d9b92893547a2a7cb797c1a630968f81417fb21011f7638e494650f16b7165323e7f1f71f89e2db82fc6ee52884470b313572ede921b298ab3b6270043633f03a1ccb66bdaa8a2ea218465f989bda895f8b215e344604eb275f0a07817ccdaf331049a6400b0601456f3a22961238eec7a44c9987f8220b843257042b4d0026871821eaa35daf5ce0b2a23989ee5933fb68848d6a1e5dfd882cede468dd56dc0bb1421798acf69ce71cdd778b5899a5add26e07c8acfe52ace6f0184374f6023b8bfabe7d03e2ab82c7ce7a67f031f62b69ed52f20b584ee7429f386ed22a91be9d51d4b1e725364d508008cb69dcc1f1b476a5bcc96f5d8f91a01da0c979949225ed00951f3c6527884c5f87d72339b779ccf40fc6ce4079195efe698d3da7309ca700d8740c21917192807d573a5084fdfe6159fa46dcca81ec3e8b6f242609cfc8bb8a390e7d725b4aaae29047c96b602e688acab9ec45d42b7cb30a10bffa8a00357cda7ea3f0b81e4e9c31e48a7d6ea3b219b8dcbca02364d45f44e0c9d52cf677eefbb53a8a633c72b5410af956efb4d08ea9f4b1a9e4a9c0195efd90392066fb838690aa6c722746a70bff5344ff6e8bd993e6fa96b2f4e7ec84aeea8b36f061f9db69a1cb232aa340f857cdfde297f1dae98c0205804f10ee9dc384fd575b446532a665b9f8f161e0e95ad0a96ef8de2b28ce107873c89917447bbc86bac47f157ee32d33d38124eacf61f06837ac48b339598b8d0508d8d8c5113d8be050c88e604cb2b2a5896230d0df1c1c7f5c51540c5de683d42b5d27778148c6759004a058d9d057c7271b0bbaf963618f3a087f0d400258cec47301f10d3d1d93ac66ffa16947f3b5c074520d3ab4e402956b4db3d25e3b028d08893f3c263d51f4644d0088eade40626650cc179019c8208e69f67c9f150bf635984c5cbaef3d64156d8123488e81ab94535e13fa39e7bb0605ce407e3472be6c6c8c074c0204e4f7423b415832a626662b2117c4e4cd16ccb39bdb9be222cf166db188ad31aa9f51908cf42e285025dd1808fb5afbe7fca98ddcec4b5a5284d4559eadd9af6a7e651a44dd31c4488e1bafd6e8c6ba3109e77f63529c94661231a85bb06fc9b5d8ed655d60e16a8c060c170a40fcdbd3948afb6cb9fd7b1f25340c68c3d86453673673f9026bb666413367f9c9b6860cb7f0b50b2db4c42a899be2479bd93024e20dfc7f7b29f26927cadb664c3e5f7eefaa480f1c03d8e9e65a1cfe646ae381794921ac3678705194de6076406317fb5916500232215dab59cf69947a8811d516de8a7d6af67149375fc45a354ec0bf6b39f142fd714a2d4a0f777696e613e4e50e3a1492350d64f7191a8a5a3133e7c4c72ff183fd714fa4f5103571e29f98a3e907e7a09d5c60e14ea4cd11571d4866bfb8bdb7ead9220e304fdc79a080294dae67a33590bcf896401b16776aa1996528d86550f2f34f0d60fc52197a0e23cd2ae15dfe8e1d2cf4d373c0e0c68708abb323f466c8f444148e4e3073aad7326e96a91ccc0a7246f3191b5e60353cd1275d00d5af656c06e6ebe807c515f65955d1f7584c1db0d70a479c837f76f3b6bae1bc8465aecdb781d0fea90ba6ebd98a1060c2d10f6491fe18b84a54da8586b8c6d4ef80ef47cf7798614a6dee25180a2ece7fefab78ba8b73edef97d2bd4e4e13500cdbcebbddc48c5133d68884da318f657c947b3f68007322f7d6faff651257042e29f8f6d07855ea257910db076990ef4861e26a2a10b79b2de7d7d17d4fd06fe7037888fa5249e9f68adc8d0f36515190202fd1d92037b07ade1a724820184223ad496d966387d8cd4d03ed24e2f26658988c8e74203ebb35e7fb920077fc21d03d8f7367fce460f02b89a33fd4b083bb4c6b8f4b7f3e69eea5eb0a0906c9d6622b8f9447943b472fa4f66420c17e443379e3c2e830e8593f04d194d0dd6b9f51b15d8d7d8eab2239a8203a3c773dbe7c862b7f10e5ef6c3716eb165bb64a0c84d79c2748d399835512893cdafaf07d71587db8db80cec4219b4083b07da5160ac9096d734dc44de7341338dc03cac85c5439dc8ba8c1c8914923eb3b99763418feb3d6d2d639af71ad9bf8107fe22aa8e6a5c59e8e29cf27b5b18c7768f7a758d9ecfda786eb27f94338c9e8a2024bf5bd7d86e369e41404b185f048c33ac0a6a518e756e837918642adcab7eba90857f31c12717466214ead0a80d0f43e68f108f9bc73df6aa88d3d73fc3cdaad47df9ee2f18a449297944926f174c8bff9ba168c1104191ae3a13f455d7ed724971cacb36cb98b9115973ac9e6c84f87c468fbd03c21fe483bbea85e32a0705c1e95827ad3c1d575305f5ed8ce8fc8f67aec9bac204c164ba3df9c8a1d36ac084b7e5f9d15b60c1c4b175ddb07f5c2dc974fb6218300ecc7017866b9d3e5916a26c1054f68e02cc2af527343ab55a1d873cd631c35585587942e18584ab189153e06b9a512c5048034dad1a2224beef5a316f2c60b59be00bde53edcaae5b37b2ae22ef54034ccb989a8eea31faaa1a9b7eabf0ee48f0dee1aba0c41eadf785a0b5ab7b4fd2893a94a04476f67841495ed6bc959a384ee23391146c4166eb790d988e8cf9d0d8295086d01994748c6f45edad6f8a637f0bd3182733fb66b564433f38b18823be53e958e415f6d8bffde1fcfe070eddee8284f0aa6d8a92fa84f354df51436abfb666889bf7e120c7cc3e4152ddcf6b1b10d86b8ecea0a27c9d14c7786d26347d215b69c30e0ee0239e8b7d28bcda08da3ceefe2b2abca16004784bbc7114879d3711b3b334783c1c494c74adc2256468ae05c42a026a6fc5e42dd6385f305a8639aa02b765e06f39efb57094610b604f9e32faee7ccf3faaac6e2579ab7c1ae818975703ef784516d215bf04ec8793159d56849aaaa11925e1c4353893912918a47a95423394a07da4363096885cc8c1084a679d399459e9d29fcba5ce425a3bd2d205ab4756f6d870ba9a5d953b7e2de12b4dd6fc262e1eed4ceebad95877407abed3a7eb36d1ee6e485609d1f36627e4f70443e5a41c859892679e4216bbaafed38e119d6e4ef1623fc00ddb27e9651bd6ff4051197af0eccc4819b01d592cb844acf6ca3f9fdc5f56b9e1dc7fa0494612d35dd3faf509ab79621b9271f310297803f4686b89250737284b6e6bda6fc94cbcb7d24c0b693896cd4faeb2237843e1511d6506e6b79fa574bb067a19150e4215eea60391f9970f64c1fa76f55f4c0fb675c25325459e3c0cbc8d0895477b78e3a111892eb298c9468f788e6b7f25e335f8593dc5e2781adf6adc82b7e972404b863759de9ab46f57b10050f8f0076137308483ddf8df21e40642c09f84fcb418017bd26f235a57bccc349406adf9cd07622dc29780b695948c5d129fea07d7365bda7c07ca9938631806d970d487a7564316f8b19263eb6b62de75bea971703981b33f9c1172677951842408cb8f8bb9434353fb4557a76a15fef4374e3327d7530f377ac83385f1bfe6a1122221552665dca3dbdddcf89825c5b3f774690c6a88d61bc19eb856044b01a0c752c28a7ba4fffccc4894937ce26c4e22ae19fe6f19927ce50c6689ac9d978cd448a2b9f7f147b9daee2cf6a557702a8e2fe265094799562c8a36b6275b40c4d8bc8080bfb90a4f27d03be722491e1b4657fd397e27cff9331343df7a63ca92e95e57dc5afc8d1e6af346720a40be4208fe42a9b9c751836a0087f9ec27568c10d10dc036f7b8aa6b246018f6a69a69c8991186c7dbbca5932cd8d1860f957bc871c21f215d017b8802e93882f6811c2b3049575affe1e7476b4dc19aebe07d83f6715fd01e973b345f5af0d0f190c40d19c2f1498618fd714f8d32397c1d8a25d607845bc3108f72ec28cc04f35792fb90e99f0edd865cd141bfeebb04968b8783219dbf501a6b5e3b21bcc947ad86ba1b6e28467907c77ae718863bf0486ef5770e9d5bc4e8c7a40ad59aca9dbe1bbeb62578355a8bfd54d841ed198653f6b10dd73b731b74e007e5f37f3a7f3311b3db974e3f191c849ba5079676ae6f006e2a5491b0ebb93fdc0fa9d04ddf72a6f852d50f708adfef5e301cdfbbc6b5d5d5b9a1c99e8b8a5d8cc958abbcb65d1e1fd687c9f5ecbee80150d8b31286149103f875b62dcba29b16d856dd45b010c06f82638ca1ab93b9bbc83c9aebf13031098c2707313c140a8a67700a29e57ea8621b647669adfb031992ab3a7962a0d56b726ca0e20a3e788f38630fada9f7a8271d802de83537ef796d13a23397aed2a2b71be45d66d8fdde1e9cf19969e91e219fbb15dc55b2f390cc871c53db21bb9a027cb82179d455c4cfd6dfbc9473296606c540a7eb1b37e4077f8b5a3aaf3b34efe8c592f1121ba40079e0669b99e1408885f0cf332c4753e530888ac410a8c538d9b0791a333573d5c2cbecfa2842b07c4f897ee97ce38b6ecf776ae3ef6595b48f0d8283fc893a9f2e19275aa43ce68094fb02f714718f1794f446be15f6621c016abc39255698ea99d8f5b5c510ca694d004eb689a43835db93adae9338daa8b8111d9076663dde11e9b79d695c77067efe78c1d7e9b2c3c71ef1c4d5ac247cc51efc7792c870e95c20d27ff581bab27e1339535513fb7c29c8aa5cd1da58dcfa019569db708ac29b9f2e068bf397b9f582703760e3673f9fc734c1c1c6023e0dec89dc40c179b59b02ba829aa9c587aac69f4ba9161ec42f072ce2244da31e56685b50f2d23e42b470e56ba68b2dae9c916c8f3faf9da5d23df617ea8830af4809f3e43ae36f0b4ad727e949da6eb4085d22c5bc6231b6a3e63af804f2914ee13d1f83074ab61451bf766fd4b95cbb44812da235387fa6ba01633ad6b623d499460d95d9ee50516840d16d0cd57def9e6fa28fcf94fffbe0701fac1cef37aaacc7d34e28478e82688db909ebaf8bcba75db4fa1d307c57a00fe23560ab91b8b0a5e886728757668077b3eb62aaf42a92954035a9ab950b635b62ef0890e91ed8b0a71e83d6a435380ec71b84e35d2c93aa42e640d34003a19d1c44949f6dbf5b22d3faa9000373f63304ee8e251be46b035e0afcdd42f8520fce279a133d99dd160e8ceb5e1609e097fafec7c3b8de868858840862b7f9255afb5a94a8eef2cbff60e7686411b92d073e401fdb747353c69c3dac0f904229316abf998c08550f7db76e828a3ed95071e914fcb6c1e6c87fb7d619b21060f077a7bc7f3588548d2f610977227c63814b197ec033c65c3b80dbe7fa664fa8639481b413bf5f2d4adc93ec81e9bc538f396f57d5d8de9bec765fd1d177b700b9148a875e16f02c33a7e6f3b81a8356110390a3510c169f02ca3d1c95c28af44ec89b29242396ecdb43d4892d206cc74ae5a5e88e32b2f8cd99936a2629591b64c74d4611551c5cb8b93f2012f3a3ff6243da9c3cc4a8048aa022f886118ecfe4aa80f54876439c9dc0eb38e6b06af4c78fe2a2c46523eb0036d2aaeb9ae434c962d87f44693fa993c9fffdeae99825d60a71d612d30173bd812cb312cee98ebdae3b3337843ad5417f886a62b314dc1f9cc1a8110356a4b4d1debc7cb983e70e3a54476890d01280e8f6025b965fb8ed9c0ecd6d3ee73a911e3baf6b1c680516c113d23410920fd75e9e1d0a0a0891d2e31af57205b0e80a02b9fdc8fda4b29a732bccab52831ef93936bbf64635a16cc2aa9d13895f68f3c67188554e1753e9fc9325f6079ef27d685bef76e62007aeef06eb6d2bd65fe3c5a20cc57ff4ca943706eafc7d4d296b2d0ed941f09892468a7d955ff4ad12b1f42e25a7cc44b51e988375ff68f3a453e1332b18ae7ce30878dfe5fa26ade41a6a1bdb3fc761eaffc6cdb80b2064b717e51145c5db44d8646801c2ee2c56ac4f9a13c6b1a77b56cbb7eb8d8a81681c90ebd5dafa8f784c51a93dc933dd5ad53e3ce4bc1bbc162065c8a91c52483cc4ad5e0e2ec98836273fcfef8090bb2053da1a01168c4f8a1e8818164ff075106ed8eb32d6b55cb0b9efe9f990c62e4263b4900078eb5682a0135704561f9b891296321819b36fac6d48a13f107bd30f20a30f1a3a41357e82dbb78c3d9633411ec5277cd3797102c47d1e2ce12b0209e353e1af54f3f25195f4a18791a7fe427cf3fe7ff1079922a32f172c1ec4566877f840bfe1e355a354b7eb1512603a8f709c52247745c78bf550a93096a188961c7809712d8e6270edf2261a7817eb9c4257d3d41812395598f4f72d591d2ab158ce76905685f086940a95bc49a5e5d0b740fddb633d77a89959a29c54e9f60d9b81e4087ff6260b2df7d7e7beab260a5e2932e9762bc143c7d03ffcffd430afae5b066e8c10e8f63e675ac9b5aa1d19a6521e914a2845f68c55044c19cb2ad3fccdd37678736c913d77b656b7ccf643cec47ccd89ab172823f49291711a8a669fab417a310992fe1dbc17c50ba09abf9c872778fa0e01510d57cc56c95bf68c4323f1fa0aeaabae3d9ab210df2d0f3bea47dfd27579e781e8151927bd86e81bdc222e16be2ef3eb9822f07875212256fc335d007100d1d8899f4815c2070a1f248ff216fad1124f51f0da0070f1c727b018118a1542380c6dd99e3532e91c54f1f826e38403ab4b84c0aeed203d68b4740965dce9f9b3e672ce13216961b6af56ba048beb8e12a271be294cf0cf637c151d7fc50952c0c4ef061a75f0572dd696a550c0c2370febf1bd636cd0fc7cee540941faeef2ce190a647953bcb3fea3e6af205a772db8123dde7c60c2436ff056a7b494c913a1e96898ede0778491a5ff4102d1aef837d7004f6a7e6a73ef59dbaa2b8e680d554e329840c4e158b59e8218a2c33784ad4dc73185e2b4b8817648161848b2317ff5599a8aae38546d062ade89748a23fe5cadd144ec894e0f8985350a07ade91ba13f851aceb7d49a86cce92249d9b8044169abbabd92bfe945a5295072175c27a79de04a5923bde27f2fcaf04cb15c2c09a1fffbbf0decf554604f6a16911e2d71c7a6300965b3b881a989e1ac2a292f1991e9a11ab3450dd15f0ae7e452b330fc378fa29a38e6d3d494d3a439a90a64a90234ee3fa9f33fce57bf3fae77d5e55970ac904ee82b71f2916536ec2cb8c0449266f06189d086cff10600add4d54d2f2677056a198cae0a9a762b5d3235774946c53bf77374a04d8763d7fe618a7537c2a072b7a886fc7fe1fddd4b237fe06d753c5c3c35bd4835ac03352033923dbf2e471d0297a919678a03441857483837fd723daf7986b666895bb477ffefcc28f663969c05484d0d27651c4aa3e28783c87c249312844f37079ff0cd3a3d8c68b1902dda42649e700cb81d0c7cbb0000a6e7b1704a70ceeec7664cccbdf74d89c67a3596e61e94c27433ffaaa40bf1b26e1c9f904900ac4af209da85feab962877a0c2acfe3ca5e971cc8619709c6f707c4e8f838a5077facb76d8ebd8fdcdf73e31467e4b97dc01b0ed426c91c4d5677073599c5ebbee2324942759e7d17f3b822b08718c8460d7077539607cfe209549fda571824b16de8d75ae086b19bb4e8c110d6edc907b70e1e0a0d155ba5f4ecb50985ec23c3db76aecec9ac4020a1edecf64621e147d8066d7ab43744d5fbb33328103af3ce02fc53fd9927c9075acccae39be797948f484718a5a7021eb5642a244c2615d9a3bfb14bad9dbd8848f6f6937f7e649b269c1f30762c5840f708ee5324251ece2c438d3e33b14702e6fdfc30b07cc4bdab2d9de96c801bffcac2db091ef359f2a25c20b20e8f8be408ac43ad3eaa8498a553782309e874a5981aaaff777fec806b00eb10cf34648dc3235a63d22d451410e327a281bdd550cbb4aede94ed4887d29b326f0d67d1e394ce4e917344781fb4c22c4f7198deecbfaa5c2df2b302532491c7456ed470c17d29f0cb1297babf6af43520e95977704318c5aef89fe04253c19244bf67231e6521f51420e5571ac906749ed33724d01283cf8e927d4f8f624369b2a977816ec6e222a1b742299fa79775227b898b998fbe97e9c8832c62394e6eed4d89678675adf23d3f0a5b9faa8c7a9678ead97ff9a95f33d74dc267415808783a57850fb61c11c2d9c338a5a9139d64b432a4ed34731f3d2753aaefba40417a24d6ed577617767a895dd691e592e32af0aa0fcda1a1857725bcf465332bdaed206367f6e4a52b0f57bb0ef975c49b0023888017b80d481e4b9bc396523a4fdb3665448dca2f3bfa6f4ff9502a702866ec8a43958b8360e0c6557f20bd09f0118ebfcb29b044c757928c3d2a87e09a6c6d8f09a3255bbdae3fccbeff8058db518758a523e9ce9908f46012facd87634ce2a210d91bf41aba35bd396d5831986dfc787db63ad962bbefe4d2f8bdecf5ee1a4b4a172b112dbc92777d5537f6e97030948fca20b48f2e006561f4870d841e0d0948ddbbc36d95d46e6caf021ab251a480f809ea15b81c6967b12a49f35c7205d4686bec31c69bc6459e57f062f8a73f5ca7eefed2679dceb82ab3934e0a4409ca964757e90e1a370072831bfb4af06625a9b237fd5253bc74e7d632867e8eb034b2fb6f700e4031c2a8ba9503cff3b4967922c7e97d411efac16c22a02b80fae43eeb30f1746496012a8f0b1c50fa044ce9b44222030beac320be61c74337cd9a9eae4fa9b277cf74c236b50dba285559760793b743e0a4b298b1a96bd9eab67913bbabfaf6e8be0d686d58eecb745607e87c5a908da259d8a5c8d629f40405e9cc26eb94f248ede617e373d78c01bac0b1adfe130c0f1770d4719feec9ecd691bfaf5182d6b98f9a8a71329073f93673c44a76fdd8e1b334245c81daf02ad54be36fd61d30afffbd468ac8cf65a1b1fbb30766eaa4b5b2ebf0cbb26166ef094d43ee4ea1ec989f4f07e129712339c49a80354113e4edc485dbf83b43c6e02bd1d598c5f030c22b76e36d6f000f9d8bdf2358e1ef7a77c6784d72914ba7224092a35df40a27d005abb7fa02e7b1f013840d3fe6eeb6217f487a1fdca0495c8d84a4ff960b1ac39bae351ad5f592512d8655cc96f36327f5c13d4190ca5f9b62a13b2f56831e3cef5480d1d846cc792a4705047a2976d2890a619f24537350a276837735174ad15ee64d0560f29a3bf023e9e5c1d6172c285229d95943e5f519821ab0dee73cb5dcc9ef0416b2194783fa4807209b6c329b565eb5c4ac7bc916b3a712f6078863a7b2f09f17450561d0b2b87e52519842d9df8bb9270d92c75329191d29f0ea5b929165275fb33370ffa53cdc9414b229c3dca72473ebbc0ada4384b02f84e771fca1c31f543b7dfbc0473a286c47953ec9bfc6ce7a5032a7a1b5891308a73b57495ec36cd87ed80f4051724f127bdb4a040c16a5cc77ce841159c13bdc8be2a508532fa76729cedb73e028d3b78e7ad35b945a87a79d66173b436b45dfab166cbdad3822da5966af5d2b23a45d8a26f0217b0773ac8284d293e708d72bb93ee7e953237cb34e63f574a7bab95fddf26f7702b6ec771461ca09dc7e8b17c46a9b7ff817d2929c754d3122bf792b194bf8b5c0476be67df4c2fdfa2c902006f7fd4222b835e007260c3f946845251cad08378d5570c5073cb4db78fedcedeea69e04e41dc9129b67915b5d1fbb84efc8b65e6c38cbe0914d223fa13648f0397680a9606edc48a640f945da4bd7cafc6c9a2cbcb6bc01ef8ec4f240e20d92d661b9251804fd8fa89935bed5657048ee48d7cd01c04ef52dd0ed0d1a524b971c7f71c991ba74bc7796353c5b8bf08977e9c9aa28636261d3bdf7244cc2fc2b55c542dcf6194b131a8b6e62ad958598cc9fa2973f5dfe92945acd0e34c04c83601c5a115bdba95ed549e49c49ffa1dc3f4741676fe2616d5bdaa8eb86db5e6042081b8d24c0a9fa205604d1ef58779a668839c667c3e92b2986d9af21f961e815ace0f32cabf0c6fd0b4262a7bd07d32552902003c2f59086dbf1e62c0ad7fe67ea981a47ccc486476b78fbad5c0f77990a6be84b87a4c52b8472eff67d499bfce27865a3e982e044e8b5e088323b920c516ba38e8a82cbdf0a58362ccd8a869954afdcc904fd1c9fd05012f4474abe1f05797e51813c4ae67ec414ab051be9b0e3fde35c12f01a006e64caf1588250a33d22b58dc7dc839fca4531ae4fcfcda9c444f141d838b691e60bbedb70bb4fdeafa61e2045438aa9dc9e156d6cdd30350b316ad363f35785a0d76b0c71ac3ae26adc4d9b19400009e5a5f2ab40d09c5bbdc2a863977bc172a8492a666715d2861b1da156cc6886dd47cd1e3192f50241ec163ea4e14c102212679dd73fa8308910e4b7352815ad88f494f73533717f8d5d2f669128651e991ecba4a54bc760087fe7b5656627a5b59ba2ff12cdf326c29427256165dfcf1f83d89454b66a6569c9fff21887930c4865aff1753cd127f5c8b62487801b43336169a0b6300ec62b1bdaabe7385ee189da822c2438c31861741aa9985f7853c980b823be57f12137c4ca31a8d8dba0c8351fd76ce1d86a81d4dd9ea6f64238cb6fa745a6342b46b266d0da6acad048b83af91d8244ed76053217e9d11c57c171c63ed4f4fb1ecfe3c193fd89b2417332037cf317b28f3ff1493805ac2083fc7c1bef1e723906477edcc8ba8b1f75fd7b3bb31f2382ff728dd5a138b3b82d4f7585e8e521db7cd1495caca137fbdabfee4b5e62ab93908523b72aedf8c60a8f96594a1a4813cf9259fd1b39c8b610962e8f568129b19134a1acad1ca20a2cf922a394755794e5190b3620fc621f37673936b6682a26971ae56130504d1b3e67960d107c2773fa65035eb809b2bd1a7d23eeece7928289873a593a6060b2ee7dbc3e16ea93387ecf2107262281fc52cd6782e1e981bff475fca40a0900462f5da6d39d3b12fbabf16db9fc2fefade060a883b4ea7905b118f1e9c072600a9a909ff3cebe0d2b93cf43e3c17b3522436adbd466310e3b660f36608028ea1a725f2997c3e23119f0bd4bc7a9f5f6b642530988e2c5f65b3b1cdfa93045352f75990ad1ea5c9a3214095b8b0c22068c3ecbcb59ba9c8a1eef9363a71ab45b6e73cd58fe9cc08d27d0d27d84ef19686ce44aa96397ea12aabd6995497f2f72f4a5725e8f52a77c91a2911b237af676b2bac0830d502e3fab38a365f458e407579f8d9e953d89286731deccbcd77bc7e34b21b4e2bcaad661b64a0bb617fbb330f97be5e2d973a4d04bde5e8521dc2581491d7c670e8c19bb7b340ceefc446c5f95f93812ea532b13e5b3fa8721e2b3e3242697c5ee305b27e1acf04163987f4e3b98ea83f0fa32ec66fcf73122eaaac0100ae2fcbf5044c2493e02c6e3d62d1208fd8bfd9f1985277ce7ebec0d27af786b8b922bd3c6bc79a48541a9ad784958d840c947f708373fbaccb925d94d8f3224c518b1d778cfd59fce39d5644dc981be0bcec2536b7f9ea444671d6a784b5779470d5773cde938d799acb86d41c56f4c5ec7694ffeb37c0ff167f0f6a455b7817c5b56ef27adc8c545791f11c3c7f699f957f0908bdd31cc07404544462ebf5fa330cd153a0463e004f9dab1ef82123f3247eaa2f2dbe230a2e3be8c529403b455096780f305f4815899467134ddc3db2f86e8b5a1ebce982256d96d76b048ab7e7715ce032613281619926f600214b02e7fd56d6d87e3e7dcf6251effebf8a97766f81834f56d635062203b6178c65d8b980e18b894c5010b52146133af4018db134b21b55c7c6dbfb1e103e5d47cb4dacc13061bf38ec82f6faf64c7d485c51fd2e29e6b6fc89fc9a718622e27eb072ee99c277efec5cacc3e939e6a7c6e34ef555969e09cfcc138cb2bad079e53b88265c1ce2f2002a4ae141346b9e27b075e9bd2a948807eff8524895943949dff6ec0eed0283e339302aaeadf5cc2fa47c5afa933c91508b1cc0cd86a3fd377e0a4d774c07c25218a70c7f54b79c1b0a7d8050110526b6dc277cc832a2e4b3147a48f3e0d4b36463420849cd11001ca157c4b90c3d6cb623a683af4ac7d1f7cf045324f653c01fa1f734d3a7cceee22d9a46b6e1382fe26c82a54153d3ec4bcbfad669ce3433a3ff43ba559322de3fe0ac58efbb365d0d5b4e042c4e921feae25bae2bb92ebddfc1bd57086c704ecc1fe700aab722870fd7789900828199f24e53acf4b73ef2df5a84dd00fc493ea891cb7a09ae126314c7d5af7cda804f70f83a145dbc9acaa079a0b1618999b5466913b42157354c7c2f1a661f9b2c766dbafd1fc5c8e5227a878b4dd88b383a4a5661d1d9abfbc04c5f3a31105e990a1801a479df7db039175bc4bf6b23215b71a16ca79eb176e1300aea585599b14bf67812c30bdf7833ff218d289b2af129934a3541e5193ff50972d8775a24613b2bc0861281572b9aadc2ac3fccfdf8baab395a5d7d5fbfe60f02a5fdc6771021cb4b2a6e484a9ed83a3954cc095279a6f8d4faeeb34b64c381fdf2aea4a1ae0840f3275407b7a0bd94b0162d12b5048b258ea59b450d0b3de6237522d627ae40bed30c359abdfbff465a39d4b590328a8838b53e214bff2bdc6c4444dd52e6dcc87de1f57e0a36b5d3467ec685cbf2c76e3a2767fadcfe067b3d57d3f19a3f33c69e546c0f76ffdc9bc0f31dd260daec78eb64d7ab13046e43b978e467316f60a49884e88b60ead357eb3f88f889b2a607b2b2056abb0aa39f10b25e66e0cac28db971a9f5947fde1faa56c602536d27b8b1f79cc5419f0cf4eeb17cedba6b7cad05e764b9bcc8a6040e862f95b463a3f01c59fb7da9cab43f6a604f2d576bf603c5b3c129ae24b637cf4b2b2d8d6ad37c7bb4b42336d9c84c16783a2c8d045a4b4764ddc91f9800c49274b8af13dd753a785c0d8bf98c1a711daf74bdf3c518ec8f44c9678ad457d6fe9be8479d34c9d5079c820ead88a1de80b589c795d16201dc5b090744def395df426095d0261596fa6b41a18022f783e20fdada995ab16d343c159b1d92f08cde56db2aa08c233a4b77acb1c959d97c7aa01818d62e3eff7df5db910d4acea08e35a55ca685bee4bc02dbdbbb5dab121a82cd668a1f8aa4b9373b2eb187f29aceea215fbc6438199a8c176c95eef24c5eb3d3fbba89d6b066b48ab2aeda2441c4a844b5e6f3e7c7a60160b1e9e5d71174f2da2fe513ac30391b105b423010fef13d17eff086a46a9734e81ff35dabd60f14e7a508f4544e96a28a288058c519299c38b852254a319fdd964317435de07a12cfc13eb65fc688e8a87a14d538825a59593638cd3d3ee7c7848206960614d80bc06e5a89216c1f1d907a8df4c2238871c0b7c772e0f200fb82af21485e570269b9354a8c2ebe67d3595577d49ed15877c6b1fee71e4ca59e906c0f674381a6700da43674e48a768a9146d5236cbe10cf580fc04ae24e530f685dab3f39da7a9ce5019728e1a430fcb872c166b184f08579cfedef9e849f1af129b93be52cb620ba58b69f89a7c583c3913d0b55bf3cd6bef3a703c88746a2cd50058191662838dfc9625b81cb0e7a99697ed62332706ed0be0a4059a054f5ccc9ddc3acc796f1f534c16237cb433a99183c114c825a20ee42062d4c70f40677780c5247a43d6d742e7f7b57d7b79d1330d160e47e2db36840295525b0b64a866ebd1698b499b32946ddb1579b9021ca9e326a64f9ba31255479bc67fb64666afefa3a7bf5da90b29d4bf4ec51a995796b0a33272e65341e76b2af1dadb119c49da911ec18a32a04cf92a493e913284a3f7fb4edbc8354e1c787b352cc56b4ec972d318e86f157e622c3850939eadc60c0723a5a313f2d0fb3bff4990876336b9024f827ec5e1d7317645bd34361064fbc40f9781b250cde57451b85c64ba781d5dc3d215e0a129c369ee3b78e501d9e2249bb31b8f6b9974b89cb323c87d2a082749a0b08dadba43c599341849f6e4ccfcea6d629749219a7cb72bb9dd90cee72ca85166df163a28e88a040e94a6e4825f5e39b40267854652934c1598e52c703ab8799387fde020120744661f424f878e65918d3680fcf1187edeb653e1339a7e2d68e9e444304ee6a922b032e19b34a6dd1c9a6371223c7b254609bf0c187462010124c785e3c9cbf658afdf34947518a8a8fe87f7c7e7d571e4e233961a774d120e9e7e63310f5c81c424b064c7454d1b92e35f8c621a99bf967f32946c470621df72273083d8be0faf67714606ed79671cd31c164e22885ec5f5a2615bcf88b7dc858570853f1320e8e74ed635f93383278b4503eab847c466f9ceb8ba2eb982409c0ca2a3460a83cb47b25d5e2c10abca17c69f2f97b89efd59f60043dbccdae0c2beca599adcbfd5806e4b78937b4c49b7b213c5ee4225452a9febb88b35bb8485814008d9229e52dd0cb18594e03e11ead38fbffd7ab8eaa28e5d330038a35d7d2f18df041e25529ec2e0d49a0854bc67f42021634f8b82895eaad19a8fe7ef76210f7f54e675509e90089c7916c2f94cd63fc71677281175a3cc3da1c51688a0eae2ca4e39f78ca05391e32ac88c66b64a772f190bc384ad35f513cdc509295a9ff185947ed53317696bd60c865e8941e2b370ceee68d9559a642beb3a92dc4f6462f43b5c9ab44b7f8dc1a09d29f3b8ba7424c999518515f668714e6cf8de5575cefe682fbe0cff3bdfb9ca559fbef26145fce98afa1dc9c8bada41d5e3f690e8e61999696b8e178d9591684819426246ad0c43c71d4174d50e4072f4fd0e60bf789092e46ad4c3a94da2da4ca38b6c3c78af3cdfba2e67931c28eabbd1b9752afd4b4f4986c286729787a59c8b65e3d766b368cbc32b3587013bb4313b6dae577866f6a4900500eb1610a56d3103b389458f9fc4496ecb6b2afc3f9d109b14ac44fe199bc68e3d20ad9dc84233e53d36621be896ef3817694d5132461d04298433a051f311bc4b0ba2a66b45a320e3e93779c6ee3f5671a3cd3c83f88d557184b4bac7d58eb1bd1e9a51111ca1ab7e0c25fbbf86697176344d33593815fdf77c45ff9c75465d36efd4eb86ffc9fff147406d91477580ffb8c7d61a0ce286b8781f8e394548219cb75232d684f817b6ef9fbc82d21fd5033c4bb86673894b28b17e8b64647efe331072ab43fa50f2193e978ed5a24c3decfbb599479a5a60d1ea41592a163d2670e1f3686a379e3cfa38172c3debacf44e4da69abdaaf5dfb9c1dd2ebb17a199f0f473a5033e3a9c434bcf456f5a4440dc03e4ea4b4242d9f2c1190070351e0cc5c1841b1db4b0b25d61e6ec9f24e054ceb5c0789d9d7c83d47f0278325786bf0d9b0a8bf00ea7d792c8057d0b12cb6153338f1191675e3c0dd4f35cf856289527cdc088378444d2e9948e83652a4e901c67a68455439363708bb83ad92abe8feae644078554b3f1273ef55594722bfae6007e9b68dbdf85446fbf3b633ce1743c737821081cf06daceb673a9b630723d8ad317448e3debec4ff8bcd58fc36f33895adf2f2663229ed7e7652d26ac78d2e01cd2808604397975916dc923442026bcd5b6aeebecdd5bb71182fd7a6fd818764bcc2b6ee9c39b90129fa828a5f137cf11a9d9fe77ec700f0d64c947114eba3b1383bd3605dd928426da574a8c15c58d5228f2ed0a772101c6b5506b8cb7eb7fa641dfcd0c0ac64e69ca12aef38cf4e34484ac528f8c18c60e26eafeedf6df790bc8f1eff83940a7fb7eea48383f2ab3d05ab04c1496ded8fc90ff94622a50e6e6ac85cae5b62e31ba2042862cdb92b6a0ac1bccf5d166ad926a8ec73ca8fab34deee26e5436633bfa38523e69009c771270feabb2df83f9b712a716cfa61ec06f45821438a4d410ee5da4fc56fc7b528061dd9c7b156d8dc2d7f11633d903a32db9942b79a81cd912dc9a96a99ba58a17fc31dfc4af41bc111325f05793708ceb9b7a3fd871f9084abee611482e576facfae6cd64ea7e547ef9902bf238343033994ca95a04cc6280680fdbdae00f903e7cdd3809ba8828a16f95d226d49fc2f6b75412ab241d8704d3f34b7afc10011fec6b8f0d2c2827327029dd0bb20605873e90c617fd1515ef5640823772e1afea19d9dafb690a04025956f057cc42628e3534d718c4035b3ee6f9857955cec7ca81e28842fd3d7050c1b86cdb98311ba3df250d88ffd896877cf7bf688d6626ada13706e936b7e2805203cd9c90348342db7eabf47aaac65f8f65c0e212fbdf665cfe284d8ecff06f7a7b54b261b04c47cd0f02bdaa3409de474355b08525d4f2d74f6b3361c136ebb47a0d33488de52c62d9cfd700558ed958e5f0f7c03f523ef8b6f5b3be2ed00d98ed17d97bbae3b12c937577ab8d17c5577160265ce9cd3594b479cecd996083c5d8472ff9a781ab6828f8f05083084a9864d2c7b284a303c73edcaeefeaabf360ce13642ec92a1cf137d3909f810705ad0e2d0b23b9ecb24b3cb0ecf4efbe18b279d31b3d1918dfc8929b47f3f62d06da11b5e9aff264a8a50b814f9cd6a74127184edc9395fc7a6dbf7158e6f3c8f6f7e6032d19e05da3f4fcece118b5be4303fc98eb9abec1edcb7a44735dafa90681584ecdc8f3c338b7d38f5d8c037e1ca00a46bc111954831731e3e482030603c306fb61aae532fa6a48bd393a1cebc5553ce24083ae7f09f387e0c62fefe8387ce5889e1e301abbed890be7b6c08effc220c4c0e88cdcf254d14459751333057bbdc515c293f1a5bfb6ad017e94fd0d6c572a082c43cb3a4f9c10f053372b3432a754bdab917d13ca649e84299ff0b26117aa0dbb68811c6c258f1e30c39e92969584eb1cf123561b1b9a47fbcfeddfe8aaed66c81526efa0df780a4247925204ca76c9593f50eb1bd8e951897280e5e9edc2262566feb8f0f1e5100ef581255d5e9573864ae8c41d8cfdcb24bd012144d279fed9fda94bb8b51027f122322e0abb5942f2a4d09cca9ad6990249f16a01de92bcc67e6ffb453be8a0eec90f2c0f2363f52f258184e25d42e84390ddc7caabbd8298149943cfc3724738072182e627ddbab669a922f0a0e436bc74dc1dee67274d58eae0e90e4700e95ca085fb6f31364d8d4de550142412a9b9b993f0ba5419a5acb3ed72d6ef2ccda479876fd6fb25a1ad75810bcf1d7ba72386d165fa46e798aa23dff9a59ab3b6e61c3ba7f21604de64c94fe5b7b5a51cfbe8b414cd282e53bb74eabced5c105d0b37b0265c656e9af0f48b716c8838d6a6506661477762ebace206f1f4faf287881db2bb0a9f3f97ffe4cf55972715d57d106067bbc5ae497e2faaae81708136584318f425d0d572894d18f6d6954296b0e146b4bd372238d7209509466b6a7c53c4a6dd61d8a80ea626d9ec76cda79e095f525598082ffb66fdafa5c6d0c7fe63de7eb112ea30a1aa2a22c1633a96d3fc8b45f90d04790b9515f75ae733d13ab2731f4df30f1daed757d38c7c3c246a2449f728050ef4ca083877ba8a615e00d289f59a74a1d95e5073e1846aaa577799e06ad8f19a524f8e3b5a86b9d639caf1ca7c4c4ed066f5e882aa95724b206b8d7d78d29933c49bc778036f974743de3c05f087f3207f5397515a85495c220029af05c11ef603e4ea4b3ca7021a625ff3ab896b80e90dd0f5ef5e4ecf0d516afceccdd30e15b7b2879670211ce947bb36535488afd6f5d4552eeda3ba0459ce3b0bbb479b561b0dee3efb871e03031e2c368bb6686727ff542695d2a9d8da4b9ac9cd50fa9205ee5bab8242daeb60d5e1b38ae5846f07bbc4b3e66ec2977524d5449628683630b2864c651ae5d720ce57cc50c974962597f9f225840540a1c9e7823c4331c5d94f6f1136500c32f69426a88d8792d742c744c80f35c24a59db70eb18edad53b373b1ef4834e01a5b85830f44c60803a8916f5bae001d1c976107cbdcd21d0c1fdda6604f9440e33210ccf1f9bd633dcca08d963d1d76929cca1ed24a36e0abbe93c9e00490a9923c4455df7831137d4b7360aa1f9b957f0e789f937fd19b0352d0b871eba6a0625502f8901e6d5e0d2d64d388e141e825dfe48808caaa877ae5b2251818110a438b87b55ebdaf8d0f7a05cd674f59c4be08aec90c145b2c895020b65d0fb55e56ff36dbc60c9f56f423c7af4499d0a1f71708f9e0da88a33d04619dcfb14cb37298cd3feca9a9eb063b24695d87756d70866bc4a7b79991b9295b437e388b41a285698f14a87868df74d0dbb5be2da33de581b3e977dcd3b21082d47f4156aca5900f1f2ab1d2b7f002a0c6941564a768d79d5b14c6ad23f1edd4fd6cec63886d40986b3bb69279fbb8230704f81bec6cdc473bf66159bdd5e98669e106d0caa0cb2e3bfa2f94e19a83333c55dc5ccabbeaa93d44e9b96fce7e2aca00497a9f1adbcb533cefb602b62ee15999c7e79c3adae3541730d957e3277a63eea13fd1691c56dc7c7d5c7b107c8db529e26895c09e92b16a2913f22d5f18095494605e50a452c9580d0103bb89196e1e1c97f99a5214dd9d5f7d185aece07cdea2a60b44214f2912fbd1ea9f886ed9d8e1f3f10a115363f5fc94fc2b83a5efbfcc1ac9f2dd6fc010dd633df8c2d5cd7be01cd8c3a020c819647528f6b16f282c1a5f83b793cb255c24eacbd6f2da326ddae1fe6fee6c3b9758191a2420a27fdc38fd8c260d7f80244d92219be8b242da8b90422957e1de5db76819b41c936c8f283c238698a2153b9fe4078081604a309f284c03c3aa7640bf1480b77c7add84f74eb2524a8b13967e4c010fee1184a3b16956828f7c0e78e984c8d2c9ccf2018fee64b70f026ad14b2ae1aa4870dcaa67ed6f1d4a16732252565fbfc05d00ce8fb03b19915a0aa8d41073037cdb108091bfa9c13ad65fbdb02f61cc1fe4e7ba3ead608ee4939d7e5410f5a279cc99a45f265e50be22e7ade0eb245ac5b8f9158146ac6110ed99d904310363c0d74159a78cbf1d71b4ebbcf95cb05f1eeb8934e92158915d97a4e33d7f74f5320a1f2737c02b71290b7894f829fce5e40451f1d2a7ad4f7a7057eb264b945c3e9ae1583bf6530ac2976046f78839882e178129909915846ceed5e2f67d146f5027107cb2472697b464e4c6f5d722fccaea303ca5904e4854a717dfc426105a9e01d7e5f197893d8447ef6c3256be85f26d608f1579b86cfe1e729be8a0771e3b34852c3a176915f8e437741280e07e44b9620dc05893f146c4b6f6eaf5490c7e4bafaa5bd340ab2512b37f9acf29d31546eba2cbf5c78f4b482d70065d114eb91135006b0859814b5cbd1077e331860f3055bf232e512f6eb9cdb037fa325e40bc14908523228a7bc1ea10954e102d1d9cfcd90412a571270c6101de3ec48dbe420b01a6a9afaf15e4a34b4a927972ab9eabea98138d27a9fb99d0eb8dc27eddda7ebe8874244d124397f10d053592363b2defa3ddb4d8506ccb30a705bca83bd21578a0f7feb51c418c15c1a7c6df2857126840a3d73eeab99c56fa9032d7b240a9a01a7f98ab7d045531872bc63e09ba95daabfadbc8c19bff1c54a244abee4cf8eef39ad6e8d57973a12eaa82aaf974555023955e198788b682183a711753fd9ddd1c280298a803852329d340af854b49d6cf652487ffc3ed29a11a9fe332cf502caea9f3ddb5a0af5d148f97efc0f7f6fdc209ad9d8880c4d6cd4be92b568e605e2de4484a25931a57d25b2da1b563665b0383a6996abe26f5245015dfd026bc3be1ec236880d2f3fd31e35e0d2332c107a99853effad2031384ad0141296b864d361f08347dd2a29e278e25118c30da210a6e0ec48ec1c06360abcd69ce08666433aac59f986890709516eb9f958c1248f722f1733dd0c03b9768f5c4de244a13fea7080d81da5a09a459d53cb5e63322088f560c73020294ceaa1f913e5b52306ff10cd7505f64e7aaec7b832f9cdfd8f76955d05263168d6509e563fe19e657b71f2cc9989d69eaa836e7a066e22497117390b6e94936e7f4556aa21193cc68136e77a43af034d54c102fd6788da810ad69b6ea3001c38a5f64420955be4305916d539d137c92d08d2c0dfa2e9c8af8bae8d448e07d54007394ad29e525aa15e3bc17dc766060e9d3cb3df53ae3de89617731e5100c58f517bff7e5c4764c44463609fe778cf58eb07b9745c6711fffa860b487af1e59bd126967f4d30f4d807545a1940503211427e5587c5089781c38241128bfaa48ce535deb631509697dae57f9a2d397b23485eb49c0dd422fa52092c0776bb87a41d8107610466d9332c6975d21166fd588cb9df34a753bade3bbe39056eb139d551a72c030c39ffc5a4d687e7d0bca8312a6bc60e7efe1ec239763085d33489dec110600c9c02821f5c44d741757ea02b6f4d68e7a16401b3670761756784d4f0dc574f639d061e8c4e23acec42b16a5bbe33ef678105945f241179fbf8195a96068e7a2adb6bcb0d0e5702e4c4607074f9459556a2277d0af6018214bcc5f96b50fdac7ab939e64e3fa658eecb74e9d00550b3a6ebc024ecaa6bb87617c7fad7dee426a895709580db1631ed48077ad436a429ab7d8bcf425b173529b20fda44f283169f3898dbc217ecfd9aafaf1bdf066e22a09aa76aaa52be09d49443eea6531454f69603eee35534f550613121fc414920a3e9aa012b32d576decd19ca01cbfa489be24a2b926413c00f455aa6eddec2dc4c3984a87bca68d54bb45983578484c429aebd8ff112d150f75c80ddf4220e1a21bb96f3bb1da302021b00ad69d16e035f99a73cbdb94e388fd928299593027d05b21a70688ee82643c880b5357e55effab40b5fdab63c47df67b6fcb6f1738baac489483b9844ce9604e6a709313587ed2b838396ad606413c32cecc3401a1cfcd85b03bc169b5c292feea29e463872e66a434944a70939da067ac553c930bb38fed4794a250d6860e53ae37ff4cf881b4afd7e93cff8c5f66bbc5e77fc575a85781c311619f1578c2d66ffd3fda1291db28775778b05d3ac6748068d94a5c239fad9a4f874ba1acd0c7831b221c34be4ee6898ff934a2066e0dd7668b89990b741886bca1c6973310cabe0bc928b43c557da931fef5a48ffe8bf8afb823fb84f9bfcdc3c0fab1f88b69b18f7cac7bc58bc813448b595bac8aaea9a4e90fd0c3e468cc8dfa5680af016de138240a8b2561be6640e616172c1ce5b0388a41f1befc5c02ebe7eb8c5f64f8eafed00b15bd9a9790e21a9b5d33301cd7a46ae1065ea179514e2498f4425b7fb3aa30230f78cd452e0e777d97013757479b3f160c19448e3cf4d4444989c08718d91d3a5943e739d2ab10048abc141b573d205d53dcea0a44761a32cf38223cc47a0f13df83a67aed543bb8cccaa2a8012909728d6aadfeb912a05367d844ce1102a539559f1e0b5502150bad7a2b07a0b35ac894643b66a7e213bdb8c49ca92be32183733838a19db6fc09322ff2dea7ce5893ff9455ca9c10a41e6b13d575cda5e91ecf5a2eacc74a13233211ab237b22ab5eba1b512e88e4885184af4de9538b0837b67411abf518c1f9ac4efe8e92a2df362d9c994eea2179f43aea986b04b7aa20afa743d3263e324bdb4607eb8c3de361ad9e2a2f05c73418d013fb87b6d9a31c7daafd063091d4e2a69dd92ced528ca73dec16faddb4f9fb41d0f22f499de9a7cc31ca1b6d3adc9e7c15f65e9147132adfc9022e2c290edae9c3aacd8d6f165ce9960a71e40cbf84aa3ad85b2ce993cef49ea91c903c3cc456e4872693d7e75f1f3e71d6d65eb9e5bb5f6c584c508c357bc057b9aac85090f731a00d7a33bf67aa1085c2f52449938526923eb897634317d16eb7f0329bd465df3ee63d982546543823d9b980b323d4d59ed72d0fdf383d64e4f8529a73f463f361f61a90410c252dfac193c77d46ee07dac6ca86ae5bf258c34c16cfb03ce0423b897c9f63bac60f0e4ff145f2285dd20254aaefde2eebaea97d744d30108f38b2e19ccb7c807e3d37f350a6a2eaac63c60582d5643b086f46b0e02c5324ee3fe2fc40d9a48edd049f88ddf1e02117384b0c47249868739ee01109977fcdc2d199243a9d92758b6448674c9543704972ab7b2644b7e4df19d1d6efe29d5b6510da5197832717ccc784cbf7b12527ba9970565ad5609daea40889b5e19a866b9a5b5fb065e326a3c67b8c6f4827fc3cfc1d68a53c1848f96d84a6ee43e7ea83ba060c75e26e3ab709a822fa76abcdc934a5ef7fc0c6fac5cbdd8f9e3ffa9594637bcda9af116a56945cfebf1b5877a7e5f2193f2e3754e5f2bbc631c8cba26e788d72fcc53a813ee13a2e9ba79ab66c885f4cc1dd9b343d005c7c11bb58e6a8b6c1951addfcbe0a71b955c5d3201f89523e3f6971060682be1270aa5c21ac733cd2df33688c2669a3a6f3dd395c1f489154e871dff094a031c31d0163ac8277092f1a9226c55f79f69ff802830615808c5835e528b6785a17683c2d940218110b7b113167a527f71198adf5ff9a715ff37796bfa1160cd73a8e8c56b030267c3c0d488051ce5012565aea8501d5ee0913d3580ad5b7f52aafd8f60cf8c86fbf236e78b8e006541600ff9bbd353cd3f79affd0f6de9f4018c37afa15215dcc6b7cdde76ec2b7704359789b86a39546497f8555448ebb0489c6c06b09f50a458031ce8850f4705d7bce07d4c49b6bb67f9d60097bbd5a9f868885825726ec4005e066ed2ed06288a341c5b91e1c8e4a4f248276c51bb27bafcabc389221a3c130fc010ac930e3e7de5f1d10eeac91697a634ff8720c5a6b6dc5ebf813c2d5af255badc7f97cee5a014a4f0013b94b4de99420f66c5bce8e6e3eab9c5ddaab37a053ac6636b2a299de66ee7fa3bb56a76f64c2ed2844efcd22a129bbc571533521e0f6b086fabfb556c96207b1649355f277f44de1af80e03fffa84753727cb16a2db4fc60f52c2b422e67989e03a426dffded201f9faae68dddfd7fdd42dd6d5fc8c6fe139e73809f388d14c11a71ae2854d386de6979917e37122bcb72ae81972e3f6da8a54377a43cb6e343fda42a382e4400d8e05cdb503263270b2e2c063cdde3716429eecc949c9aea8bbd2d77873cd188b861a950c38e844920a91ad3fb19b0ec5aeae88a807446d1927fbe12d53c7e81529f16e89606998a5a163b4b2b9a5286b08d4ee74b149899f4774f332486446b1136416b6469dc8697fed93861dcad9280f85cb5a291c585a97d9beafa9790713a242a6f867a1a4c240941ba228157c38b8df665b98dd44ea619a25a90f0c1c4c3e4b4dc194836789a57453e702f1aaa1aee2098fd2421abdc6c25fadc71adc4cc3a270957f1a5a7d29bb890459a770bbee2863918e1db24561a2de9bee91cfb00b3b15ec598f13346f7f5f65e0c1e01d73bf536e36f854063a08555a4e54ce66f7fe0940b8d37427a84595cd57778ec10cde0bc34d479f318313bef6dd66c93a39929795e89a8214e24c58e63671d393722dc07a2bf606a9627236d7bad210900066547d04c0bfa46ca1d8b66575eef102f24c59bc6bb1fcdac542114880382b5aa4f9202ae39ca320ccbd9eaecbc162c071503862a5e42c5fba99d9db22177cf6b07bcfcbea0b6bc2a3cfb58396ee8c29db3b1ad07ef6b98fa41685249797a2a2591fe288bc1a7cc2162e492b9e0d124a923b33dcf6ba79c8affbf6a0081e15aaa5704ba77b338cc90308b94072e474d082f7c9517a2c779070a71e929a13fce2ba310fb549df52a742183e945ad659274c544a3f7b60d23ea4eb97ee3b6b0b13f26b84cbd3feabbbab2a98924636695dc78c82354a01fef69fa3c337391f366547429d0746a80c6bae4e2eefe8ae0de80fe96e9bfab4e4843000bcd3d765492818505031b83aee46bbbe575fb11dcd0cdb0f48dd2f72daf8099b1206e9e670b8436c8cda505760814c1185c441bb4d8f90d4b2d23f7780940be62c058b5933a30c6c0c6cbfcb142c7837eec6a7dc90aa237291167f34977f3b028bbe35c6cadcd5a9aabb98a3525b313b2adc5440592fa74d4698e87a0288a702a5ebea8dceb9d7974532d653adaaf84f3a2222d2b9dabae68791b2238ed4247002a01a121d847908b9b56571f4bfa7244f2762cfcb31ef488d25a88ae78a740186ee2c49423006f7110adbd76f04b7794814de6289ed3f79f8c9551fa9454a92a3c888246a20ec9d5553f096525beb7fd5c963614464f8db08fd06831bdc096bea5a4bf6887bfb8de8edf404f18085511f820ac65415a32b6592e8c9fda275ebe75755389d4e6381149f67dd9b2affb339c7b096ce98f8295df92d22c1e201c1cd8f48c9c85dba8c9abb36c3513b7570a962ac44f2db2c09c6bbb211430212f86a184abd743374c44eeccd0c0702998f5f329881588d153949b96b4e92f74ba2b2922625ba9b6cd3556f2fdf385abf59fe774b370c41b435fe04a9cf13e982591e63c5cae5f4db7f340b05ea0d9dc26991cded965780fc02b21043cc68e249a74459eb507a3e0f92cf97bf07e81e8e90296dc40bd62e59c336453cb4e2e9689c2616d7e69f42169325b43c50e381d7e4de5106145143ccc0c9fa5d5050f45249a9d3d3b8b04b33ecf89f483c3dfc52c04cc2e6de58e75ea891019bb8ffcf0e36143436494211394b13519e283cdc4baa99aa7c67fc4c1b62ac6c22ea180f402eca53de7a6b1d5eb6239cf327874a5f6a4dee981a41f84911c2f48b835ed30feaa7062a4398448a8d08b1ed2284df1323d314beb13809d00cbde8a331607341cf6199af04204009364cbeabaec1196c2a639b9011155272f00e1064faf0aede25da9a9133395b658ade384f2a7aa147f1f341ff683bf576b95416e113805849c10447acabea48b06978f2f19da6101471b9005b1ece599a7b3a9fe4e28bc8bd84e318bc5793bf69333babfdffc3c177a8077514c25e460f8a3340834ff9cd3eaef48bf2a4116ae551300e08b3292c17ff4503296f400df1930de1ec2596f9469f6fd22107692658a2dd364754435883d388f3b92701396e8922c7a4b59c38fb03477fc0508f76dd158038d8df1d53ba2945f61f63042a9e7f8f718f85956ff5e016873894fe446af53ad00d38c69192057090fad5186f3bc90715942f9e7ded6fb9a6291b8f4a80c5dd2dedcebb95c2b5e10d656459c29b12bccd77b6c3930c0e06071053dd8d2b35c1a3054a573e3bcb56c45c2774cb9346e289046273156866e556741af74bfd59b82a3769f4d93d143695079c035ab5890a6721bd25c89d8592cc998d4d472860dfb8f96ee232fea84fb4c36a5838753aebfd2c5904bb529527831cc4ff0ba3f2734d4980320288e58a6f9676c3a21fea8485de3987ac32f169e84985b96985b491f54752a48c1c4dd392baeff02375896c08a6a18ce7567986513265505049ab7e381bd58108d9a5f02bb629ea00b03c2ccd5216cd0778e33dbfb9916f9e7a0214a96104cc6e5a251380e0f607a73c63d045823a6313b29ef729c85213afd77bbe0daa4f8f282705cc317f186e25709026dd161644189fed39fb97299f7a709e659aec2470bb71d7aa2057da2055c5c9e4c891ef75c709950510a5c0e7617a767e7b949260a32c7228be3d981ab22f0792ee971d78b59c8ad083c3617ddaf853494242ae3cd5e0f89915c5ff633af1ca4412dc66dc49f1f93782362bc2d353bc7de6feadd529299e6eb6d1e66797fd93470075d2893f7759be0ba3ab2ab454509160f2385254d8fd8224d592e8347473dd29ccae11d5365f996c38b63437184cb3ab903a5a5c01fcc40fc06d631ab26485ed77397bdb3ce2b725c15ccb6a2bcce67ebcb05f5a2f87ad2064f104fed5e6af99f1570a88754c204a30c239b9e145c0210515aab1dd177caaa158fab4e77e34837401266c7dd31dd819a557113996d35457e7ea94ce3ca994068a534c3214ee78dd64a080414b93b225a516315ba6679cbd105a653699e0f0e10ba2bb6ae60df2e900362fce954f013df1e70e7cd5c2abbdb194d8a27fbd1cf078ae9d1933d8e097989b61c0ec552222bcc10a389ef6ebec1a80da0e094812dfd3ad722eb534f2a71239b817ca086f93827a0a68ab1f132bde3ede1dfa6dafcd33d8778f4cf933babf804b4ec797d3373480fb2d1202f459d0d01fe6fe064b02001189b8f9b477b2f47bd595f1469203bedb040412f5a4d439958b96361848eaf734ac6c4eea5aa59eab58297a78fa267a225fe235b19f423988a0e8fc6fd55ff56f6de3c70df605831f8109b34b4c1bdbeb22ba3579e6bc87827144c463fc5c261c602c5e53313b00528f637ea842e7c2d8e6a5045da574f15b9dbf8dc00125e326665d2746117b861d5a67999c1cad308e9a6a07ad0d6ea11253374890dbf6f118ce9f8728bdf1d171631363a0c6f6704c3ce6adc49785e8b8cd3a49947664384888773c6fab05de521d9837f9593d1a7d0966ac66ada85150312f3063afd5caf455e9fd659214de0eafb3c7c17351e59fecec083ed0ecd09b797cce2c8d2f288e03e7e0c4eb89426ec3c8057d04b4d375aaf715703e3b825650022d3d2046fdb459e39e3902d2de8a8b132116f6ed4c46e15e5141ea25a79317fe99ed574f1177d1b9788eadde9bc95d27a5624c71c6258e1674824f2dea53fd39acae752cbf88a34870db03beb4a66764266495500981f81a8efd1f5016ca4e6b344992e04f415579c26a7312a3df901ba054f92523222403dc247f8958a8a6b35228d1dd67d36358c7c71a9f2895c44d584e6f4572a951f1c0a0b8300eda4ac246bdeef301b1d2ab18ce1a86309a42bcad29679466169d4230cf8e5ba29488f0bf7a19a354478858819dbc3f15b8c9c93a22f4ccc2a1d069100e9e6d350c576755917f9bb16c4c7342ba1f2e220243c719fcce98aa744118b9b13856a0ae05175de5ac5cdd8ed34fb9fb336d37cf5e766b15a9796efd25f98255f7a37057a9c1f97aa312a4bf5a5c84c9eda34f069eeb951d1d4f60c6ee02624356b02b5cd880c7690dbc3cb752da8ad5b83c77d62d676ef918928c6574503928e832961d2d4da7b83ea404841f64d262dbc25213eaaeb639f029d38b57ae4a73f961e83a309aaf3d10b7c0077be8ed20a203995c8472d779f7f39433f33e134c4b1e7740504a1bfe6a50519c074e5e60cc6c26acb2b9174d8f7d5c0a581f7860e849d6f61ae67b53b0d9dfd9bc8efa07020d56e3b8d33cf9cf4bd922f3485e31ad5edd9d2c5ab0a8bbdbb93e6f1f0c71a105a613c6172d6d6da337b610fcf8f49d69ae61f5d9cd1119a0b3d028f4cbdb9888afb740e727aa6c1eac7b5137f69c102f982b7917185f5ffeaa94f3c0f1de9c377466d2b982043008bccaeb11f0f5dd373950e90e3a61e06eb7bdd7266eef8ab8441f1a0128893f1332dce2e9f98e496068cf695ae681a9e54b8794f24643ad9e9d8b1ee4dbe2d08f3f83a85d5df9555c53ac71a146cd569a10a8f1e3e2df436302c9e1d7d23b1314f9420b7f8583dc5530a76a6598d410200d777ec4f556027bb24af20187facb4c07310e6257b61f96c76f42948c0381bd41c31e515b555c83c5c1f025b24822832a6f07ddbb842a95ddc6fdcacfc2e6933299701f2f27ecc3a534940c426fce2862f416a7e08b29b87994cbad83a856d6819965b6c13413793f7a52a969e823b1133c29bd7fcff06e079f6c320808f56712df885ad99b32bf47aa5e458fd25501ceec41b926d86797176e0035efb9ff081eabb6f03384ce963795cf15672ef8c6282b189beaf8e2dd9c34d346e31a93d800d1cae61102003b66f3fd01e5f291295908b26515e9681c830be96b23949a9864db383dd84dcd81982664005dc913ad8a27f81a2b234ac85d417308e0a50f763ee659e3098d95a8c921335a04ca9ee4667c36fed6c22a9690de825edd512fb1b23fb5039dee72c63a774634b048f266aba5239d34c26cb62cf556aba751ede624090007afcbb4dc4a1dc96a5fa1df8c58153cbd719e85471429f3d68e81a3d24bf4f9999ba19832c44eca512903bd34c562a50e13becf82cb13c4d6bddf1a9000f1621e1a220edeb8865f838628a1ae7fe89c4bfe339273dc70262bd518a32c738eee9be325c4da348a30a8230f31af3ba07009fc36d9de01fde9f5a3cab6302519079459b79c0cd30ef40a0d0b580c15cfe2e97573bc469240253c44ad7df6aba4bf6a911c5e3340a30aa0533f4cc1534caea1ebe9e03bc3aa5277fad330a6e3807cdc7afd0a1774faa066877e9faa7c8a7f49020415dd343a47296f4cb75b0d1404f9be4e62b8b13cd8444113f78a564bf16adabd14a3c6aa7e214fcc4c18882373bc9742af83738452b57fc31d0306f27d61a1855149840a28a8ce8504b80bfa87b150b86f697f4d0d426017aedda96e76fdec2ad901e30c9b7923376a6d6c7cbefeb58cd909aab8df7780925a32ecc4612e755de881537566e40c8fb1efdc774b7d8c6b1748412ba2ed784d840280cee628300c9c45df7b6b9b22f0c9b1ccc89520eb56c986da726a312dd2a53b9faa929e73f6d1ee714fe358acd61198d8233dee44b4165bf77cefbbefa0655454dd566ac37e6d638e4b1596c2c0ead5fce73dd54dc3775bb365275b0d94b032405b6334c9aabceeb685210835a2e49d5290c467cd0b4dbf5a34f1c842d1a38247a578aa921d7302ca43027fb551ee7e5d8ddfd1e2ccd82d441cfeb1a1682af0535ab79879770973d3371e78589fd91baeb2d901f9d7ac1135c42be13e482dbfc99fa590d6e02adf43d54f9d65896aa331177a50b1c11f1435ecb9458ed8ef93163f34c49e8f8d53bb51c5627bdfa466139b3b3901c9872477acab5947105f040ebd8b3a9d28d7a8fe1309fb7f167e90d084770fe4de946d69ee888b58e5416bc47a44d82cb422082414eaa443043ea2f17f9c7ceebb616fcbd1ae46f45b5eea8331d5e8c1b2ca03ab8ea73967d41d978099f84189edb49996357b2b98db304fcc93596631e8e6def0b1df2f5850ccac96c6a531bea9597e09bf8794d7bf49726103375083b8f0e70b4a673ad89f6f8ba6cff91b932914bda29044e2c72ec7686506c815f6c009f9ff9f84f91598f741455bf23f848de55e10dd422186b7771f044ab06af597e73f0614fae75632c8157e4f093828c3d1e255cd953194885172e1177ff25ff6cb9ef703913b3ceb53b356eb0f4259c5cfd2a637365f9384c0c7c0dd46d50e68f3eb7b52d35a7a84a9915ced13d5496e930482215b3c82fb3120a6142afa6ee40617b29005233218e4191291e6aa5e99d192a1033c75865e61b7e6c0547fea5878800de55e03cd7afe9f882973451b5a2945031b38fc1ed470d6f04fe301531936921fc6f012b3581c6cd1bac22b8b9f04b8d7ed97edf5c2ea7eb7ba10e09c458ce4a12e8886cdd296446a7837bdad88deb899beaedcf7a23f2f27ed766e0aa5887feb237c9f2264db2cd4cd0e7de26ca433fddfec3760895dcf8bcc8c002e2cbb052632595b1a9b25b837acdb88f8ade8280a0bf78fa93293f6763e898bb3ebc796fd9700f23dbc390b8875640daed8eda6ff00808465d2b521a68c20ebb27c929138b58f3c4ba704474273f3751feb118047da9508a4abbbae4a358b195dd9a38118cd2f9968e4d7bf2aa57eaab7dc0f389e1baa5b9ecbdba07bd18ff435346e8ccbc92e09eaffbb41f14c7c94122ce0e1f3b4c0f0a94b4babde1b405a09623fac048386165662fa81ff8a40193078e023a0f59d47874f458b352690dedc8d8c3fa743c1877d7e481c931fdf5c1ae440b2182466d42daed6589bd78624da79b209bbe7cf4e7ff95982664a7d7138fc9556fdd60eb6b6e75b95367e91e48a3eb7858497a49cac1830cf64c9c0a7236fa47956613373b8b41991b27cd4d4c8b4661fd9da807b700641e2ae7b2c1ecf9408b5585d443bca00d7d5c309ee027c3991ee5b865ef4ece061e6b5def0634361112483a007e3ea9155989ad7ab68e0c72b5846875ccaea6c4b292cccd621f207bcb5ae51a44d3addb3b842a49649adf2b07defb19e9fcbcc7e72ed632782825393a495c3632d35a69f827cf0f844653a6e60d4124f1e35782d60103ec35ecdbd8ae295173635aa47fcabf690811f2953afcbed3946e0f3ee40183f5e9b1af84e176e208d992c57a665e8b493bbde3f5f9c6b4bba8e2b13221755e6c9e5dd6191aedb061fd31398f4e6a41e9aa3cc0797407445b6b3ffe811a81f01428066bf050f9a32937211c2877138a695a3917098a66d8ad272fb09dbd83cc2d36fe9de846381dc554b29a058aad81dbb802d62408396cd6522883c9e97c39f9cde23c2ea0b6562511042156dffe395b3b12d22f16d714fec9c5b9d7ae54b1ff5ba74189fce3db0ec191e5ba9b1834fa2f2e4347b21c74827d3c97f5f69e6422bbf8443e99250bb0f2075d5cdafdec79134b46abc04e4674e310e687ed58b37d1b92047f1e3a9987f90bc40e55c94dd6f34a5ab76fced865cb5f7217b594b51bf3d1e36a7a720d9ba0a999863cbd39a0a961e36af2ba8b896d278e4d3e48b57662b8679feb9d43fd7c871f9d32ad0328f233ab81d3df5520cffe8f00527cb1cbb67bbb589d69eaa49ad7dea4987ef477b4be5c1c63144038d6924c5020e9dd3cf40a2b2277de05937d3720cea30fca1efbb207f9007fe9baa611a5d2e6a51ec49129ac16029a6e49b157759be9b134ca6c3aa03b450502cc2266f9cf7ff190f5d04a51b185594e9160502f66568957b14d077fc64990d077a8c6ae8fec4101925bf6d72dbe70a791affde7f2bf8c4ccf532ae02296b2adb166a3acb42cb973e9104b1a79cecfca35d29bf61586999fedd76764f96fab87a2e371e2a9f4d59e0d2d289eb14f4ee1e31820fd67f307d27e2dad8e1a92b95f478e2cc01e15b76bd433a00738a00b66836d795d0a5072d8482bd647178c2dcc5f759ebc93a5907aaa499ca14fd4d6aad3faf7c07bd8ec9adf2e2a5b694cb275ac7dbccdf643029d35e401d94eeaba4f12f8988e7c0ac175ef24d7fa2b97d09a7db15ef34ffbd55bc7fc3fc15f24fecef1d2d87903fadaba57603837646bc88c9183a5ad7da34f6e9f2ee490d0a91df3df31fbf241a07b2d154ae68cd9987931431f388101cc9c45fe6abb20f4d1905c9c886db1b8566f87fc3363557e083b8108205278dc832583072d0d6f92ba69fd1e3906315cf0fbf544d8b85068792e0b414a489e7b90767e7457b31ee90b3b9ece7a6731c922f696e24f812359b4d471541fc586b4e53dc016262cf53b0c055b0782ba348931f200b189a5d6d67764e720677fc5d1705a8a2add465df5644e23f1a14c02670d50bb6d8c9255d49eacb8b9089673af88504c7920b7e4563bf73ba6625525d359f7c4820dc6efe94318c175cc0dade7948c2e4c1caebdb97ec7b8a9c5516d580af40ca1265ada1de3c1f44de126150fbebd8b2eea933d5f8e3308ae32ee6c208ac8f72a34a7ac3cbc60d78b3f615001b93a7080db1e5fd198546aabc66dfb879f36de6e262ae3c1f74102c84210873dffd222d9935141d3569c7146ffdab3c37d748db1cf9c5f50506da538154fe75206a795391d5ca5e1035e8586fc127594b63fc508f994e3ba428bff4fc600215d48a2f50e60f77143b6e061dcd2e9e30bfe03bb05480e979c0621e8aa2648ea902ffa2105a9952faa78519678a73be460f257972f3ffa8981d13c484fc38c7cd836a52ba926ae8e62c79507f290bb24d45966f74de350279f26bb9d3a9ab31480d115d9db887cdf1453b16b3c76b691a488766983cf23c61c35e47a4062ab72f2eba427142cc998152b2ccfee65cddf13e1aaf437c2e41fec863e023980cd5c851700c3717ab1d545b1a811da1fe0057b9f3890d3ec67cc31821a717f03de1b56b39f909f6cf3d283f9a266efcafdbe6e64d0a9489029cf3d7f8894c043a6fb35a4c1248654f272ab7625d476113709f42eaba06eb0050c1475214b19fa3ed0a66da549ce5b86bb261719b9951d9aaac4b8b34a2ce8dfcb847e480e85ef1f6ce7bdce7f3360ab580e33b942253076a7a366906f72a8b037d5b951659c9af42a10bfe379a6174f16daed9df5d8cb41cbc13ee64c066c0ce1f744496fa34606663fbf549c3fa6d167a1edc128b5ae2326329709111caa803d5558eb5ce89dc502d6015b83011047e2eb89e287d5132c8a6e6e7564c191112324c2bcf371b19ee3c3cb047d6b302bc011d49b9283f7c010ec9fcedd17d8ba505244190dfed7cb72e4fcf40ddc2105612c1bac1c9d579d91bf66ec885f2315e3fc967b1850fe4c1c6bc83469c33e6ac596cddccd4aaa082e52e05b66d1dc28e078113994cf4282d25f5ef6fca526ec14fed928a1060599c9258118ec497306b0eb3b699976f6298a2b986fe5ee70c01702f2da68f2ade789a9adbf82c358f39f73cbb750105dabbc34fa037692f75bbc655088447e111d5a1ee95905a144da5de0ca6d76c7c6a8e58b9ccdb580e3ce95cfe4fe2d5f561e6b8b4cf045c884347efbf114dc04ddcc08a8cc34dee7e469591a077870d50974aec23789888c67d1fa606db6e99d3ca828337aaede464118623514a32c439eb65b756ed3056908b8ade798dc1653b73dcee6c0e9da4fa1e52e959665d412b66a6d5813bb6a8b09dfd8996e2d719ed68c897821ada69ac859df33dc80b0ed74bc23593360572e049d9e3daca91bd279036a963b644f27a1afbb211ca75b42517681e1d26ae1c3b983886df15aca86a94084f31ae792b04470aca58dec461b20e5cc2caf8410f04fcf02415881485afafb7264695462ceeda5c0ed12eb3445551812ac25d5605321b2db844a1ff1fd1836d10b18f066e8faadaa4f5961bcc5b112a07af3b74425ed8cc983951839955910c19651d878f86cac3f199190cba1da0c8ae2be8b9e9e6f92e35efef7246464ca6889c70a271d8cc6fbf01e1e1ff491e026f19c614934ad6c25f6d8d4cccd596e6f9baca436fc9b957ac6e8c8cc538c10911b058b90a17a39ef6063c0dfa207a58324ee7f1bf5c9b5ff5220d4bb8a1ca628f3e47fb3c20b9b86f6e7981fc00905d5987e508123bff0f029ada3269c2f4b10eac55a693f93a892e6e71ee0fceff09b15c32cc6318046575998a81e1b539cb027373559053d429f61d0ae8d69c3c18dfd75fc7dfb8402639a1b6605d1bfab326ded02a0920dab9c51fc0f23de848c68a590a1eeff311ee511360a14b4a213050a4b264e1070525ea20460b3d87e4a6e100ecf91a41d6dd4a0e8ced62d1ec35de041dde83c7d661e1aa13fe5ab848de4bc98699747cb72c9c47b1babb1bde6b279c1d1e826451ff7c19b0c70fd7832938075fe6122c64c8308712746a355ea3d2b238d38b7a5b269de9822a394b5b548753d5506fc45cf650402b3308d89977e4a464abb0b7887d977b5d2df782ee655f53861d2abc7cdf3ee4e4343a772c1b9714ea6c5a11e8e0e733e001b606ab50da86ddfc16b438c59bd9e322fad20c373d435fc9d50be9bb63902175f6daacb4b9eb1be08fbea80e4d352cff804ef53faa3a5cb0214ac44dff2fe5d493fdb58ec7e55078d9099e1864670b67635e8d9db83c667a3a6d9f97e4b6f7f8b4e295450c337586a9d5ff356a9e090d3acb16f6a0efb4ea6ec4a1cebce777d6a9c52fddd0353376e5fd4b50e71cc5c11d7b9483d69b6f8222ede5063cff63041b10ffc7c5419649b43174f8be3b0768284a2fbb1e9e09aa421f19d85de38defcea2deb1ab12e12a918ab90519813a84ea0218064c97c64aec6acc3d6d593619e1b3576297a5473821195080589f59bf036d25ab9ea7cef331002cf3d6f2af4bb77b2711c01983fc4976955f1b1673bb9ccaf8657adff371d5fa11e876d5a8ffed4827fee2416fb42ee30df8342e39e4c043629aad25f3bc8cc76f0e4549783a538b6e19cfee59083a34daacd5d477320955e036172cd7d8445751568b3b3ed00b6e617a69dfc7a42d4ceb4076b9eeb0292f7deeefe60191673ba988bf9da53f59c1cc492d41b17ec3dd83109f60c9b90ff0bed095adaa5377755feaae2bdd97ff8991d69151ef925210a02d21f8cdcbeba58272df97d3c14763a3d836db3292a5d27e9385e310343f412fb049f51c055af074c1a1ac217ccf02cbfbd64d0669907bc879135d3ccd1e5d4eb645007d16f5e2bf85107bed2f5efaa805e77373a014b9b2d07b79f690e2fc9cd5f573465fdb4b4ba9b6c8d1818fe22e253e12354ab4f41bdb6af45f16c368b3f80ce799b4132ddb683bb19a830f2fedaeba370877521b912b8ca31421182b537fb078592545e3f92d79ffbec2f8a4d065c4e9efc520e36c5b0cdc258597a7ada265ed9e7a19017f82b8466127ad514703d5699d7c6540a8644e5183d52fca27ffa93d30a680667f055265061686f97210a00c3116a22554d9b01d2f93ebcdc4196c5887b3c49f3a6f23b46efc689162aa4d7fe376ee20176e3424546157882e3bce8629f85e55b03e026a41f56c7da48ceb8c63212dcf2c3cd750f8f00fa477053dd9c6fff70c0a0c2b91a3d4be68d6cb167873c2c2171100fa002d0dfea73c6e30d00e839e1b048800cbc7dce3a2ff359b9c7b0a5cc3b02e19cca42c474911d1ca085c618326212c460fd177df92cf5354cdc6cfc90d0c781c9d83d76b5c3b34f35bdde515c61ac7d1661abe6516ced9bd02911c196c743afc168bfef002506f33a1bfef29b3e2460b0ba2311594abf41e137901bab5c7c312cb008def3db5b22cb9020d906ebb462e83e6af3c3df72c56b161f43be7f6850978b5d2656d0f6191d0f7f3b73c2c13fd4d1abfc7111ff27a9d7c807b206665a653a755dcd29f0bb85d8d31fae7b29f71945b88ac737204aa6209d4878dabd1ec9fb684e5f02a20f08c10ae10179894b34780273d86689765c12b07c81399c73d29ef577c4f682be75c075af0c33df44c78c158062a1b5d9590de82c7b69ccb18a57b58c8a7e83961f78880fe7b732ad04a1e7ec97803d01fa11e4775e725835216b8ff206365009168e0f579a789a9420c24ee9b91add632ebf1b8ecebce6e22785fb45198457aef9ffd234e7def5cdd508011b833245d8d631b836c7ad509bdfc7ded2bd4514bf5bcf94245c65316ffa11f51b62f9691baa468b499b9976ca94e9b81e7069ea9aaea967fcbb2bc4542445acf583ee404cae00f93543a8c6dc0bf18f5d5da4d541c66d6b62b4415c9d5d9becaaaea4c66f41169d7bfb8445c3fd6289fb66ab45d9e8b01753e4215c2de9d7b3afeb78b4eeb1e6f43133f32873a4f26717f599c62e6fa55e9f3217e1291765ba6c02848513675a5c81caa23d655707350399707f2ef5041d687486877e2947b0bfc250498f996f3a28a1dd621638a935c67fd5236a1ae07da76356075b48d77a761f214af69b7550f774e85f89eea6ea9ab480b198d27533a7e5ef0fb286381ed818ccbb41b338bd56103138c17506446e33fe34134674a888a19080bcf6b8a91d777e7f4a7fa06bbcc8a3ec48f8126064cbafdc59424597bde6e93672ce9072f154e88753f38252384dc249acbc992654d138d48c3a48c78ee34533149cb124f579a19080dead21ebc455fbfc2128d148a2bdbdf2d71b0a972badbea68a97b53e85ceaa580469b68c3dd69f4fac5cf9fef5a021761a59f5478ec6946eb64e11d4e7509848c5f2f7e269e1fe3d219d166901a9d82409623f623d6bd078911e6e34af0b0e2057b98a4eb05ad764b4c36c3069b647b0f225188043297f12588d8fcf1f07bd938d521f3e890ef3e443954d2e04773ff03dfd508c4a2e4d5095ade5891f7d71bb327400bb2df34ee3a41237f06dbe755dcafea845b254b089b7e7613b7018ae94ef6ee9dc9560300b00f7c83a696ed997e0701dc0f64c56d8b4b10e31baf0e87dc4ed1c50c7d9f0073ef5c382121975deb237b0e4661d5f9cbfe6f3ac660df7ba11fc29d97341230984864574e2c62f996d63b0ec2a721af80ab195604a7942f42f94cbcbe7937647c05b3b6c808a7ed3d73f33e99a7793549f3768f81f311eb79838d5914880637946c499ff64cc04b98387163ee53834da3e82e68ed276eaf29e592d29237b435d7c9b38724c78341d8f9c962205bd2ef9594597339853c37bb7896ca34a24e934968f5a4179763af6f3e83d21095c27e613882e6ddc52228951c2d6a5061a334263d3ca9ac61ae7de68c8b49ba906d116575627b3fabf33383f3b0aea91935b512a8d2ddf8a73cd93d19dc3442f7ac9c6f9f0a4f3c8f09e08ae96edbee85cf82f969497284aa287763ff6aaa2a99a0873f22d270df266285eaf631d84e70db82bb251c2bf38a8e90f82dbcf93756da3629cfaa15620d2e30251395730b996ea9cf6ce38a28d50ef8747b89cc4136e78190c32d416f83ca3a045ec26ce957bcc5a6550ab04a5225d2ccd6f613a4f3798e473394e0ae2eeef471da168f9d0595d43b6012fccf2855ff01f8befcdb3bae251981475e2a5cc20ed0dbd0864389043b7938d8f14c581eeebec9da9a854805eb454e4f2c4d44111e84bf7c855c224e01de7af00abd6c25fd5e144bfcbe2a844a42f0172c703fd9276f20cb8258c413f5c35156e94b1d05b8d9d5f598913ac997cff233479d608f4367cd977c128e302620b196085e19c5e9430688c0a570d70db9e4e135ad90bdaadcbd5632c3f92bea17a35ec69f6d506922d0a01cade809f24b166be5f2cb942c3c33f28fb78427c8da53570de56a8bad0495b6518f43ad0ff76b48f70fdbbb043706c60d7c0b04b1f5c7de7b8e2d693bd8d57e3989e6337d6b2b72fead2a76b5ebe0661ffd0dace55c8f7817c6555186a15830bc29ae846599d04531da6b74028c53c69c3116aafb02a7fc4e6d8ceff0d61d3dc61a027e0fae91a46cecd65611a9e1d140ac82bcab4a6cfe26377c129823542d514f92a4e72db68a6b7b826d48fcda909cd93f0a66b1f5733979e1af3c50cac40d06672693a68ec9b0378d06a473b589d993e6d84e3237495d9817c4d7916dc4d5c5a82cc136f2423d2197959e6ba113e11daf6d8abebf562b2ac975564c8e7cbd379b3ed7ffb437bbbd3d5577624e5596a2e34aa5a63ef0d625542a9f0fe3bd42677fc47459ad99845fd64aee1fcf59dee781e8708492fdc5b7f3769acadfd0f9836b079f1bd5c916cdaecaf78ae67497a7aeb40d358ef49c4eda7a5ca1fa0b940456a6551335350233619e76f5147a0ffd5bab85c898c7df7e506f09ec8d2f58a9fa3eef9a5008bcccc41af5e04b66904bf203947585ade03c2bcef25a428a1b6c7a9e0c034d2d3c186a306c1d4d5ca22f8e7dbbf26e3502a53a9227b271b3a582cf2eb4116f8e2cf82962779373bbe8721a56defa527d36414a06564c14a21d6de036b5b4ddfa5b6f08add4a11e5dd7457f5198a53af33db2c487cc4ea1e04ed9971cdb26d7d6baabbd6995b4ffd33bf2082ec43067c666d420a9327c727e82d189e1c2b70a08fd5049e03b90fe44c52ad24dc568a328b2e1cf6a03a55e3f778ba440b7497b45bd36d601dd35947d3aa64c2a6fe4d0a10e314caa24f2fc8e3877e00667964b26f3433f521ff66499558c4bc3dfc1d567d875d86e3dedbb76465454b88f09a26eafd1600eaf47af7ef848e0ee417f2b75dca0f36eafead3a2ff269afaecf5586b2271b29024e4c93446548706896c951678d10d82ff035277161a7259cd02b41bd1f0ca1990efd7049ca081e17c897858267c29173954197c5ba4fd90df46343dd44dbbc1717a36bbe949618187c40168b2388955ef26c4c6559d2cce5fd39daba6dc9b176b260324b39d7fbef89a4bbb04017672cb342e63b6d9218caf2322cd46061bc780b0df013438dc4c248be7b2c6f9a6737a6ec8b747a0dbe339f81b7ff5c423c6cb16c0fdfcf661fc914ce5a9819a4d39c3c74eb02d751498d127b88c590ba8e7a1afa1d7d0f5c1b22d1963497d7f09ca60b93b0a731c5ba0e1f1b1a08a131b26c1d3e0f82c85d59cc63aad0a2f8f9c94c4e13d5f1063b7379dd9812e22863fc6776b86245d553131b32c683c6fe5745eb63c06cc82b5b1cb4270b4e5232696d644558ee8ca26b946cac3ff0c421f5463c4ce7ce1a4758c78b1d1de94bbe9669058f844f3cb1b6fc40c380b7a7ca0fc1c22f673f6f55f6bb8ca0d23d044322e40cbcb444e119196a116b942e00190d7b13d651792a26e1b1fad95c04e527a044d8179971fb0288e0701ec36730601bc4d6301cbd94dd526e6db2fe095d494d913551d34f6c2a988b11583f23eca6a4e7efce5e44d3710a007c5b0130e67829a866ff3e4e92635c33421ae278c12691cdf267263d472e92132ec7b8a4fbf9e1905717ca60301294fe586bf74ce1cd5970f354d4ae0534d619a02c988867058c31e477dd8269b229dc49d21ae28fbfea12c6148fea65f70a3b6487567143482ff0885e55eec54f012e58de231a9ed25560faca4269f6bde34faf16237d4364200b01f109096e0c178f6c9e22380c1155df36ba31398223195b4edce9e7676c2bb224b7e74a859dedce18296c357f3a9d631dcf145d33993f3db620a854fd698e2ad01e989ed5cd4c7cbd18b7fb814100678c18ccbe48e1ed9c3ceed03ce8d8c387bcf4b9fec9fa4f9995074816a4cb5f74e4479f974894333dae94c137942a1dc3f9eea9510d94c452a6172eac67b46f0ef4310e70129d5f3c0426460c84432d3c2924dbc500a1d8d4b16f8fd5988948bc4f503119b6bb16f5eefc2dbe6bfc02ecae93bf30a33c199755b6059e180fca252744aa417f03ba7b4a2ff009f2a233d5ce9a40327112c1b96d9b0a465ebacd691b4d2488375c1df2551f6000b374b57a137dc71e3ce3564eb04e7495f136291836d3642d3b028e2bb4ef71b4e1288a6769e6d5f1f3079ca534040251e9e28079e3dfebc79e04295bd1070f2df3663911135f2ecbe6e6cafc4376f8240950ea0ebe9f249b4ec681c64692fa0c114c1b9ff7d88cb8067f25246262ce9672c75e730ce62574c28098b63afec5a2aee87260d627efcd5ce2d3f8f509e9cffcc1fd080ea9022e9bbc2021887ed5527fc158fb97836dceeeb612cc5ab068532f6d1f1c9683aa3f41dc17c4ae8b748d394f759e4d09a3e702b4b1116d1f05f18b2e376600325af56591abad295ffac2057e8bbf6d38a641db29e2efd525fa864c1a861a3d179c371b65706acf78fe073530d33085749165b978558a72110465f97b9de68dbad8011033e8534c1b9d5bdfb82397e2869bbc3796ab22353a6c624552b190904477ea61a958b99ccfcbc1555fee8ab91ac0a9cd8c3168c77925f96ccfba1d5f048e5e5ecdf1dca9debda3e8e208eed93ff8ffa0f9ef9006aaf8f7edda6aa9e0eb2273807544ffd1d758285ff247405feb02d79d8e09162b2fc19dcd8eec59e10ffc6df2cd08f3300fb976a0b3e9fcaa75bd002c90692d0f6cb92fcbb877192b2c0068dcd534f3f31bc6147c3dd04a94190ca1bf59c25a904cd75ccd5cb3ffa31e27abd3e162b5e057d7d3321e8c89f67852aaf0d6dbb179bb38009131e8946a68faadd01788f27c6268fd5dd73685af157c40bb0a32a4bd29a0142020035903b0a5ce2bf4fadff0a0ddb1165a1d5badd99444098ae373070d01ebdfc86738ee40972f43c8c952d4b758abcb933a59e38ff18aab00f0a5d5601cf615fd92d86587b0579635618793129dd607dc8162dc69e87d77a9c60d601e2dbd9a458de69d96067856b482c7fd6104748886f9a12879192f54d6244c85c9a4c7125a2dd190a43279a4108b68dd1783b26557a59613394719a06f44dbdce5f7936daa2a99508b3232f8e808dc2debbcf6bc0741c2e604bf147f6b5a02d47d7aeb9adaffa0d948d96a7631a3cca157294040c2ca847c9a51404e59654a6d9d4c255f3f9da6997ecf8b4db24f81b01c485e4aa9ddee0da0dbe0b1da4b4ed3d5562aef63eb5038f4231e8b87d9004fbce5e79b50dc94f51d5e796f244b0071bd564c85861dfbb1126c7c3e9fb7afceff6f26b142ebac98ffa69d8c81b5beaa6bfb41701ad4a780687457273e3362186f5a67b578bb01c7948941f9ce00d2a4fe11da35460f46622881f0a07bde1ca0db926574e62484a911a78662fc242ebd17c2374365f1b146a71e2bf5c35afdfad805bd4eef9085c60fc74aa9934a917f5bdaa94c84511b13e081d8c4546f05f63b339a22eb34e5b908c0f8dd2ac959e4f22c6fefe71dbc33f078debcc36fc269a0d2174ea81cbafedeef5d91f075ad9b0bfcf95f848e08689beb1b6277f20acce687fc6ce604448f554486c02642ccff567684ef8627c3d0c6eb2c582bd74caeb1a7ca37da052d2f94d3e09fa329a80a884308fccb8b9e0afb557029709d25ece70b54f6ac0d66eba9042f104f711c54db20eb6d671f570be801f140280b3f4b34f8434cf92a28ba02480096a8355eaf8b8df23e1f7a6e8ae08214790d23aab2860e51c251a7e42cf45fbc42404980775a8750a6efa9f2f95878315c286b32ed91530953baf12dd537397e1c3c6324fc0c57db6d8545fb766b6e6ff65a83631f4b436a6e2b59e35467534138d79db3c8ec6cc2a4c34ec6a4574e92e2d60b3bb771f5b65b1297291185b923e8dd44b8b1464d525c41e59e18fe5e930da5dbc0a001523e9dcf29fa26758abe1530f432adeb8c995bc8c8f7d766d529872187fff7be5503fe367f538f7e11d71e4706057cb0dd00bfd8ab636d39f5a4977a361746b066793d11ef5358cd6eea50aa52ca13ee2a28492356554446c1b93d34cc6494a1b8c6e19c85c977f1cf556e773cbc8904c69ac21097d14efe3b2629a75bf745a0c13e95079e000360381d7ee01078f8d0a22c2639087d73f8374c417eefcdb482be0698f84cdb86f1d99285abb5c27b3aa41280ed9fafe56db825c938a097055c47163b069b7bd261fd60b1b1a7a3e6aaf80756ae137854bce1343caafaef810af4050e3f8f0ac6ce7ab8e15f4971498d861227b40ab9d0e77951127cb6510eac105e65a309669e3fc2a5833881a8b6d368b8116920ca6515f975b2a7bc39fe23625723a66dbfc51974ce1f50a8a9f6747bb8f7ebb646a30e2aeefefd7d349e98fa3f39b21c7568ab89190dd2c8b403e6f797868e99f4c1c25a890fa199cf48f61df62ba3c0b0eca40d04447c2cbf38a9219764576feca0c21cb2e08ad3a9c06b362b02496694f29764a4651e3bfc16bb02df6181fd6d93c5958df8055b1bd7ebd86067e96700b27e259ea71fa933672438faf5fc390adb256084453061301cadd9d17abd273c623482887cf8f53e456cceea81e92d0db55ffbd6c380ffef71ac70b9b56f9a0bd7fa3f897eb63a4855202e6fc71fa8511597cc1fca3c6e29e2276eaa90a2a1fba5f33f26587c48a4fb511848c6aabfe88312afe436ff8a4103217fe8136936eff5a7978f25ff1662f114b391b846263f16b9149a1b3aafba8de281f54ace6337928439e40e0883e9f803a68137e84aaa279cb6b9ebafd6708174eac4a74d11c0f9582d33aaf65c43c7ba461caa364c87486dd5cea8f715c45e263747be569d1bead8583b0a39c7e46f5bd5b2ff0abb55d6010a9afefe602b850d753f65ce4777551dd82b3dd8e417c20bc93c60f32c78d2d3a5aec481a1e9bd7ed494358e6cc2c922e6e664ecd75b10722cdeecb39e84d881a32be5971ddf56964d9295e38d920e47558980231056cb3f0a2de3c2a64e884d27723a252eb1f6b254c6f90c2f8e26d19738753ee92e33a2e90776c97715539d8ffb8f6845c280f79e74c9c07e68f0675879e293997323d05a0f7728b37f5e7b15b1947045b4f4cbf6cb60a5d1b75a97a290d48b70d7a68393147c720b4f1b15fd66379f266f325a2052ffdaec307d4f9bb17525656e91771b0f84ecb6f5403e565d982deee5f012ce370d6e2011f164c9f97471f43ad9ee886b3f11b2771aeb4ce874a7f4130327a58606e631c1710eee7df8165029c62d4dc50710d24174b809a2b46c7cec3b10525b87bb16fb256a6de995cedcd15bfd905d434ca7e0056122c8892f9836df08ad9d68d97efcf0f74672fbe4009d902b21e168db2fc1e5499aa66878618f928eeee898222f90d2199e23a9bd4222ff086b4f18085a6570f67232f4237f0c2fe5f39561e92bf8ab9ad2c0584cb7dcbfe98814c6d256b60f92cc35e0dffb38a99493191478b302b72429c3789bc08e02a85ea8456abc5e7a6c96afb3af6c6682e80129b08a08d49bc803346d491cefcf21c64419b978565675cf48486d678cdb6d4556efd10c2f597aaa651f07427915b6cb84ccd8930aa5c45601de4249304f79bb164771ab462292a3ccc31a127bbeccfafec795c2ca02124c769c304d0f2da1ac65de831362ec4c9c7ba3dcd3b9264cba2cfcf5c8668e3f00646588f2848f42f1742893c2dda549e103253b8448a423b9307aacac2541cb7545ee74b3e707f9a51dd2235f53f70c67bc463974693fc26a4e91a7b7809a5e7133316e0b564c2bf002f42663f2fe7e62b13a6a3795f09e932450834a4cc6401a6567c7cdad667ed29807859653ad6b71beda79e200e6b1e5fc58137f38cfe12af4e921cca7615e1181f19e59514a03539539a3993d63f7acc2d0dd9ad30253b1c687f5cc51567927f08fc0bfae2f5a30b3ab3a6fcbc6f28b72d09d517867cb5fc0190ed26009434a0ca0b07997f6e77486412392b92227481529c2d7d107236d9d600caf15fd737e78792690422c093747d259ccad9e9fa6977aecc4dfe25b051982e3efadf984bc7d1351ee41217d3044656851ed8a6758119674c6a382144a41cd590f9b93352584034fbc488ee946a6fdf2dc40a510f74ce4e421e39613e31ac88bfdf9230a46cea8b689729855a4ec8a2e21ce912f11a3117a07a674c41ef6ea68f604fdc86da8850e485536ad5fa306648540f3de467a90aab45e06b5a217d835a87961fd107eda5515681640b516967234dbde3976fe9b9966e28b16ac965007aa2deb753e87f379d688ee44cf04dd4a7b4ea2e2f7e044a31c9e8440e092a14c424b9ac623003ee1a1aa8b4f1e908c71776af27eeb33bad52ab1412bad23c7437df0bbc6a11c26e5f9f60fdf41f49f821509a33694b270739cd7e8f639a30c7f932a340ff129f351d7eb8cd480bfa6a95fc9f937c5080374664e2462e7368468a0877c82569053e683408ef1d192f2291c7686325eeba5016a1b575760b749b9a8b8d5c1e7a60ed9449b395e74aa7cd6bb312dddd713d3fa4663f05f3a5a2703f1fdbfb35f8b5e53d5615e2918b1dd9c98c1b7" - ] - } -} \ No newline at end of file diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java index 776eafc71fc..40190519cf7 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/SchemaObjectProvider.java @@ -36,11 +36,11 @@ import tech.pegasys.teku.api.schema.deneb.BeaconStateDeneb; import tech.pegasys.teku.api.schema.deneb.BlindedBeaconBlockBodyDeneb; import tech.pegasys.teku.api.schema.deneb.BlindedBlockDeneb; -import tech.pegasys.teku.api.schema.electra.BeaconBlockBodyElectra; -import tech.pegasys.teku.api.schema.electra.BeaconBlockElectra; -import tech.pegasys.teku.api.schema.electra.BeaconStateElectra; -import tech.pegasys.teku.api.schema.electra.BlindedBeaconBlockBodyElectra; -import tech.pegasys.teku.api.schema.electra.BlindedBlockElectra; +import tech.pegasys.teku.api.schema.eip7594.BeaconBlockBodyEip7594; +import tech.pegasys.teku.api.schema.eip7594.BeaconBlockEip7594; +import tech.pegasys.teku.api.schema.eip7594.BeaconStateEip7594; +import tech.pegasys.teku.api.schema.eip7594.BlindedBeaconBlockBodyEip7594; +import tech.pegasys.teku.api.schema.eip7594.BlindedBlockEip7594; import tech.pegasys.teku.api.schema.phase0.BeaconBlockPhase0; import tech.pegasys.teku.api.schema.phase0.BeaconStatePhase0; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -118,12 +118,12 @@ public BeaconBlock getBlindedBlock( block.getParentRoot(), block.getStateRoot(), getBlindedBlockBodyDeneb(block.getBody())); - case ELECTRA -> new BlindedBlockElectra( + case EIP7594 -> new BlindedBlockEip7594( block.getSlot(), block.getProposerIndex(), block.getParentRoot(), block.getStateRoot(), - getBlindedBlockBodyElectra(block.getBody())); + getBlindedBlockBodyEip7594(block.getBody())); }; } @@ -161,12 +161,12 @@ public BeaconBlock getBeaconBlock( block.getParentRoot(), block.getStateRoot(), getBeaconBlockBodyDeneb(block.getBody())); - case ELECTRA -> new BeaconBlockElectra( + case EIP7594 -> new BeaconBlockEip7594( block.getSlot(), block.getProposerIndex(), block.getParentRoot(), block.getStateRoot(), - getBeaconBlockBodyElectra(block.getBody())); + getBeaconBlockBodyEip7594(block.getBody())); }; } @@ -198,11 +198,11 @@ private BeaconBlockBodyDeneb getBeaconBlockBodyDeneb( .required(body)); } - private BeaconBlockBodyElectra getBeaconBlockBodyElectra( + private BeaconBlockBodyEip7594 getBeaconBlockBodyEip7594( final tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody body) { - return new BeaconBlockBodyElectra( - tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra - .BeaconBlockBodyElectra.required(body)); + return new BeaconBlockBodyEip7594( + tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594 + .BeaconBlockBodyEip7594.required(body)); } private BlindedBeaconBlockBodyBellatrix getBlindedBlockBodyBellatrix( @@ -226,11 +226,11 @@ private BlindedBeaconBlockBodyDeneb getBlindedBlockBodyDeneb( .BlindedBeaconBlockBodyDeneb.required(body)); } - private BlindedBeaconBlockBodyElectra getBlindedBlockBodyElectra( + private BlindedBeaconBlockBodyEip7594 getBlindedBlockBodyEip7594( final tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody body) { - return new BlindedBeaconBlockBodyElectra( - tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra - .BlindedBeaconBlockBodyElectra.required(body)); + return new BlindedBeaconBlockBodyEip7594( + tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594 + .BlindedBeaconBlockBodyEip7594.required(body)); } public BeaconState getBeaconState( @@ -242,7 +242,7 @@ public BeaconState getBeaconState( case BELLATRIX -> new BeaconStateBellatrix(state); case CAPELLA -> new BeaconStateCapella(state); case DENEB -> new BeaconStateDeneb(state); - case ELECTRA -> new BeaconStateElectra(state); + case EIP7594 -> new BeaconStateEip7594(state); }; } } diff --git a/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java b/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java index 7eff19ab000..fb2551d4511 100644 --- a/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java +++ b/data/provider/src/main/java/tech/pegasys/teku/api/ValidatorDataProvider.java @@ -34,8 +34,8 @@ import tech.pegasys.teku.api.schema.capella.SignedBlindedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; import tech.pegasys.teku.api.schema.deneb.SignedBlindedBeaconBlockDeneb; -import tech.pegasys.teku.api.schema.electra.SignedBeaconBlockElectra; -import tech.pegasys.teku.api.schema.electra.SignedBlindedBeaconBlockElectra; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; +import tech.pegasys.teku.api.schema.eip7594.SignedBlindedBeaconBlockEip7594; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; import tech.pegasys.teku.bls.BLSSignature; import tech.pegasys.teku.ethereum.json.types.validator.AttesterDuties; @@ -175,7 +175,7 @@ public SignedBeaconBlock parseBlock(final JsonProvider jsonProvider, final Strin case BELLATRIX -> mapper.treeToValue(jsonNode, SignedBeaconBlockBellatrix.class); case CAPELLA -> mapper.treeToValue(jsonNode, SignedBeaconBlockCapella.class); case DENEB -> mapper.treeToValue(jsonNode, SignedBeaconBlockDeneb.class); - case ELECTRA -> mapper.treeToValue(jsonNode, SignedBeaconBlockElectra.class); + case EIP7594 -> mapper.treeToValue(jsonNode, SignedBeaconBlockEip7594.class); }; } @@ -191,7 +191,7 @@ public SignedBeaconBlock parseBlindedBlock( case BELLATRIX -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockBellatrix.class); case CAPELLA -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockCapella.class); case DENEB -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockDeneb.class); - case ELECTRA -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockElectra.class); + case EIP7594 -> mapper.treeToValue(jsonNode, SignedBlindedBeaconBlockEip7594.class); }; } diff --git a/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java b/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java index 4b3f84907d7..16a769569c8 100644 --- a/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java +++ b/data/provider/src/test/java/tech/pegasys/teku/api/ValidatorDataProviderTest.java @@ -56,7 +56,7 @@ import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; import tech.pegasys.teku.api.schema.capella.SignedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; -import tech.pegasys.teku.api.schema.electra.SignedBeaconBlockElectra; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; import tech.pegasys.teku.bls.BLSPublicKey; import tech.pegasys.teku.bls.BLSSignature; @@ -330,8 +330,8 @@ void parseBlock_shouldParseMilestoneSpecificBlocks(SpecContext specContext) case DENEB: assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockDeneb.class); break; - case ELECTRA: - assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockElectra.class); + case EIP7594: + assertThat(parsedBlock).isInstanceOf(SignedBeaconBlockEip7594.class); break; default: throw new RuntimeException("notImplemented"); diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/response/v2/beacon/GetBlockResponseV2.java b/data/serializer/src/main/java/tech/pegasys/teku/api/response/v2/beacon/GetBlockResponseV2.java index 7e6783225c5..f38fe72704a 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/response/v2/beacon/GetBlockResponseV2.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/response/v2/beacon/GetBlockResponseV2.java @@ -21,6 +21,9 @@ import tech.pegasys.teku.api.schema.Version; import tech.pegasys.teku.api.schema.altair.SignedBeaconBlockAltair; import tech.pegasys.teku.api.schema.bellatrix.SignedBeaconBlockBellatrix; +import tech.pegasys.teku.api.schema.capella.SignedBeaconBlockCapella; +import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; import tech.pegasys.teku.api.schema.interfaces.SignedBlock; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; @@ -38,7 +41,10 @@ public class GetBlockResponseV2 { @JsonSubTypes({ @JsonSubTypes.Type(value = SignedBeaconBlockPhase0.class, name = "phase0"), @JsonSubTypes.Type(value = SignedBeaconBlockAltair.class, name = "altair"), - @JsonSubTypes.Type(value = SignedBeaconBlockBellatrix.class, name = "bellatrix") + @JsonSubTypes.Type(value = SignedBeaconBlockBellatrix.class, name = "bellatrix"), + @JsonSubTypes.Type(value = SignedBeaconBlockCapella.class, name = "capella"), + @JsonSubTypes.Type(value = SignedBeaconBlockDeneb.class, name = "deneb"), + @JsonSubTypes.Type(value = SignedBeaconBlockEip7594.class, name = "eip7594") }) public final SignedBlock data; diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayload.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayload.java index 844513d76e3..cca9d4fd57e 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayload.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayload.java @@ -17,7 +17,7 @@ import tech.pegasys.teku.api.schema.bellatrix.ExecutionPayloadBellatrix; import tech.pegasys.teku.api.schema.capella.ExecutionPayloadCapella; import tech.pegasys.teku.api.schema.deneb.ExecutionPayloadDeneb; -import tech.pegasys.teku.api.schema.electra.ExecutionPayloadElectra; +import tech.pegasys.teku.api.schema.eip7594.ExecutionPayloadEip7594; public interface ExecutionPayload { @@ -33,7 +33,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayloadHeader.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayloadHeader.java index f82dd3f7eff..eceed5f78b6 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayloadHeader.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/ExecutionPayloadHeader.java @@ -17,7 +17,7 @@ import tech.pegasys.teku.api.schema.bellatrix.ExecutionPayloadHeaderBellatrix; import tech.pegasys.teku.api.schema.capella.ExecutionPayloadHeaderCapella; import tech.pegasys.teku.api.schema.deneb.ExecutionPayloadHeaderDeneb; -import tech.pegasys.teku.api.schema.electra.ExecutionPayloadHeaderElectra; +import tech.pegasys.teku.api.schema.eip7594.ExecutionPayloadHeaderEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; public interface ExecutionPayloadHeader { @@ -36,7 +36,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/SignedBeaconBlock.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/SignedBeaconBlock.java index dcd5ac7c59f..1692893cc93 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/SignedBeaconBlock.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/SignedBeaconBlock.java @@ -29,8 +29,8 @@ import tech.pegasys.teku.api.schema.capella.SignedBlindedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; import tech.pegasys.teku.api.schema.deneb.SignedBlindedBeaconBlockDeneb; -import tech.pegasys.teku.api.schema.electra.SignedBeaconBlockElectra; -import tech.pegasys.teku.api.schema.electra.SignedBlindedBeaconBlockElectra; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; +import tech.pegasys.teku.api.schema.eip7594.SignedBlindedBeaconBlockEip7594; import tech.pegasys.teku.api.schema.interfaces.SignedBlock; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; import tech.pegasys.teku.spec.Spec; @@ -67,12 +67,12 @@ public static SignedBeaconBlock create( return Stream.of( () -> beaconBlock - .toBlindedVersionElectra() - .map(__ -> new SignedBlindedBeaconBlockElectra(internalBlock)), + .toBlindedVersionEip7594() + .map(__ -> new SignedBlindedBeaconBlockEip7594(internalBlock)), () -> beaconBlock - .toVersionElectra() - .map(__ -> new SignedBeaconBlockElectra(internalBlock)), + .toVersionEip7594() + .map(__ -> new SignedBeaconBlockEip7594(internalBlock)), () -> beaconBlock .toBlindedVersionDeneb() diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java index 85ac4b86670..c0f59bf7084 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/Version.java @@ -22,7 +22,7 @@ public enum Version { bellatrix, capella, deneb, - electra; + eip7594; public static Version fromMilestone(final SpecMilestone milestone) { return switch (milestone) { @@ -31,7 +31,7 @@ public static Version fromMilestone(final SpecMilestone milestone) { case BELLATRIX -> bellatrix; case CAPELLA -> capella; case DENEB -> deneb; - case ELECTRA -> electra; + case EIP7594 -> eip7594; }; } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockBodyElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockBodyEip7594.java similarity index 88% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockBodyElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockBodyEip7594.java index d0dcadeba81..de51a6a83cf 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockBodyElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockBodyEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import static com.google.common.base.Preconditions.checkNotNull; @@ -34,13 +34,13 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; -public class BeaconBlockBodyElectra extends BeaconBlockBodyAltair { +public class BeaconBlockBodyEip7594 extends BeaconBlockBodyAltair { @JsonProperty("execution_payload") - public final ExecutionPayloadElectra executionPayload; + public final ExecutionPayloadEip7594 executionPayload; @JsonProperty("bls_to_execution_changes") public final List blsToExecutionChanges; @@ -49,7 +49,7 @@ public class BeaconBlockBodyElectra extends BeaconBlockBodyAltair { public final List blobKZGCommitments; @JsonCreator - public BeaconBlockBodyElectra( + public BeaconBlockBodyEip7594( @JsonProperty("randao_reveal") final BLSSignature randaoReveal, @JsonProperty("eth1_data") final Eth1Data eth1Data, @JsonProperty("graffiti") final Bytes32 graffiti, @@ -59,7 +59,7 @@ public BeaconBlockBodyElectra( @JsonProperty("deposits") final List deposits, @JsonProperty("voluntary_exits") final List voluntaryExits, @JsonProperty("sync_aggregate") final SyncAggregate syncAggregate, - @JsonProperty("execution_payload") final ExecutionPayloadElectra executionPayload, + @JsonProperty("execution_payload") final ExecutionPayloadEip7594 executionPayload, @JsonProperty("bls_to_execution_changes") final List blsToExecutionChanges, @JsonProperty("blob_kzg_commitments") final List blobKZGCommitments) { @@ -73,27 +73,27 @@ public BeaconBlockBodyElectra( deposits, voluntaryExits, syncAggregate); - checkNotNull(executionPayload, "Execution Payload is required for Electra blocks"); + checkNotNull(executionPayload, "Execution Payload is required for EIP7594 blocks"); this.executionPayload = executionPayload; - checkNotNull(blsToExecutionChanges, "BlsToExecutionChanges is required for Electra blocks"); + checkNotNull(blsToExecutionChanges, "BlsToExecutionChanges is required for EIP7594 blocks"); this.blsToExecutionChanges = blsToExecutionChanges; - checkNotNull(blobKZGCommitments, "blobKZGCommitments is required for Electra blocks"); + checkNotNull(blobKZGCommitments, "blobKZGCommitments is required for EIP7594 blocks"); this.blobKZGCommitments = blobKZGCommitments; } - public BeaconBlockBodyElectra( - tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra + public BeaconBlockBodyEip7594( + tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyEip7594 message) { super(message); - checkNotNull(message.getExecutionPayload(), "Execution Payload is required for Electra blocks"); - this.executionPayload = new ExecutionPayloadElectra(message.getExecutionPayload()); + checkNotNull(message.getExecutionPayload(), "Execution Payload is required for EIP7594 blocks"); + this.executionPayload = new ExecutionPayloadEip7594(message.getExecutionPayload()); checkNotNull( message.getBlsToExecutionChanges(), - "BlsToExecutionChanges are required for Electra blocks"); + "BlsToExecutionChanges are required for EIP7594 blocks"); this.blsToExecutionChanges = message.getBlsToExecutionChanges().stream().map(SignedBlsToExecutionChange::new).toList(); checkNotNull( - message.getBlobKzgCommitments(), "BlobKzgCommitments are required for Electra blocks"); + message.getBlobKzgCommitments(), "BlobKzgCommitments are required for EIP7594 blocks"); this.blobKZGCommitments = message.getBlobKzgCommitments().stream() .map(SszKZGCommitment::getKZGCommitment) @@ -102,8 +102,8 @@ public BeaconBlockBodyElectra( } @Override - public BeaconBlockBodySchemaElectra getBeaconBlockBodySchema(final SpecVersion spec) { - return (BeaconBlockBodySchemaElectra) spec.getSchemaDefinitions().getBeaconBlockBodySchema(); + public BeaconBlockBodySchemaEip7594 getBeaconBlockBodySchema(final SpecVersion spec) { + return (BeaconBlockBodySchemaEip7594) spec.getSchemaDefinitions().getBeaconBlockBodySchema(); } @Override diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockEip7594.java similarity index 76% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockEip7594.java index 3b2d0b3e9b4..1bdae66d5bd 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconBlockElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconBlockEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -21,23 +21,23 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; -public class BeaconBlockElectra extends BeaconBlockAltair { +public class BeaconBlockEip7594 extends BeaconBlockAltair { - public BeaconBlockElectra(final BeaconBlock message) { + public BeaconBlockEip7594(final BeaconBlock message) { super( message.getSlot(), message.getProposerIndex(), message.getParentRoot(), message.getStateRoot(), - new BeaconBlockBodyElectra(message.getBody().toVersionElectra().orElseThrow())); + new BeaconBlockBodyEip7594(message.getBody().toVersionEip7594().orElseThrow())); } @Override public BeaconBlock asInternalBeaconBlock(final Spec spec) { final SpecVersion specVersion = spec.atSlot(slot); - return SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) + return SchemaDefinitionsEip7594.required(specVersion.getSchemaDefinitions()) .getBeaconBlockSchema() .create( slot, @@ -49,17 +49,17 @@ public BeaconBlock asInternalBeaconBlock(final Spec spec) { @JsonProperty("body") @Override - public BeaconBlockBodyElectra getBody() { - return (BeaconBlockBodyElectra) body; + public BeaconBlockBodyEip7594 getBody() { + return (BeaconBlockBodyEip7594) body; } @JsonCreator - public BeaconBlockElectra( + public BeaconBlockEip7594( @JsonProperty("slot") final UInt64 slot, @JsonProperty("proposer_index") final UInt64 proposerIndex, @JsonProperty("parent_root") final Bytes32 parentRoot, @JsonProperty("state_root") final Bytes32 stateRoot, - @JsonProperty("body") final BeaconBlockBodyElectra body) { + @JsonProperty("body") final BeaconBlockBodyEip7594 body) { super(slot, proposerIndex, parentRoot, stateRoot, body); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconStateEip7594.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconStateEip7594.java new file mode 100644 index 00000000000..33d7011dac1 --- /dev/null +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BeaconStateEip7594.java @@ -0,0 +1,178 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.api.schema.eip7594; + +import static tech.pegasys.teku.api.schema.SchemaConstants.EXAMPLE_UINT64; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import java.util.List; +import org.apache.tuweni.bytes.Bytes32; +import tech.pegasys.teku.api.schema.BeaconBlockHeader; +import tech.pegasys.teku.api.schema.Checkpoint; +import tech.pegasys.teku.api.schema.Eth1Data; +import tech.pegasys.teku.api.schema.Fork; +import tech.pegasys.teku.api.schema.Validator; +import tech.pegasys.teku.api.schema.altair.BeaconStateAltair; +import tech.pegasys.teku.api.schema.altair.SyncCommittee; +import tech.pegasys.teku.api.schema.capella.HistoricalSummary; +import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.state.SyncCommittee.SyncCommitteeSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.MutableBeaconStateEip7594; + +public class BeaconStateEip7594 extends BeaconStateAltair { + + @JsonProperty("latest_execution_payload_header") + public final ExecutionPayloadHeaderEip7594 latestExecutionPayloadHeader; + + @JsonProperty("next_withdrawal_index") + @Schema(type = "string", example = EXAMPLE_UINT64) + public final UInt64 nextWithdrawalIndex; + + @JsonProperty("next_withdrawal_validator_index") + @Schema(type = "string", example = EXAMPLE_UINT64) + public final UInt64 nextWithdrawalValidatorIndex; + + @JsonProperty("historical_summaries") + public final List historicalSummaries; + + public BeaconStateEip7594( + @JsonProperty("genesis_time") final UInt64 genesisTime, + @JsonProperty("genesis_validators_root") final Bytes32 genesisValidatorsRoot, + @JsonProperty("slot") final UInt64 slot, + @JsonProperty("fork") final Fork fork, + @JsonProperty("latest_block_header") final BeaconBlockHeader latestBlockHeader, + @JsonProperty("block_roots") final List blockRoots, + @JsonProperty("state_roots") final List stateRoots, + @JsonProperty("historical_roots") final List historicalRoots, + @JsonProperty("eth1_data") final Eth1Data eth1Data, + @JsonProperty("eth1_data_votes") final List eth1DataVotes, + @JsonProperty("eth1_deposit_index") final UInt64 eth1DepositIndex, + @JsonProperty("validators") final List validators, + @JsonProperty("balances") final List balances, + @JsonProperty("randao_mixes") final List randaoMixes, + @JsonProperty("slashings") final List slashings, + @JsonProperty("previous_epoch_participation") final byte[] previousEpochParticipation, + @JsonProperty("current_epoch_participation") final byte[] currentEpochParticipation, + @JsonProperty("justification_bits") final SszBitvector justificationBits, + @JsonProperty("previous_justified_checkpoint") final Checkpoint previousJustifiedCheckpoint, + @JsonProperty("current_justified_checkpoint") final Checkpoint currentJustifiedCheckpoint, + @JsonProperty("finalized_checkpoint") final Checkpoint finalizedCheckpoint, + @JsonProperty("inactivity_scores") final List inactivityScores, + @JsonProperty("current_sync_committee") final SyncCommittee currentSyncCommittee, + @JsonProperty("next_sync_committee") final SyncCommittee nextSyncCommittee, + @JsonProperty("latest_execution_payload_header") + final ExecutionPayloadHeaderEip7594 latestExecutionPayloadHeader, + @JsonProperty("next_withdrawal_index") final UInt64 nextWithdrawalIndex, + @JsonProperty("next_withdrawal_validator_index") final UInt64 nextWithdrawalValidatorIndex, + @JsonProperty("historical_summaries") final List historicalSummaries) { + super( + genesisTime, + genesisValidatorsRoot, + slot, + fork, + latestBlockHeader, + blockRoots, + stateRoots, + historicalRoots, + eth1Data, + eth1DataVotes, + eth1DepositIndex, + validators, + balances, + randaoMixes, + slashings, + previousEpochParticipation, + currentEpochParticipation, + justificationBits, + previousJustifiedCheckpoint, + currentJustifiedCheckpoint, + finalizedCheckpoint, + inactivityScores, + currentSyncCommittee, + nextSyncCommittee); + this.latestExecutionPayloadHeader = latestExecutionPayloadHeader; + this.nextWithdrawalIndex = nextWithdrawalIndex; + this.nextWithdrawalValidatorIndex = nextWithdrawalValidatorIndex; + this.historicalSummaries = historicalSummaries; + } + + public BeaconStateEip7594(final BeaconState beaconState) { + super(beaconState); + final tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594 + .BeaconStateEip7594 + eip7594 = beaconState.toVersionEip7594().orElseThrow(); + this.latestExecutionPayloadHeader = + new ExecutionPayloadHeaderEip7594(eip7594.getLatestExecutionPayloadHeader()); + this.nextWithdrawalIndex = eip7594.getNextWithdrawalIndex(); + this.nextWithdrawalValidatorIndex = eip7594.getNextWithdrawalValidatorIndex(); + this.historicalSummaries = + eip7594.getHistoricalSummaries().stream().map(HistoricalSummary::new).toList(); + } + + @Override + protected void applyAdditionalFields( + final MutableBeaconState state, final SpecVersion specVersion) { + state + .toMutableVersionEip7594() + .ifPresent( + mutableBeaconStateEip7594 -> + applyEip7594Fields( + specVersion, + mutableBeaconStateEip7594, + BeaconStateSchemaEip7594.required( + mutableBeaconStateEip7594.getBeaconStateSchema()) + .getCurrentSyncCommitteeSchema(), + BeaconStateSchemaEip7594.required( + mutableBeaconStateEip7594.getBeaconStateSchema()) + .getLastExecutionPayloadHeaderSchema(), + BeaconStateSchemaEip7594.required( + mutableBeaconStateEip7594.getBeaconStateSchema()) + .getHistoricalSummariesSchema(), + this)); + } + + protected static void applyEip7594Fields( + final SpecVersion specVersion, + final MutableBeaconStateEip7594 state, + final SyncCommitteeSchema syncCommitteeSchema, + final ExecutionPayloadHeaderSchemaEip7594 executionPayloadHeaderSchema, + final SszListSchema< + tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary, ?> + historicalSummariesSchema, + final BeaconStateEip7594 instance) { + + BeaconStateAltair.applyAltairFields(state, syncCommitteeSchema, instance); + + state.setLatestExecutionPayloadHeader( + instance.latestExecutionPayloadHeader.asInternalExecutionPayloadHeader( + executionPayloadHeaderSchema)); + + state.setNextWithdrawalIndex(instance.nextWithdrawalIndex); + state.setNextWithdrawalValidatorIndex(instance.nextWithdrawalValidatorIndex); + state.setHistoricalSummaries( + historicalSummariesSchema.createFromElements( + instance.historicalSummaries.stream() + .map( + historicalSummary -> historicalSummary.asInternalHistoricalSummary(specVersion)) + .toList())); + } +} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBeaconBlockBodyElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBeaconBlockBodyEip7594.java similarity index 88% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBeaconBlockBodyElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBeaconBlockBodyEip7594.java index 90a70dbd21b..4714e9219b8 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBeaconBlockBodyElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBeaconBlockBodyEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import static com.google.common.base.Preconditions.checkNotNull; @@ -34,14 +34,14 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodySchemaElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BlindedBeaconBlockBodySchemaEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; -public class BlindedBeaconBlockBodyElectra extends BeaconBlockBodyAltair { +public class BlindedBeaconBlockBodyEip7594 extends BeaconBlockBodyAltair { @JsonProperty("execution_payload_header") - public final ExecutionPayloadHeaderElectra executionPayloadHeader; + public final ExecutionPayloadHeaderEip7594 executionPayloadHeader; @JsonProperty("bls_to_execution_changes") public final List blsToExecutionChanges; @@ -50,7 +50,7 @@ public class BlindedBeaconBlockBodyElectra extends BeaconBlockBodyAltair { public final List blobKZGCommitments; @JsonCreator - public BlindedBeaconBlockBodyElectra( + public BlindedBeaconBlockBodyEip7594( @JsonProperty("randao_reveal") final BLSSignature randaoReveal, @JsonProperty("eth1_data") final Eth1Data eth1Data, @JsonProperty("graffiti") final Bytes32 graffiti, @@ -61,7 +61,7 @@ public BlindedBeaconBlockBodyElectra( @JsonProperty("voluntary_exits") final List voluntaryExits, @JsonProperty("sync_aggregate") final SyncAggregate syncAggregate, @JsonProperty("execution_payload_header") - final ExecutionPayloadHeaderElectra executionPayloadHeader, + final ExecutionPayloadHeaderEip7594 executionPayloadHeader, @JsonProperty("bls_to_execution_changes") final List blsToExecutionChanges, @JsonProperty("blob_kzg_commitments") final List blobKZGCommitments) { @@ -76,22 +76,22 @@ public BlindedBeaconBlockBodyElectra( voluntaryExits, syncAggregate); checkNotNull( - executionPayloadHeader, "Execution Payload Header is required for Electra blinded blocks"); + executionPayloadHeader, "Execution Payload Header is required for EIP7594 blinded blocks"); this.executionPayloadHeader = executionPayloadHeader; checkNotNull( - blsToExecutionChanges, "blsToExecutionChanges is required for Electra blinded blocks"); + blsToExecutionChanges, "blsToExecutionChanges is required for EIP7594 blinded blocks"); this.blsToExecutionChanges = blsToExecutionChanges; - checkNotNull(blobKZGCommitments, "blobKZGCommitments is required for Electra blinded blocks"); + checkNotNull(blobKZGCommitments, "blobKZGCommitments is required for EIP7594 blinded blocks"); this.blobKZGCommitments = blobKZGCommitments; } - public BlindedBeaconBlockBodyElectra( - final tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra - .BlindedBeaconBlockBodyElectra + public BlindedBeaconBlockBodyEip7594( + final tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594 + .BlindedBeaconBlockBodyEip7594 blockBody) { super(blockBody); this.executionPayloadHeader = - new ExecutionPayloadHeaderElectra(blockBody.getExecutionPayloadHeader()); + new ExecutionPayloadHeaderEip7594(blockBody.getExecutionPayloadHeader()); this.blsToExecutionChanges = blockBody.getBlsToExecutionChanges().stream().map(SignedBlsToExecutionChange::new).toList(); this.blobKZGCommitments = @@ -102,8 +102,8 @@ public BlindedBeaconBlockBodyElectra( } @Override - public BlindedBeaconBlockBodySchemaElectra getBeaconBlockBodySchema(final SpecVersion spec) { - return (BlindedBeaconBlockBodySchemaElectra) + public BlindedBeaconBlockBodySchemaEip7594 getBeaconBlockBodySchema(final SpecVersion spec) { + return (BlindedBeaconBlockBodySchemaEip7594) spec.getSchemaDefinitions().getBlindedBeaconBlockBodySchema(); } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBlockElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBlockEip7594.java similarity index 81% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBlockElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBlockEip7594.java index cd66627ae0a..8030e100446 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BlindedBlockElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/BlindedBlockEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -23,16 +23,16 @@ import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSchema; -public class BlindedBlockElectra extends BeaconBlockAltair { +public class BlindedBlockEip7594 extends BeaconBlockAltair { - public BlindedBlockElectra(final BeaconBlock message) { + public BlindedBlockEip7594(final BeaconBlock message) { super( message.getSlot(), message.getProposerIndex(), message.getParentRoot(), message.getStateRoot(), - new BlindedBeaconBlockBodyElectra( - message.getBody().toBlindedVersionElectra().orElseThrow())); + new BlindedBeaconBlockBodyEip7594( + message.getBody().toBlindedVersionEip7594().orElseThrow())); } @Override @@ -54,17 +54,17 @@ public BeaconBlock asInternalBeaconBlock(final Spec spec) { @JsonProperty("body") @Override - public BlindedBeaconBlockBodyElectra getBody() { - return (BlindedBeaconBlockBodyElectra) body; + public BlindedBeaconBlockBodyEip7594 getBody() { + return (BlindedBeaconBlockBodyEip7594) body; } @JsonCreator - public BlindedBlockElectra( + public BlindedBlockEip7594( @JsonProperty("slot") final UInt64 slot, @JsonProperty("proposer_index") final UInt64 proposerIndex, @JsonProperty("parent_root") final Bytes32 parentRoot, @JsonProperty("state_root") final Bytes32 stateRoot, - @JsonProperty("body") final BlindedBeaconBlockBodyElectra body) { + @JsonProperty("body") final BlindedBeaconBlockBodyEip7594 body) { super(slot, proposerIndex, parentRoot, stateRoot, body); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadEip7594.java similarity index 58% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadEip7594.java index 2a0e450b094..2f5dff2a204 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -25,19 +25,11 @@ import tech.pegasys.teku.api.schema.deneb.ExecutionPayloadDeneb; import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; -public class ExecutionPayloadElectra extends ExecutionPayloadDeneb implements ExecutionPayload { - - @JsonProperty("deposit_receipts") - public final List depositReceipts; - - @JsonProperty("exits") - public final List exits; +public class ExecutionPayloadEip7594 extends ExecutionPayloadDeneb implements ExecutionPayload { @JsonCreator - public ExecutionPayloadElectra( + public ExecutionPayloadEip7594( @JsonProperty("parent_hash") final Bytes32 parentHash, @JsonProperty("fee_recipient") final Bytes20 feeRecipient, @JsonProperty("state_root") final Bytes32 stateRoot, @@ -54,9 +46,7 @@ public ExecutionPayloadElectra( @JsonProperty("transactions") final List transactions, @JsonProperty("withdrawals") final List withdrawals, @JsonProperty("blob_gas_used") final UInt64 blobGasUsed, - @JsonProperty("excess_blob_gas") final UInt64 excessBlobGas, - @JsonProperty("deposit_receipts") final List depositReceipts, - @JsonProperty("exits") final List exits) { + @JsonProperty("excess_blob_gas") final UInt64 excessBlobGas) { super( parentHash, feeRecipient, @@ -75,48 +65,15 @@ public ExecutionPayloadElectra( withdrawals, blobGasUsed, excessBlobGas); - this.depositReceipts = depositReceipts; - this.exits = exits; } - public ExecutionPayloadElectra( + public ExecutionPayloadEip7594( final tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload executionPayload) { super(executionPayload); - this.depositReceipts = - executionPayload.toVersionElectra().orElseThrow().getDepositReceipts().stream() - .map(DepositReceipt::new) - .toList(); - this.exits = - executionPayload.toVersionElectra().orElseThrow().getExits().stream() - .map(ExecutionLayerExit::new) - .toList(); - } - - @Override - protected ExecutionPayloadBuilder applyToBuilder( - final ExecutionPayloadSchema executionPayloadSchema, - final ExecutionPayloadBuilder builder) { - return super.applyToBuilder(executionPayloadSchema, builder) - .depositReceipts( - () -> - depositReceipts.stream() - .map( - depositReceipt -> - depositReceipt.asInternalDepositReceipt( - executionPayloadSchema.getDepositReceiptSchemaRequired())) - .toList()) - .exits( - () -> - exits.stream() - .map( - exit -> - exit.asInternalExecutionLayerExit( - executionPayloadSchema.getExecutionLayerExitSchemaRequired())) - .toList()); } @Override - public Optional toVersionElectra() { + public Optional toVersionEip7594() { return Optional.of(this); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadHeaderElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadHeaderEip7594.java similarity index 63% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadHeaderElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadHeaderEip7594.java index 3ee95502e1f..e90125e7be0 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionPayloadHeaderElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/ExecutionPayloadHeaderEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -23,18 +23,11 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; -public class ExecutionPayloadHeaderElectra extends ExecutionPayloadHeaderDeneb { - - @JsonProperty("deposit_receipts_root") - public final Bytes32 depositReceiptsRoot; - - @JsonProperty("exits_root") - public final Bytes32 exitsRoot; +public class ExecutionPayloadHeaderEip7594 extends ExecutionPayloadHeaderDeneb { @JsonCreator - public ExecutionPayloadHeaderElectra( + public ExecutionPayloadHeaderEip7594( @JsonProperty("parent_hash") final Bytes32 parentHash, @JsonProperty("fee_recipient") final Bytes20 feeRecipient, @JsonProperty("state_root") final Bytes32 stateRoot, @@ -51,9 +44,7 @@ public ExecutionPayloadHeaderElectra( @JsonProperty("transactions_root") final Bytes32 transactionsRoot, @JsonProperty("withdrawals_root") final Bytes32 withdrawalsRoot, @JsonProperty("blob_gas_used") final UInt64 blobGasUsed, - @JsonProperty("excess_blob_gas") final UInt64 excessBlobGas, - @JsonProperty("deposit_receipts_root") final Bytes32 depositReceiptsRoot, - @JsonProperty("exits_root") final Bytes32 exitsRoot) { + @JsonProperty("excess_blob_gas") final UInt64 excessBlobGas) { super( parentHash, feeRecipient, @@ -72,11 +63,9 @@ public ExecutionPayloadHeaderElectra( withdrawalsRoot, blobGasUsed, excessBlobGas); - this.depositReceiptsRoot = depositReceiptsRoot; - this.exitsRoot = exitsRoot; } - public ExecutionPayloadHeaderElectra(final ExecutionPayloadHeader executionPayloadHeader) { + public ExecutionPayloadHeaderEip7594(final ExecutionPayloadHeader executionPayloadHeader) { super( executionPayloadHeader.getParentHash(), executionPayloadHeader.getFeeRecipient(), @@ -95,40 +84,10 @@ public ExecutionPayloadHeaderElectra(final ExecutionPayloadHeader executionPaylo executionPayloadHeader.getOptionalWithdrawalsRoot().orElseThrow(), executionPayloadHeader.toVersionDeneb().orElseThrow().getBlobGasUsed(), executionPayloadHeader.toVersionDeneb().orElseThrow().getExcessBlobGas()); - this.depositReceiptsRoot = - executionPayloadHeader.toVersionElectra().orElseThrow().getDepositReceiptsRoot(); - this.exitsRoot = executionPayloadHeader.toVersionElectra().orElseThrow().getExitsRoot(); - } - - @Override - public ExecutionPayloadHeader asInternalExecutionPayloadHeader( - final ExecutionPayloadHeaderSchema schema) { - return schema.createExecutionPayloadHeader( - payloadBuilder -> - payloadBuilder - .parentHash(parentHash) - .feeRecipient(feeRecipient) - .stateRoot(stateRoot) - .receiptsRoot(receiptsRoot) - .logsBloom(logsBloom) - .prevRandao(prevRandao) - .blockNumber(blockNumber) - .gasLimit(gasLimit) - .gasUsed(gasUsed) - .timestamp(timestamp) - .extraData(extraData) - .baseFeePerGas(baseFeePerGas) - .blockHash(blockHash) - .transactionsRoot(transactionsRoot) - .withdrawalsRoot(() -> withdrawalsRoot) - .blobGasUsed(() -> blobGasUsed) - .excessBlobGas(() -> excessBlobGas) - .depositReceiptsRoot(() -> depositReceiptsRoot) - .exitsRoot(() -> exitsRoot)); } @Override - public Optional toVersionElectra() { + public Optional toVersionEip7594() { return Optional.of(this); } } diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBeaconBlockElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBeaconBlockEip7594.java similarity index 75% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBeaconBlockElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBeaconBlockEip7594.java index 66ebc964edf..9500de417d8 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBeaconBlockElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBeaconBlockEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; @@ -19,23 +19,23 @@ import tech.pegasys.teku.api.schema.SignedBeaconBlock; import tech.pegasys.teku.api.schema.interfaces.SignedBlock; -public class SignedBeaconBlockElectra extends SignedBeaconBlock implements SignedBlock { - private final BeaconBlockElectra message; +public class SignedBeaconBlockEip7594 extends SignedBeaconBlock implements SignedBlock { + private final BeaconBlockEip7594 message; - public SignedBeaconBlockElectra( + public SignedBeaconBlockEip7594( final tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock internalBlock) { super(internalBlock); - this.message = new BeaconBlockElectra(internalBlock.getMessage()); + this.message = new BeaconBlockEip7594(internalBlock.getMessage()); } @Override - public BeaconBlockElectra getMessage() { + public BeaconBlockEip7594 getMessage() { return message; } @JsonCreator - public SignedBeaconBlockElectra( - @JsonProperty("message") final BeaconBlockElectra message, + public SignedBeaconBlockEip7594( + @JsonProperty("message") final BeaconBlockEip7594 message, @JsonProperty("signature") final BLSSignature signature) { super(message, signature); this.message = message; diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBlindedBeaconBlockElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBlindedBeaconBlockEip7594.java similarity index 77% rename from data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBlindedBeaconBlockElectra.java rename to data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBlindedBeaconBlockEip7594.java index b977a164978..35e57715f8b 100644 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/SignedBlindedBeaconBlockElectra.java +++ b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/eip7594/SignedBlindedBeaconBlockEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.api.schema.electra; +package tech.pegasys.teku.api.schema.eip7594; import static com.google.common.base.Preconditions.checkArgument; @@ -21,25 +21,25 @@ import tech.pegasys.teku.api.schema.SignedBeaconBlock; import tech.pegasys.teku.api.schema.interfaces.SignedBlock; -public class SignedBlindedBeaconBlockElectra extends SignedBeaconBlock implements SignedBlock { - private final BlindedBlockElectra message; +public class SignedBlindedBeaconBlockEip7594 extends SignedBeaconBlock implements SignedBlock { + private final BlindedBlockEip7594 message; - public SignedBlindedBeaconBlockElectra( + public SignedBlindedBeaconBlockEip7594( final tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock internalBlock) { super(internalBlock); checkArgument( internalBlock.getMessage().getBody().isBlinded(), "requires a signed blinded beacon block"); - this.message = new BlindedBlockElectra(internalBlock.getMessage()); + this.message = new BlindedBlockEip7594(internalBlock.getMessage()); } @Override - public BlindedBlockElectra getMessage() { + public BlindedBlockEip7594 getMessage() { return message; } @JsonCreator - public SignedBlindedBeaconBlockElectra( - @JsonProperty("message") final BlindedBlockElectra message, + public SignedBlindedBeaconBlockEip7594( + @JsonProperty("message") final BlindedBlockEip7594 message, @JsonProperty("signature") final BLSSignature signature) { super(message, signature); this.message = message; diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java deleted file mode 100644 index 9e36efb5542..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/BeaconStateElectra.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import static tech.pegasys.teku.api.schema.SchemaConstants.EXAMPLE_UINT64; - -import com.fasterxml.jackson.annotation.JsonProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.api.schema.BeaconBlockHeader; -import tech.pegasys.teku.api.schema.Checkpoint; -import tech.pegasys.teku.api.schema.Eth1Data; -import tech.pegasys.teku.api.schema.Fork; -import tech.pegasys.teku.api.schema.Validator; -import tech.pegasys.teku.api.schema.altair.BeaconStateAltair; -import tech.pegasys.teku.api.schema.altair.SyncCommittee; -import tech.pegasys.teku.api.schema.capella.HistoricalSummary; -import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; -import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderSchemaElectra; -import tech.pegasys.teku.spec.datastructures.state.SyncCommittee.SyncCommitteeSchema; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; - -public class BeaconStateElectra extends BeaconStateAltair { - - @JsonProperty("latest_execution_payload_header") - public final ExecutionPayloadHeaderElectra latestExecutionPayloadHeader; - - @JsonProperty("next_withdrawal_index") - @Schema(type = "string", example = EXAMPLE_UINT64) - public final UInt64 nextWithdrawalIndex; - - @JsonProperty("next_withdrawal_validator_index") - @Schema(type = "string", example = EXAMPLE_UINT64) - public final UInt64 nextWithdrawalValidatorIndex; - - @JsonProperty("historical_summaries") - public final List historicalSummaries; - - @JsonProperty("deposit_receipts_start_index") - public final UInt64 depositReceiptsStartIndex; - - @JsonProperty("deposit_balance_to_consume") - public final UInt64 depositBalanceToConsume; - - @JsonProperty("exit_balance_to_consume") - public final UInt64 exitBalanceToConsume; - - @JsonProperty("earliest_exit_epoch") - public final UInt64 earliestExitEpoch; - - @JsonProperty("consolidation_balance_to_consume") - public final UInt64 consolidationBalanceToConsume; - - @JsonProperty("earliest_consolidation_epoch") - public final UInt64 earliestConsolidationEpoch; - - @JsonProperty("pending_balance_deposits") - public final List pendingBalanceDeposits; - - @JsonProperty("pending_partial_withdrawals") - public final List pendingPartialWithdrawals; - - @JsonProperty("pending_consolidations") - public final List pendingConsolidations; - - public BeaconStateElectra( - @JsonProperty("genesis_time") final UInt64 genesisTime, - @JsonProperty("genesis_validators_root") final Bytes32 genesisValidatorsRoot, - @JsonProperty("slot") final UInt64 slot, - @JsonProperty("fork") final Fork fork, - @JsonProperty("latest_block_header") final BeaconBlockHeader latestBlockHeader, - @JsonProperty("block_roots") final List blockRoots, - @JsonProperty("state_roots") final List stateRoots, - @JsonProperty("historical_roots") final List historicalRoots, - @JsonProperty("eth1_data") final Eth1Data eth1Data, - @JsonProperty("eth1_data_votes") final List eth1DataVotes, - @JsonProperty("eth1_deposit_index") final UInt64 eth1DepositIndex, - @JsonProperty("validators") final List validators, - @JsonProperty("balances") final List balances, - @JsonProperty("randao_mixes") final List randaoMixes, - @JsonProperty("slashings") final List slashings, - @JsonProperty("previous_epoch_participation") final byte[] previousEpochParticipation, - @JsonProperty("current_epoch_participation") final byte[] currentEpochParticipation, - @JsonProperty("justification_bits") final SszBitvector justificationBits, - @JsonProperty("previous_justified_checkpoint") final Checkpoint previousJustifiedCheckpoint, - @JsonProperty("current_justified_checkpoint") final Checkpoint currentJustifiedCheckpoint, - @JsonProperty("finalized_checkpoint") final Checkpoint finalizedCheckpoint, - @JsonProperty("inactivity_scores") final List inactivityScores, - @JsonProperty("current_sync_committee") final SyncCommittee currentSyncCommittee, - @JsonProperty("next_sync_committee") final SyncCommittee nextSyncCommittee, - @JsonProperty("latest_execution_payload_header") - final ExecutionPayloadHeaderElectra latestExecutionPayloadHeader, - @JsonProperty("next_withdrawal_index") final UInt64 nextWithdrawalIndex, - @JsonProperty("next_withdrawal_validator_index") final UInt64 nextWithdrawalValidatorIndex, - @JsonProperty("historical_summaries") final List historicalSummaries, - @JsonProperty("deposit_receipts_start_index") final UInt64 depositReceiptsStartIndex, - @JsonProperty("deposit_balance_to_consume") final UInt64 depositBalanceToConsume, - @JsonProperty("exit_balance_to_consume") final UInt64 exitBalanceToConsume, - @JsonProperty("earliest_exit_epoch") final UInt64 earliestExitEpoch, - @JsonProperty("consolidation_balance_to_consume") final UInt64 consolidationBalanceToConsume, - @JsonProperty("earliest_consolidation_epoch") final UInt64 earliestConsolidationEpoch, - @JsonProperty("pending_balance_deposits") - final List pendingBalanceDeposits, - @JsonProperty("pending_partial_withdrawals") - final List pendingPartialWithdrawals, - @JsonProperty("pending_consolidations") - final List pendingConsolidations) { - super( - genesisTime, - genesisValidatorsRoot, - slot, - fork, - latestBlockHeader, - blockRoots, - stateRoots, - historicalRoots, - eth1Data, - eth1DataVotes, - eth1DepositIndex, - validators, - balances, - randaoMixes, - slashings, - previousEpochParticipation, - currentEpochParticipation, - justificationBits, - previousJustifiedCheckpoint, - currentJustifiedCheckpoint, - finalizedCheckpoint, - inactivityScores, - currentSyncCommittee, - nextSyncCommittee); - this.latestExecutionPayloadHeader = latestExecutionPayloadHeader; - this.nextWithdrawalIndex = nextWithdrawalIndex; - this.nextWithdrawalValidatorIndex = nextWithdrawalValidatorIndex; - this.historicalSummaries = historicalSummaries; - this.depositReceiptsStartIndex = depositReceiptsStartIndex; - this.depositBalanceToConsume = depositBalanceToConsume; - this.exitBalanceToConsume = exitBalanceToConsume; - this.earliestExitEpoch = earliestExitEpoch; - this.consolidationBalanceToConsume = consolidationBalanceToConsume; - this.earliestConsolidationEpoch = earliestConsolidationEpoch; - this.pendingBalanceDeposits = pendingBalanceDeposits; - this.pendingPartialWithdrawals = pendingPartialWithdrawals; - this.pendingConsolidations = pendingConsolidations; - } - - public BeaconStateElectra(final BeaconState beaconState) { - super(beaconState); - final tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra - .BeaconStateElectra - electra = beaconState.toVersionElectra().orElseThrow(); - this.latestExecutionPayloadHeader = - new ExecutionPayloadHeaderElectra(electra.getLatestExecutionPayloadHeader()); - this.nextWithdrawalIndex = electra.getNextWithdrawalIndex(); - this.nextWithdrawalValidatorIndex = electra.getNextWithdrawalValidatorIndex(); - this.historicalSummaries = - electra.getHistoricalSummaries().stream().map(HistoricalSummary::new).toList(); - this.depositReceiptsStartIndex = electra.getDepositReceiptsStartIndex(); - this.depositBalanceToConsume = electra.getDepositBalanceToConsume(); - this.exitBalanceToConsume = electra.getExitBalanceToConsume(); - this.earliestExitEpoch = electra.getEarliestExitEpoch(); - this.consolidationBalanceToConsume = electra.getConsolidationBalanceToConsume(); - this.earliestConsolidationEpoch = electra.getEarliestConsolidationEpoch(); - this.pendingBalanceDeposits = - electra.getPendingBalanceDeposits().stream().map(PendingBalanceDeposit::new).toList(); - this.pendingPartialWithdrawals = - electra.getPendingPartialWithdrawals().stream().map(PendingPartialWithdrawal::new).toList(); - this.pendingConsolidations = - electra.getPendingConsolidations().stream().map(PendingConsolidation::new).toList(); - } - - @Override - protected void applyAdditionalFields( - final MutableBeaconState state, final SpecVersion specVersion) { - state - .toMutableVersionElectra() - .ifPresent( - mutableBeaconStateElectra -> - applyElectraFields( - specVersion, - mutableBeaconStateElectra, - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getCurrentSyncCommitteeSchema(), - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getLastExecutionPayloadHeaderSchema(), - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getHistoricalSummariesSchema(), - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getPendingBalanceDepositsSchema(), - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getPendingPartialWithdrawalsSchema(), - BeaconStateSchemaElectra.required( - mutableBeaconStateElectra.getBeaconStateSchema()) - .getPendingConsolidationsSchema(), - this)); - } - - protected static void applyElectraFields( - final SpecVersion specVersion, - final MutableBeaconStateElectra state, - final SyncCommitteeSchema syncCommitteeSchema, - final ExecutionPayloadHeaderSchemaElectra executionPayloadHeaderSchema, - final SszListSchema< - tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary, ?> - historicalSummariesSchema, - final SszListSchema< - tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit, ?> - pendingBalanceDepositsSchema, - final SszListSchema< - tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal, - ?> - pendingPartialWithdrawalsSchema, - final SszListSchema< - tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation, ?> - pendingConsolidationsSchema, - final BeaconStateElectra instance) { - - BeaconStateAltair.applyAltairFields(state, syncCommitteeSchema, instance); - - state.setLatestExecutionPayloadHeader( - instance.latestExecutionPayloadHeader.asInternalExecutionPayloadHeader( - executionPayloadHeaderSchema)); - - state.setNextWithdrawalIndex(instance.nextWithdrawalIndex); - state.setNextWithdrawalValidatorIndex(instance.nextWithdrawalValidatorIndex); - state.setHistoricalSummaries( - historicalSummariesSchema.createFromElements( - instance.historicalSummaries.stream() - .map( - historicalSummary -> historicalSummary.asInternalHistoricalSummary(specVersion)) - .toList())); - state.setDepositReceiptsStartIndex(instance.depositReceiptsStartIndex); - state.setDepositBalanceToConsume(instance.depositBalanceToConsume); - state.setExitBalanceToConsume(instance.exitBalanceToConsume); - state.setEarliestExitEpoch(instance.earliestExitEpoch); - state.setConsolidationBalanceToConsume(instance.consolidationBalanceToConsume); - state.setEarliestConsolidationEpoch(instance.earliestConsolidationEpoch); - state.setPendingBalanceDeposits( - pendingBalanceDepositsSchema.createFromElements( - instance.pendingBalanceDeposits.stream() - .map( - pendingBalanceDeposit -> - pendingBalanceDeposit.asInternalPendingBalanceDeposit(specVersion)) - .toList())); - state.setPendingPartialWithdrawals( - pendingPartialWithdrawalsSchema.createFromElements( - instance.pendingPartialWithdrawals.stream() - .map( - pendingPartialWithdrawal -> - pendingPartialWithdrawal.asInternalPendingPartialWithdrawal(specVersion)) - .toList())); - state.setPendingConsolidations( - pendingConsolidationsSchema.createFromElements( - instance.pendingConsolidations.stream() - .map( - pendingConsolidation -> - pendingConsolidation.asInternalPendingConsolidation(specVersion)) - .toList())); - } -} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/DepositReceipt.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/DepositReceipt.java deleted file mode 100644 index 13dc8285286..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/DepositReceipt.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import com.fasterxml.jackson.annotation.JsonProperty; -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.api.schema.BLSPubKey; -import tech.pegasys.teku.api.schema.BLSSignature; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; - -public class DepositReceipt { - - @JsonProperty("pubkey") - private final BLSPubKey pubkey; - - @JsonProperty("withdrawal_credentials") - private final Bytes32 withdrawalCredentials; - - @JsonProperty("amount") - private final UInt64 amount; - - @JsonProperty("signature") - private final BLSSignature signature; - - @JsonProperty("index") - private final UInt64 index; - - public DepositReceipt( - @JsonProperty("pubkey") final BLSPubKey pubkey, - @JsonProperty("withdrawal_credentials") final Bytes32 withdrawalCredentials, - @JsonProperty("amount") final UInt64 amount, - @JsonProperty("signature") final BLSSignature signature, - @JsonProperty("index") final UInt64 index) { - this.pubkey = pubkey; - this.withdrawalCredentials = withdrawalCredentials; - this.amount = amount; - this.signature = signature; - this.index = index; - } - - public DepositReceipt( - final tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt - depositReceipt) { - this.pubkey = new BLSPubKey(depositReceipt.getPubkey()); - this.withdrawalCredentials = depositReceipt.getWithdrawalCredentials(); - this.amount = depositReceipt.getAmount(); - this.signature = new BLSSignature(depositReceipt.getSignature()); - this.index = depositReceipt.getIndex(); - } - - public tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt - asInternalDepositReceipt(final DepositReceiptSchema schema) { - return schema.create( - pubkey.asBLSPublicKey(), - withdrawalCredentials, - amount, - signature.asInternalBLSSignature(), - index); - } -} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionLayerExit.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionLayerExit.java deleted file mode 100644 index 10ae86235b9..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/ExecutionLayerExit.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import com.fasterxml.jackson.annotation.JsonProperty; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.ethereum.execution.types.Eth1Address; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; - -public class ExecutionLayerExit { - - @JsonProperty("source_address") - private final Eth1Address sourceAddress; - - @JsonProperty("validator_pubkey") - private final BLSPublicKey validatorPublicKey; - - public ExecutionLayerExit( - @JsonProperty("source_address") final Eth1Address sourceAddress, - @JsonProperty("validator_pubkey") final BLSPublicKey validatorPublicKey) { - this.sourceAddress = sourceAddress; - this.validatorPublicKey = validatorPublicKey; - } - - public ExecutionLayerExit( - final tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit - executionLayerExit) { - this.sourceAddress = - Eth1Address.fromBytes(executionLayerExit.getSourceAddress().getWrappedBytes()); - this.validatorPublicKey = executionLayerExit.getValidatorPublicKey(); - } - - public final tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit - asInternalExecutionLayerExit(final ExecutionLayerExitSchema schema) { - return schema.create(sourceAddress, validatorPublicKey); - } -} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java deleted file mode 100644 index b3105b77fe3..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingBalanceDeposit.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -public class PendingBalanceDeposit { - - @JsonProperty("index") - public final int index; - - @JsonProperty("amount") - public final UInt64 amount; - - public PendingBalanceDeposit( - @JsonProperty("index") int index, @JsonProperty("amount") UInt64 amount) { - this.index = index; - this.amount = amount; - } - - public PendingBalanceDeposit( - final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit - internalPendingBalanceDeposit) { - this.index = internalPendingBalanceDeposit.getIndex(); - this.amount = internalPendingBalanceDeposit.getAmount(); - } - - public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit - asInternalPendingBalanceDeposit(final SpecVersion spec) { - final Optional schemaDefinitionsElectra = - spec.getSchemaDefinitions().toVersionElectra(); - if (schemaDefinitionsElectra.isEmpty()) { - throw new IllegalArgumentException( - "Could not create PendingBalanceDeposit for pre-electra spec"); - } - return schemaDefinitionsElectra - .get() - .getPendingBalanceDepositSchema() - .create(SszUInt64.of(UInt64.valueOf(this.index)), SszUInt64.of(this.amount)); - } -} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java deleted file mode 100644 index cdf90d4400c..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingConsolidation.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -public class PendingConsolidation { - @JsonProperty("source_index") - public final int sourceIndex; - - @JsonProperty("target_index") - public final int targetIndex; - - PendingConsolidation( - @JsonProperty("source_index") int sourceIndex, - @JsonProperty("target_index") int targetIndex) { - this.sourceIndex = sourceIndex; - this.targetIndex = targetIndex; - } - - public PendingConsolidation( - final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation - internalPendingConsolidation) { - this.sourceIndex = internalPendingConsolidation.getSourceIndex(); - this.targetIndex = internalPendingConsolidation.getTargetIndex(); - } - - public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation - asInternalPendingConsolidation(final SpecVersion spec) { - final Optional schemaDefinitionsElectra = - spec.getSchemaDefinitions().toVersionElectra(); - if (schemaDefinitionsElectra.isEmpty()) { - throw new IllegalArgumentException( - "Could not create PendingBalanceDeposit for pre-electra spec"); - } - return schemaDefinitionsElectra - .get() - .getPendingConsolidationSchema() - .create( - SszUInt64.of(UInt64.valueOf(this.sourceIndex)), - SszUInt64.of(UInt64.valueOf(this.targetIndex))); - } -} diff --git a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java b/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java deleted file mode 100644 index 96f295c84d2..00000000000 --- a/data/serializer/src/main/java/tech/pegasys/teku/api/schema/electra/PendingPartialWithdrawal.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.api.schema.electra; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -public class PendingPartialWithdrawal { - @JsonProperty("index") - public final int index; - - @JsonProperty("amount") - public final UInt64 amount; - - @JsonProperty("withdrawable_epoch") - public final UInt64 withdrawableEpoch; - - public PendingPartialWithdrawal( - @JsonProperty("index") int index, - @JsonProperty("amount") UInt64 amount, - @JsonProperty("withdrawable_epoch") UInt64 withdrawableEpoch) { - this.index = index; - this.amount = amount; - this.withdrawableEpoch = withdrawableEpoch; - } - - public PendingPartialWithdrawal( - final tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal - pendingPartialWithdrawal) { - this.index = pendingPartialWithdrawal.getIndex(); - this.amount = pendingPartialWithdrawal.getAmount(); - this.withdrawableEpoch = pendingPartialWithdrawal.getWithdrawableEpoch(); - } - - public tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal - asInternalPendingPartialWithdrawal(final SpecVersion spec) { - final Optional schemaDefinitionsElectra = - spec.getSchemaDefinitions().toVersionElectra(); - if (schemaDefinitionsElectra.isEmpty()) { - throw new IllegalArgumentException( - "Could not create PendingBalanceDeposit for pre-electra spec"); - } - return schemaDefinitionsElectra - .get() - .getPendingPartialWithdrawalSchema() - .create( - SszUInt64.of(UInt64.valueOf(this.index)), - SszUInt64.of(this.amount), - SszUInt64.of(this.withdrawableEpoch)); - } -} diff --git a/data/serializer/src/property-test/java/tech/pegasys/teku/provider/JsonProviderPropertyTest.java b/data/serializer/src/property-test/java/tech/pegasys/teku/provider/JsonProviderPropertyTest.java index a5f4d6f249c..bd1a3e55d54 100644 --- a/data/serializer/src/property-test/java/tech/pegasys/teku/provider/JsonProviderPropertyTest.java +++ b/data/serializer/src/property-test/java/tech/pegasys/teku/provider/JsonProviderPropertyTest.java @@ -58,8 +58,8 @@ import tech.pegasys.teku.api.schema.capella.SignedBeaconBlockCapella; import tech.pegasys.teku.api.schema.deneb.BeaconStateDeneb; import tech.pegasys.teku.api.schema.deneb.SignedBeaconBlockDeneb; -import tech.pegasys.teku.api.schema.electra.BeaconStateElectra; -import tech.pegasys.teku.api.schema.electra.SignedBeaconBlockElectra; +import tech.pegasys.teku.api.schema.eip7594.BeaconStateEip7594; +import tech.pegasys.teku.api.schema.eip7594.SignedBeaconBlockEip7594; import tech.pegasys.teku.api.schema.phase0.BeaconStatePhase0; import tech.pegasys.teku.api.schema.phase0.SignedBeaconBlockPhase0; import tech.pegasys.teku.infrastructure.json.JsonUtil; @@ -91,8 +91,8 @@ public class JsonProviderPropertyTest { SignedBeaconBlockCapella.class, SpecMilestone.DENEB, SignedBeaconBlockDeneb.class, - SpecMilestone.ELECTRA, - SignedBeaconBlockElectra.class); + SpecMilestone.EIP7594, + SignedBeaconBlockEip7594.class); private static final Map> BEACON_STATE_CLASS_MAP = Map.of( @@ -106,8 +106,8 @@ public class JsonProviderPropertyTest { BeaconStateCapella.class, SpecMilestone.DENEB, BeaconStateDeneb.class, - SpecMilestone.ELECTRA, - BeaconStateElectra.class); + SpecMilestone.EIP7594, + BeaconStateEip7594.class); @Property void roundTripBytes32(@ForAll @Size(32) final byte[] value) throws JsonProcessingException { diff --git a/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java b/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java index b5797643054..649ba439c37 100644 --- a/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java +++ b/data/serializer/src/test/java/tech/pegasys/teku/api/schema/BeaconStateTest.java @@ -20,7 +20,7 @@ import tech.pegasys.teku.api.schema.bellatrix.BeaconStateBellatrix; import tech.pegasys.teku.api.schema.capella.BeaconStateCapella; import tech.pegasys.teku.api.schema.deneb.BeaconStateDeneb; -import tech.pegasys.teku.api.schema.electra.BeaconStateElectra; +import tech.pegasys.teku.api.schema.eip7594.BeaconStateEip7594; import tech.pegasys.teku.api.schema.phase0.BeaconStatePhase0; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecContext; @@ -41,7 +41,7 @@ public void shouldConvertToInternalObject(SpecContext ctx) { case BELLATRIX -> new BeaconStateBellatrix(beaconStateInternal); case CAPELLA -> new BeaconStateCapella(beaconStateInternal); case DENEB -> new BeaconStateDeneb(beaconStateInternal); - case ELECTRA -> new BeaconStateElectra(beaconStateInternal); + case EIP7594 -> new BeaconStateEip7594(beaconStateInternal); }; assertThat(beaconState.asInternalBeaconState(spec)).isEqualTo(beaconStateInternal); diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java index 7ef04910664..96b11054690 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/DefaultOperationProcessor.java @@ -14,19 +14,14 @@ package tech.pegasys.teku.reference.common.operations; import java.util.Optional; -import java.util.function.Supplier; import tech.pegasys.teku.bls.BLSSignatureVerifier; -import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BeaconBlockBodySchemaCapella; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectra; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -34,7 +29,6 @@ import tech.pegasys.teku.spec.datastructures.operations.SignedBlsToExecutionChange; import tech.pegasys.teku.spec.datastructures.operations.SignedVoluntaryExit; import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; import tech.pegasys.teku.spec.logic.versions.bellatrix.block.OptimisticExecutionPayloadExecutor; @@ -139,34 +133,4 @@ public void processWithdrawals( throws BlockProcessingException { spec.getBlockProcessor(state.getSlot()).processWithdrawals(state, payloadSummary); } - - @Override - public void processDepositReceipt( - final MutableBeaconState state, final DepositReceipt depositReceipt) - throws BlockProcessingException { - final SszList depositReceiptList = - BeaconBlockBodySchemaElectra.required(beaconBlockBodySchema) - .getExecutionPayloadSchema() - .getDepositReceiptsSchemaRequired() - .of(depositReceipt); - - spec.getBlockProcessor(state.getSlot()).processDepositReceipts(state, depositReceiptList); - } - - @Override - public void processExecutionLayerExit( - final MutableBeaconState state, final ExecutionLayerExit executionLayerExit) - throws BlockProcessingException { - final SszList exits = - BeaconBlockBodySchemaElectra.required(beaconBlockBodySchema) - .getExecutionPayloadSchema() - .getExecutionLayerExitsSchemaRequired() - .of(executionLayerExit); - final Supplier validatorExitContextSupplier = - spec.atSlot(state.getSlot()) - .beaconStateMutators() - .createValidatorExitContextSupplier(state); - spec.getBlockProcessor(state.getSlot()) - .processExecutionLayerExits(state, exits, validatorExitContextSupplier); - } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java index fe6be3115b9..de6475205a0 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationProcessor.java @@ -18,8 +18,6 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -64,10 +62,4 @@ void processBlsToExecutionChange( void processWithdrawals(MutableBeaconState state, ExecutionPayloadSummary payloadSummary) throws BlockProcessingException; - - void processDepositReceipt(final MutableBeaconState state, final DepositReceipt depositReceipt) - throws BlockProcessingException; - - void processExecutionLayerExit(MutableBeaconState state, ExecutionLayerExit executionLayerExit) - throws BlockProcessingException; } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java index 86803999d0a..b19ce0bb0d6 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java @@ -34,8 +34,6 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.BeaconBlockBodySchemaAltair; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -309,8 +307,6 @@ private void processOperation( } case BLS_TO_EXECUTION_CHANGE -> processBlsToExecutionChange(testDefinition, state, processor); case WITHDRAWAL -> processWithdrawal(testDefinition, state, processor); - case DEPOSIT_RECEIPT -> processDepositReceipt(testDefinition, state, processor); - case EXECUTION_LAYER_EXIT -> processExecutionLayerExit(testDefinition, state, processor); default -> throw new UnsupportedOperationException( "Operation " + operation + " not implemented in OperationTestExecutor"); } @@ -338,26 +334,6 @@ private void processBlsToExecutionChange( processor.processBlsToExecutionChange(state, blsToExecutionChange); } - private void processDepositReceipt( - final TestDefinition testDefinition, - final MutableBeaconState state, - final OperationProcessor processor) - throws BlockProcessingException { - final DepositReceipt depositReceipt = - loadSsz(testDefinition, dataFileName, DepositReceipt.SSZ_SCHEMA); - processor.processDepositReceipt(state, depositReceipt); - } - - private void processExecutionLayerExit( - final TestDefinition testDefinition, - final MutableBeaconState state, - final OperationProcessor processor) - throws BlockProcessingException { - final ExecutionLayerExit executionLayerExit = - loadSsz(testDefinition, dataFileName, ExecutionLayerExit.SSZ_SCHEMA); - processor.processExecutionLayerExit(state, executionLayerExit); - } - private SignedVoluntaryExit loadVoluntaryExit(final TestDefinition testDefinition) { return loadSsz(testDefinition, dataFileName, SignedVoluntaryExit.SSZ_SCHEMA); } diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/TestFork.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/TestFork.java index e69ddbe4b85..7204446920d 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/TestFork.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/TestFork.java @@ -19,5 +19,5 @@ public class TestFork { public static final String BELLATRIX = "bellatrix"; public static final String CAPELLA = "capella"; public static final String DENEB = "deneb"; - public static final String ELECTRA = "electra"; + public static final String EIP7594 = "eip7594"; } diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java index 656471c79c4..6160811ca70 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/ReferenceTestFinder.java @@ -36,7 +36,7 @@ public class ReferenceTestFinder { TestFork.BELLATRIX, TestFork.CAPELLA, TestFork.DENEB, - TestFork.ELECTRA); + TestFork.EIP7594); @MustBeClosed public static Stream findReferenceTests() throws IOException { diff --git a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java index 951bbe64e55..bfce0401d5b 100644 --- a/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java +++ b/eth-tests/src/main/java/tech/pegasys/teku/ethtests/finder/TestDefinition.java @@ -72,7 +72,7 @@ private void createSpec() { case TestFork.BELLATRIX -> SpecMilestone.BELLATRIX; case TestFork.CAPELLA -> SpecMilestone.CAPELLA; case TestFork.DENEB -> SpecMilestone.DENEB; - case TestFork.ELECTRA -> SpecMilestone.ELECTRA; + case TestFork.EIP7594 -> SpecMilestone.EIP7594; default -> throw new IllegalArgumentException("Unknown fork: " + fork); }; spec = TestSpecFactory.create(milestone, network); diff --git a/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClientTest.java b/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClientTest.java index dba8065fb1a..d6829baa2c2 100644 --- a/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClientTest.java +++ b/ethereum/executionclient/src/integration-test/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClientTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.mock; import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; import static tech.pegasys.teku.spec.SpecMilestone.DENEB; -import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.EIP7594; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonFactory; @@ -44,14 +44,12 @@ import okhttp3.mockwebserver.RecordedRequest; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; -import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.TestTemplate; import tech.pegasys.teku.ethereum.events.ExecutionClientEventsChannel; import tech.pegasys.teku.ethereum.executionclient.schema.ClientVersionV1; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; @@ -72,7 +70,7 @@ import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; import tech.pegasys.teku.spec.util.DataStructureUtil; -@TestSpecContext(milestone = {CAPELLA, DENEB, ELECTRA}) +@TestSpecContext(milestone = {CAPELLA, DENEB, EIP7594}) public class Web3JExecutionEngineClientTest { private static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(1); @@ -301,66 +299,6 @@ public void newPayloadV3_shouldBuildRequestAndResponseSuccessfully() { .isEqualTo(parentBeaconBlockRoot.toHexString()); } - @TestTemplate - @SuppressWarnings("unchecked") - public void newPayloadV4_shouldBuildRequestAndResponseSuccessfully() { - assumeThat(specMilestone).isGreaterThanOrEqualTo(ELECTRA); - final Bytes32 latestValidHash = dataStructureUtil.randomBytes32(); - final PayloadStatus payloadStatusResponse = - PayloadStatus.valid(Optional.of(latestValidHash), Optional.empty()); - - final String bodyResponse = - "{\"jsonrpc\": \"2.0\", \"id\": 0, \"result\":" - + "{ \"status\": \"VALID\", \"latestValidHash\": \"" - + latestValidHash - + "\", \"validationError\": null}}"; - - mockSuccessfulResponse(bodyResponse); - - final ExecutionPayload executionPayload = dataStructureUtil.randomExecutionPayload(); - final ExecutionPayloadV4 executionPayloadV4 = - ExecutionPayloadV4.fromInternalExecutionPayload(executionPayload); - - final List blobVersionedHashes = dataStructureUtil.randomVersionedHashes(3); - final Bytes32 parentBeaconBlockRoot = dataStructureUtil.randomBytes32(); - - final SafeFuture> futureResponse = - eeClient.newPayloadV4(executionPayloadV4, blobVersionedHashes, parentBeaconBlockRoot); - - assertThat(futureResponse) - .succeedsWithin(1, TimeUnit.SECONDS) - .matches( - response -> - response.getPayload().asInternalExecutionPayload().equals(payloadStatusResponse)); - - final Map requestData = takeRequest(); - verifyJsonRpcMethodCall(requestData, "engine_newPayloadV4"); - - final Map executionPayloadV4Parameter = - (Map) ((List) requestData.get("params")).get(0); - // 19 fields in ExecutionPayloadV4 - assertThat(executionPayloadV4Parameter).hasSize(19); - // sanity check - assertThat(executionPayloadV4Parameter.get("parentHash")) - .isEqualTo(executionPayloadV4.parentHash.toHexString()); - - assertThat(executionPayloadV4Parameter.get("depositReceipts")) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .hasSameSizeAs(executionPayloadV4.depositReceipts); - assertThat(executionPayloadV4Parameter.get("exits")) - .asInstanceOf(InstanceOfAssertFactories.LIST) - .hasSameSizeAs(executionPayloadV4.exits); - assertThat(((List) requestData.get("params")).get(1)) - .asInstanceOf(LIST) - .containsExactlyElementsOf( - blobVersionedHashes.stream() - .map(VersionedHash::toHexString) - .collect(Collectors.toList())); - assertThat(((List) requestData.get("params")).get(2)) - .asString() - .isEqualTo(parentBeaconBlockRoot.toHexString()); - } - private void mockSuccessfulResponse(final String responseBody) { mockWebServer.enqueue( new MockResponse() diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java index 02ee14282b5..c4e94d8b45e 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ExecutionEngineClient.java @@ -20,12 +20,10 @@ import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV2; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV2Response; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV3Response; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; @@ -49,8 +47,6 @@ public interface ExecutionEngineClient { SafeFuture> getPayloadV3(Bytes8 payloadId); - SafeFuture> getPayloadV4(Bytes8 payloadId); - SafeFuture> newPayloadV1(ExecutionPayloadV1 executionPayload); SafeFuture> newPayloadV2(ExecutionPayloadV2 executionPayload); @@ -60,11 +56,6 @@ SafeFuture> newPayloadV3( List blobVersionedHashes, Bytes32 parentBeaconBlockRoot); - SafeFuture> newPayloadV4( - ExecutionPayloadV4 executionPayload, - List blobVersionedHashes, - Bytes32 parentBeaconBlockRoot); - SafeFuture> forkChoiceUpdatedV1( ForkChoiceStateV1 forkChoiceState, Optional payloadAttributes); diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java index 964c527cd4b..45bb127707a 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/ThrottlingExecutionEngineClient.java @@ -21,12 +21,10 @@ import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV2; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV2Response; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV3Response; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; @@ -81,11 +79,6 @@ public SafeFuture> getPayloadV3(final Bytes8 payl return taskQueue.queueTask(() -> delegate.getPayloadV3(payloadId)); } - @Override - public SafeFuture> getPayloadV4(final Bytes8 payloadId) { - return taskQueue.queueTask(() -> delegate.getPayloadV4(payloadId)); - } - @Override public SafeFuture> newPayloadV1( final ExecutionPayloadV1 executionPayload) { @@ -107,15 +100,6 @@ public SafeFuture> newPayloadV3( () -> delegate.newPayloadV3(executionPayload, blobVersionedHashes, parentBeaconBlockRoot)); } - @Override - public SafeFuture> newPayloadV4( - final ExecutionPayloadV4 executionPayload, - final List blobVersionedHashes, - final Bytes32 parentBeaconBlockRoot) { - return taskQueue.queueTask( - () -> delegate.newPayloadV4(executionPayload, blobVersionedHashes, parentBeaconBlockRoot)); - } - @Override public SafeFuture> forkChoiceUpdatedV1( final ForkChoiceStateV1 forkChoiceState, diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java deleted file mode 100644 index cedf1fbe2aa..00000000000 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2023 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.methods; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; -import tech.pegasys.teku.ethereum.executionclient.response.ResponseUnwrapper; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSchema; -import tech.pegasys.teku.spec.datastructures.execution.BlobsBundle; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; -import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; -import tech.pegasys.teku.spec.schemas.SchemaDefinitions; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; - -public class EngineGetPayloadV4 extends AbstractEngineJsonRpcMethod { - - private static final Logger LOG = LogManager.getLogger(); - - private final Spec spec; - - public EngineGetPayloadV4(final ExecutionEngineClient executionEngineClient, final Spec spec) { - super(executionEngineClient); - this.spec = spec; - } - - @Override - public String getName() { - return EngineApiMethod.ENGINE_GET_PAYLOAD.getName(); - } - - @Override - public int getVersion() { - return 4; - } - - @Override - public SafeFuture execute(final JsonRpcRequestParams params) { - final ExecutionPayloadContext executionPayloadContext = - params.getRequiredParameter(0, ExecutionPayloadContext.class); - final UInt64 slot = params.getRequiredParameter(1, UInt64.class); - - LOG.trace( - "Calling {}(payloadId={}, slot={})", - getVersionedName(), - executionPayloadContext.getPayloadId(), - slot); - - return executionEngineClient - .getPayloadV4(executionPayloadContext.getPayloadId()) - .thenApply(ResponseUnwrapper::unwrapExecutionClientResponseOrThrow) - .thenApply( - response -> { - final SchemaDefinitions schemaDefinitions = spec.atSlot(slot).getSchemaDefinitions(); - final ExecutionPayloadSchema payloadSchema = - SchemaDefinitionsBellatrix.required(schemaDefinitions) - .getExecutionPayloadSchema(); - final ExecutionPayload executionPayload = - response.executionPayload.asInternalExecutionPayload(payloadSchema); - final BlobsBundle blobsBundle = getBlobsBundle(response, schemaDefinitions); - return new GetPayloadResponse( - executionPayload, - response.blockValue, - blobsBundle, - response.shouldOverrideBuilder); - }) - .thenPeek( - getPayloadResponse -> - LOG.trace( - "Response {}(payloadId={}, slot={}) -> {}", - getVersionedName(), - executionPayloadContext.getPayloadId(), - slot, - getPayloadResponse)); - } - - private BlobsBundle getBlobsBundle( - final GetPayloadV4Response response, final SchemaDefinitions schemaDefinitions) { - final BlobSchema blobSchema = - SchemaDefinitionsDeneb.required(schemaDefinitions).getBlobSchema(); - return response.blobsBundle.asInternalBlobsBundle(blobSchema); - } -} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4.java deleted file mode 100644 index 250ddfcc8f9..00000000000 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2023 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.methods; - -import java.util.List; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; -import tech.pegasys.teku.ethereum.executionclient.response.ResponseUnwrapper; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; -import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.executionlayer.PayloadStatus; -import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; - -public class EngineNewPayloadV4 extends AbstractEngineJsonRpcMethod { - - private static final Logger LOG = LogManager.getLogger(); - - public EngineNewPayloadV4(final ExecutionEngineClient executionEngineClient) { - super(executionEngineClient); - } - - @Override - public String getName() { - return EngineApiMethod.ENGINE_NEW_PAYLOAD.getName(); - } - - @Override - public int getVersion() { - return 4; - } - - @Override - public SafeFuture execute(final JsonRpcRequestParams params) { - final ExecutionPayload executionPayload = - params.getRequiredParameter(0, ExecutionPayload.class); - final List blobVersionedHashes = - params.getRequiredListParameter(1, VersionedHash.class); - final Bytes32 parentBeaconBlockRoot = params.getRequiredParameter(2, Bytes32.class); - - LOG.trace( - "Calling {}(executionPayload={}, blobVersionedHashes={}, parentBeaconBlockRoot={})", - getVersionedName(), - executionPayload, - blobVersionedHashes, - parentBeaconBlockRoot); - - final ExecutionPayloadV4 executionPayloadV4 = - ExecutionPayloadV4.fromInternalExecutionPayload(executionPayload); - return executionEngineClient - .newPayloadV4(executionPayloadV4, blobVersionedHashes, parentBeaconBlockRoot) - .thenApply(ResponseUnwrapper::unwrapExecutionClientResponseOrThrow) - .thenApply(PayloadStatusV1::asInternalExecutionPayload) - .thenPeek( - payloadStatus -> - LOG.trace( - "Response {}(executionPayload={}) -> {}", - getVersionedName(), - executionPayload, - payloadStatus)) - .exceptionally(PayloadStatus::failedExecution); - } -} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java index f45056c92a0..6a4815b58c8 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/metrics/MetricRecordingExecutionEngineClient.java @@ -23,12 +23,10 @@ import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV2; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV2Response; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV3Response; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; @@ -61,9 +59,7 @@ public class MetricRecordingExecutionEngineClient extends MetricRecordingAbstrac public static final String FORKCHOICE_UPDATED_WITH_ATTRIBUTES_V3_METHOD = "forkchoice_updated_with_attributesV3"; public static final String GET_PAYLOAD_V3_METHOD = "get_payloadV3"; - public static final String GET_PAYLOAD_V4_METHOD = "get_payloadV4"; public static final String NEW_PAYLOAD_V3_METHOD = "new_payloadV3"; - public static final String NEW_PAYLOAD_V4_METHOD = "new_payloadV4"; public static final String EXCHANGE_CAPABILITIES_METHOD = "exchange_capabilities"; public static final String GET_CLIENT_VERSION_V1_METHOD = "get_client_versionV1"; @@ -110,11 +106,6 @@ public SafeFuture> getPayloadV3(final Bytes8 payl return countRequest(() -> delegate.getPayloadV3(payloadId), GET_PAYLOAD_V3_METHOD); } - @Override - public SafeFuture> getPayloadV4(final Bytes8 payloadId) { - return countRequest(() -> delegate.getPayloadV4(payloadId), GET_PAYLOAD_V4_METHOD); - } - @Override public SafeFuture> newPayloadV1( final ExecutionPayloadV1 executionPayload) { @@ -137,16 +128,6 @@ public SafeFuture> newPayloadV3( NEW_PAYLOAD_V3_METHOD); } - @Override - public SafeFuture> newPayloadV4( - final ExecutionPayloadV4 executionPayload, - final List blobVersionedHashes, - final Bytes32 parentBeaconBlockRoot) { - return countRequest( - () -> delegate.newPayloadV4(executionPayload, blobVersionedHashes, parentBeaconBlockRoot), - NEW_PAYLOAD_V4_METHOD); - } - @Override public SafeFuture> forkChoiceUpdatedV1( final ForkChoiceStateV1 forkChoiceState, diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/ExecutionPayloadV4.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/ExecutionPayloadV4.java deleted file mode 100644 index 9b14eb870ab..00000000000 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/ExecutionPayloadV4.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.schema; - -import static com.google.common.base.Preconditions.checkNotNull; - -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import org.apache.tuweni.bytes.Bytes; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.infrastructure.ssz.SszList; -import tech.pegasys.teku.infrastructure.ssz.collections.impl.SszByteListImpl; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadDeneb; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; - -public class ExecutionPayloadV4 extends ExecutionPayloadV3 { - public final List depositReceipts; - public final List exits; - - public ExecutionPayloadV4( - @JsonProperty("parentHash") Bytes32 parentHash, - @JsonProperty("feeRecipient") Bytes20 feeRecipient, - @JsonProperty("stateRoot") Bytes32 stateRoot, - @JsonProperty("receiptsRoot") Bytes32 receiptsRoot, - @JsonProperty("logsBloom") Bytes logsBloom, - @JsonProperty("prevRandao") Bytes32 prevRandao, - @JsonProperty("blockNumber") UInt64 blockNumber, - @JsonProperty("gasLimit") UInt64 gasLimit, - @JsonProperty("gasUsed") UInt64 gasUsed, - @JsonProperty("timestamp") UInt64 timestamp, - @JsonProperty("extraData") Bytes extraData, - @JsonProperty("baseFeePerGas") UInt256 baseFeePerGas, - @JsonProperty("blockHash") Bytes32 blockHash, - @JsonProperty("transactions") List transactions, - @JsonProperty("withdrawals") List withdrawals, - @JsonProperty("blobGasUsed") UInt64 blobGasUsed, - @JsonProperty("excessBlobGas") UInt64 excessBlobGas, - @JsonProperty("depositReceipts") List depositReceipts, - @JsonProperty("exits") List exits) { - super( - parentHash, - feeRecipient, - stateRoot, - receiptsRoot, - logsBloom, - prevRandao, - blockNumber, - gasLimit, - gasUsed, - timestamp, - extraData, - baseFeePerGas, - blockHash, - transactions, - withdrawals, - blobGasUsed, - excessBlobGas); - this.depositReceipts = depositReceipts; - this.exits = exits; - } - - public static ExecutionPayloadV4 fromInternalExecutionPayload( - final ExecutionPayload executionPayload) { - final List withdrawalsList = - getWithdrawals(executionPayload.getOptionalWithdrawals()); - return new ExecutionPayloadV4( - executionPayload.getParentHash(), - executionPayload.getFeeRecipient(), - executionPayload.getStateRoot(), - executionPayload.getReceiptsRoot(), - executionPayload.getLogsBloom(), - executionPayload.getPrevRandao(), - executionPayload.getBlockNumber(), - executionPayload.getGasLimit(), - executionPayload.getGasUsed(), - executionPayload.getTimestamp(), - executionPayload.getExtraData(), - executionPayload.getBaseFeePerGas(), - executionPayload.getBlockHash(), - executionPayload.getTransactions().stream().map(SszByteListImpl::getBytes).toList(), - withdrawalsList, - executionPayload.toVersionDeneb().map(ExecutionPayloadDeneb::getBlobGasUsed).orElse(null), - executionPayload.toVersionDeneb().map(ExecutionPayloadDeneb::getExcessBlobGas).orElse(null), - getDepositReceipts( - executionPayload.toVersionElectra().map(ExecutionPayloadElectra::getDepositReceipts)), - getExits(executionPayload.toVersionElectra().map(ExecutionPayloadElectra::getExits))); - } - - @Override - protected ExecutionPayloadBuilder applyToBuilder( - final ExecutionPayloadSchema executionPayloadSchema, - final ExecutionPayloadBuilder builder) { - return super.applyToBuilder(executionPayloadSchema, builder) - .depositReceipts( - () -> - checkNotNull(depositReceipts, "depositReceipts not provided when required").stream() - .map( - depositReceiptV1 -> - createInternalDepositReceipt(depositReceiptV1, executionPayloadSchema)) - .toList()) - .exits( - () -> - checkNotNull(exits, "exits not provided when required").stream() - .map(exitV1 -> createInternalExit(exitV1, executionPayloadSchema)) - .toList()); - } - - private DepositReceipt createInternalDepositReceipt( - final DepositReceiptV1 depositReceiptV1, - final ExecutionPayloadSchema executionPayloadSchema) { - return executionPayloadSchema - .getDepositReceiptSchemaRequired() - .create( - BLSPublicKey.fromBytesCompressed(depositReceiptV1.pubkey), - depositReceiptV1.withdrawalCredentials, - depositReceiptV1.amount, - BLSSignature.fromBytesCompressed(depositReceiptV1.signature), - depositReceiptV1.index); - } - - private ExecutionLayerExit createInternalExit( - final ExitV1 exitV1, final ExecutionPayloadSchema executionPayloadSchema) { - return executionPayloadSchema - .getExecutionLayerExitSchemaRequired() - .create(exitV1.sourceAddress, BLSPublicKey.fromBytesCompressed(exitV1.validatorPublicKey)); - } - - public static List getDepositReceipts( - final Optional> maybeDepositReceipts) { - if (maybeDepositReceipts.isEmpty()) { - return List.of(); - } - - final List depositReceipts = new ArrayList<>(); - - for (DepositReceipt depositReceipt : maybeDepositReceipts.get()) { - depositReceipts.add( - new DepositReceiptV1( - depositReceipt.getPubkey().toBytesCompressed(), - depositReceipt.getWithdrawalCredentials(), - depositReceipt.getAmount(), - depositReceipt.getSignature().toBytesCompressed(), - depositReceipt.getIndex())); - } - return depositReceipts; - } - - public static List getExits(final Optional> maybeExits) { - if (maybeExits.isEmpty()) { - return List.of(); - } - - final List exits = new ArrayList<>(); - - for (ExecutionLayerExit exit : maybeExits.get()) { - exits.add( - new ExitV1(exit.getSourceAddress(), exit.getValidatorPublicKey().toBytesCompressed())); - } - return exits; - } -} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java deleted file mode 100644 index 09d959dbe47..00000000000 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/schema/GetPayloadV4Response.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.schema; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.apache.tuweni.units.bigints.UInt256; -import tech.pegasys.teku.ethereum.executionclient.serialization.UInt256AsHexDeserializer; -import tech.pegasys.teku.ethereum.executionclient.serialization.UInt256AsHexSerializer; - -public class GetPayloadV4Response { - public final ExecutionPayloadV4 executionPayload; - - @JsonSerialize(using = UInt256AsHexSerializer.class) - @JsonDeserialize(using = UInt256AsHexDeserializer.class) - public final UInt256 blockValue; - - public final BlobsBundleV1 blobsBundle; - - public final boolean shouldOverrideBuilder; - - public GetPayloadV4Response( - @JsonProperty("executionPayload") final ExecutionPayloadV4 executionPayload, - @JsonProperty("blockValue") final UInt256 blockValue, - @JsonProperty("blobsBundle") final BlobsBundleV1 blobsBundle, - @JsonProperty("shouldOverrideBuilder") final boolean shouldOverrideBuilder) { - this.executionPayload = executionPayload; - this.blockValue = blockValue; - this.blobsBundle = blobsBundle; - this.shouldOverrideBuilder = shouldOverrideBuilder; - } -} diff --git a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java index d27e5307b04..633cf0c55a6 100644 --- a/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java +++ b/ethereum/executionclient/src/main/java/tech/pegasys/teku/ethereum/executionclient/web3j/Web3JExecutionEngineClient.java @@ -31,12 +31,10 @@ import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV1; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV2; import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV2Response; import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV3Response; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV1; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV2; import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; @@ -122,17 +120,6 @@ public SafeFuture> getPayloadV3(final Bytes8 payl return web3JClient.doRequest(web3jRequest, EL_ENGINE_NON_BLOCK_EXECUTION_TIMEOUT); } - @Override - public SafeFuture> getPayloadV4(final Bytes8 payloadId) { - final Request web3jRequest = - new Request<>( - "engine_getPayloadV4", - Collections.singletonList(payloadId.toHexString()), - web3JClient.getWeb3jService(), - GetPayloadV4Web3jResponse.class); - return web3JClient.doRequest(web3jRequest, EL_ENGINE_NON_BLOCK_EXECUTION_TIMEOUT); - } - @Override public SafeFuture> newPayloadV1(ExecutionPayloadV1 executionPayload) { final Request web3jRequest = @@ -173,23 +160,6 @@ public SafeFuture> newPayloadV3( return web3JClient.doRequest(web3jRequest, EL_ENGINE_BLOCK_EXECUTION_TIMEOUT); } - @Override - public SafeFuture> newPayloadV4( - final ExecutionPayloadV4 executionPayload, - final List blobVersionedHashes, - final Bytes32 parentBeaconBlockRoot) { - final List expectedBlobVersionedHashes = - blobVersionedHashes.stream().map(VersionedHash::toHexString).toList(); - final Request web3jRequest = - new Request<>( - "engine_newPayloadV4", - list( - executionPayload, expectedBlobVersionedHashes, parentBeaconBlockRoot.toHexString()), - web3JClient.getWeb3jService(), - PayloadStatusV1Web3jResponse.class); - return web3JClient.doRequest(web3jRequest, EL_ENGINE_BLOCK_EXECUTION_TIMEOUT); - } - @Override public SafeFuture> forkChoiceUpdatedV1( ForkChoiceStateV1 forkChoiceState, Optional payloadAttributes) { @@ -260,9 +230,6 @@ static class GetPayloadV2Web3jResponse static class GetPayloadV3Web3jResponse extends org.web3j.protocol.core.Response {} - static class GetPayloadV4Web3jResponse - extends org.web3j.protocol.core.Response {} - static class PayloadStatusV1Web3jResponse extends org.web3j.protocol.core.Response {} diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java deleted file mode 100644 index 8ae7b63e7b3..00000000000 --- a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineGetPayloadV4Test.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2023 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.methods; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; -import tech.pegasys.teku.ethereum.executionclient.response.InvalidRemoteResponseException; -import tech.pegasys.teku.ethereum.executionclient.schema.BlobsBundleV1; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; -import tech.pegasys.teku.ethereum.executionclient.schema.Response; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.datastructures.execution.BlobsBundle; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; -import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -class EngineGetPayloadV4Test { - - private final Spec spec = TestSpecFactory.createMinimalElectra(); - private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - private final ExecutionEngineClient executionEngineClient = mock(ExecutionEngineClient.class); - private EngineGetPayloadV4 jsonRpcMethod; - - @BeforeEach - public void setUp() { - jsonRpcMethod = new EngineGetPayloadV4(executionEngineClient, spec); - } - - @Test - public void shouldReturnExpectedNameAndVersion() { - assertThat(jsonRpcMethod.getName()).isEqualTo("engine_getPayload"); - assertThat(jsonRpcMethod.getVersion()).isEqualTo(4); - assertThat(jsonRpcMethod.getVersionedName()).isEqualTo("engine_getPayloadV4"); - } - - @Test - public void executionPayloadContextParamIsRequired() { - final JsonRpcRequestParams params = new JsonRpcRequestParams.Builder().build(); - - assertThatThrownBy(() -> jsonRpcMethod.execute(params)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Missing required parameter at index 0"); - - verifyNoInteractions(executionEngineClient); - } - - @Test - public void slotParamIsRequired() { - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); - - final JsonRpcRequestParams params = - new JsonRpcRequestParams.Builder().add(executionPayloadContext).build(); - - assertThatThrownBy(() -> jsonRpcMethod.execute(params)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Missing required parameter at index 1"); - - verifyNoInteractions(executionEngineClient); - } - - @Test - public void shouldReturnFailedExecutionWhenEngineClientRequestFails() { - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); - final String errorResponseFromClient = "error!"; - - when(executionEngineClient.getPayloadV4(any())) - .thenReturn(dummyFailedResponse(errorResponseFromClient)); - - final JsonRpcRequestParams params = - new JsonRpcRequestParams.Builder().add(executionPayloadContext).add(UInt64.ZERO).build(); - - assertThat(jsonRpcMethod.execute(params)) - .failsWithin(1, TimeUnit.SECONDS) - .withThrowableOfType(ExecutionException.class) - .withRootCauseInstanceOf(InvalidRemoteResponseException.class) - .withMessageContaining( - "Invalid remote response from the execution client: %s", errorResponseFromClient); - } - - @Test - public void shouldCallGetPayloadV4AndParseResponseSuccessfully() { - final ExecutionPayloadContext executionPayloadContext = - dataStructureUtil.randomPayloadExecutionContext(false); - final UInt256 blockValue = UInt256.MAX_VALUE; - final BlobsBundle blobsBundle = dataStructureUtil.randomBlobsBundle(); - final ExecutionPayload executionPayloadElectra = dataStructureUtil.randomExecutionPayload(); - assertThat(executionPayloadElectra).isInstanceOf(ExecutionPayloadElectra.class); - - when(executionEngineClient.getPayloadV4(eq(executionPayloadContext.getPayloadId()))) - .thenReturn(dummySuccessfulResponse(executionPayloadElectra, blockValue, blobsBundle)); - - final JsonRpcRequestParams params = - new JsonRpcRequestParams.Builder().add(executionPayloadContext).add(UInt64.ZERO).build(); - - jsonRpcMethod = new EngineGetPayloadV4(executionEngineClient, spec); - - final GetPayloadResponse expectedGetPayloadResponse = - new GetPayloadResponse(executionPayloadElectra, blockValue, blobsBundle, false); - assertThat(jsonRpcMethod.execute(params)).isCompletedWithValue(expectedGetPayloadResponse); - - verify(executionEngineClient).getPayloadV4(eq(executionPayloadContext.getPayloadId())); - verifyNoMoreInteractions(executionEngineClient); - } - - private SafeFuture> dummySuccessfulResponse( - final ExecutionPayload executionPayload, - final UInt256 blockValue, - final BlobsBundle blobsBundle) { - return SafeFuture.completedFuture( - new Response<>( - new GetPayloadV4Response( - ExecutionPayloadV4.fromInternalExecutionPayload(executionPayload), - blockValue, - BlobsBundleV1.fromInternalBlobsBundle(blobsBundle), - false))); - } - - private SafeFuture> dummyFailedResponse( - final String errorMessage) { - return SafeFuture.completedFuture(Response.withErrorMessage(errorMessage)); - } -} diff --git a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4Test.java b/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4Test.java deleted file mode 100644 index c27ea6f9cc6..00000000000 --- a/ethereum/executionclient/src/test/java/tech/pegasys/teku/ethereum/executionclient/methods/EngineNewPayloadV4Test.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2023 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionclient.methods; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.apache.tuweni.bytes.Bytes32; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.ethereum.executionclient.ExecutionEngineClient; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; -import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; -import tech.pegasys.teku.ethereum.executionclient.schema.Response; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.executionlayer.ExecutionPayloadStatus; -import tech.pegasys.teku.spec.executionlayer.PayloadStatus; -import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -class EngineNewPayloadV4Test { - - private final Spec spec = TestSpecFactory.createMinimalElectra(); - private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - private final ExecutionEngineClient executionEngineClient = mock(ExecutionEngineClient.class); - private EngineNewPayloadV4 jsonRpcMethod; - - @BeforeEach - public void setUp() { - jsonRpcMethod = new EngineNewPayloadV4(executionEngineClient); - } - - @Test - public void shouldReturnExpectedNameAndVersion() { - assertThat(jsonRpcMethod.getName()).isEqualTo("engine_newPayload"); - assertThat(jsonRpcMethod.getVersion()).isEqualTo(4); - assertThat(jsonRpcMethod.getVersionedName()).isEqualTo("engine_newPayloadV4"); - } - - @Test - public void executionPayloadParamIsRequired() { - final JsonRpcRequestParams params = new JsonRpcRequestParams.Builder().build(); - - assertThatThrownBy(() -> jsonRpcMethod.execute(params)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Missing required parameter at index 0"); - - verifyNoInteractions(executionEngineClient); - } - - @Test - public void shouldReturnFailedExecutionWhenEngineClientRequestFails() { - final ExecutionPayload executionPayload = dataStructureUtil.randomExecutionPayload(); - final List blobVersionedHashes = dataStructureUtil.randomVersionedHashes(3); - final Bytes32 parentBeaconBlockRoot = dataStructureUtil.randomBytes32(); - final String errorResponseFromClient = "error!"; - - when(executionEngineClient.newPayloadV4(any(), any(), any())) - .thenReturn(dummyFailedResponse(errorResponseFromClient)); - - final JsonRpcRequestParams params = - new JsonRpcRequestParams.Builder() - .add(executionPayload) - .add(blobVersionedHashes) - .add(parentBeaconBlockRoot) - .build(); - - assertThat(jsonRpcMethod.execute(params)) - .succeedsWithin(1, TimeUnit.SECONDS) - .matches(PayloadStatus::hasFailedExecution); - } - - @Test - public void shouldCallNewPayloadV4WithExecutionPayloadV4AndBlobVersionedHashes() { - final ExecutionPayload executionPayload = dataStructureUtil.randomExecutionPayload(); - final List blobVersionedHashes = dataStructureUtil.randomVersionedHashes(4); - final Bytes32 parentBeaconBlockRoot = dataStructureUtil.randomBytes32(); - - final ExecutionPayloadV4 executionPayloadV4 = - ExecutionPayloadV4.fromInternalExecutionPayload(executionPayload); - assertThat(executionPayloadV4).isExactlyInstanceOf(ExecutionPayloadV4.class); - - jsonRpcMethod = new EngineNewPayloadV4(executionEngineClient); - - when(executionEngineClient.newPayloadV4( - executionPayloadV4, blobVersionedHashes, parentBeaconBlockRoot)) - .thenReturn(dummySuccessfulResponse()); - - final JsonRpcRequestParams params = - new JsonRpcRequestParams.Builder() - .add(executionPayload) - .add(blobVersionedHashes) - .add(parentBeaconBlockRoot) - .build(); - - assertThat(jsonRpcMethod.execute(params)).isCompleted(); - - verify(executionEngineClient) - .newPayloadV4(eq(executionPayloadV4), eq(blobVersionedHashes), eq(parentBeaconBlockRoot)); - verifyNoMoreInteractions(executionEngineClient); - } - - private SafeFuture> dummySuccessfulResponse() { - return SafeFuture.completedFuture( - new Response<>( - new PayloadStatusV1( - ExecutionPayloadStatus.ACCEPTED, dataStructureUtil.randomBytes32(), null))); - } - - private SafeFuture> dummyFailedResponse(final String errorMessage) { - return SafeFuture.completedFuture(Response.withErrorMessage(errorMessage)); - } -} diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java index 22b8d6aab57..b4dc559a640 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java @@ -32,12 +32,10 @@ import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV1; import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV2; import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV4; import tech.pegasys.teku.ethereum.executionclient.methods.EngineJsonRpcMethod; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV1; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV2; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV4; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.util.ForkAndSpecMilestone; @@ -73,8 +71,7 @@ public MilestoneBasedEngineJsonRpcMethodsResolver( case DENEB: methodsByMilestone.put(milestone, denebSupportedMethods()); break; - case ELECTRA: - methodsByMilestone.put(milestone, electraSupportedMethods()); + case EIP7594: break; } }); @@ -110,16 +107,6 @@ private Map> denebSupportedMethods() { return methods; } - private Map> electraSupportedMethods() { - final Map> methods = new HashMap<>(); - - methods.put(ENGINE_NEW_PAYLOAD, new EngineNewPayloadV4(executionEngineClient)); - methods.put(ENGINE_GET_PAYLOAD, new EngineGetPayloadV4(executionEngineClient, spec)); - methods.put(ENGINE_FORK_CHOICE_UPDATED, new EngineForkChoiceUpdatedV3(executionEngineClient)); - - return methods; - } - @Override @SuppressWarnings({"unchecked", "unused"}) public EngineJsonRpcMethod getMethod( diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java deleted file mode 100644 index 7e940fbf04f..00000000000 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/ElectraExecutionClientHandlerTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.ethereum.executionlayer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ExecutionException; -import org.apache.tuweni.bytes.Bytes32; -import org.apache.tuweni.units.bigints.UInt256; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.ethereum.executionclient.schema.BlobsBundleV1; -import tech.pegasys.teku.ethereum.executionclient.schema.ExecutionPayloadV4; -import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceStateV1; -import tech.pegasys.teku.ethereum.executionclient.schema.ForkChoiceUpdatedResult; -import tech.pegasys.teku.ethereum.executionclient.schema.GetPayloadV4Response; -import tech.pegasys.teku.ethereum.executionclient.schema.PayloadAttributesV3; -import tech.pegasys.teku.ethereum.executionclient.schema.PayloadStatusV1; -import tech.pegasys.teku.ethereum.executionclient.schema.Response; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadContext; -import tech.pegasys.teku.spec.datastructures.execution.GetPayloadResponse; -import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; -import tech.pegasys.teku.spec.executionlayer.ExecutionPayloadStatus; -import tech.pegasys.teku.spec.executionlayer.ForkChoiceState; -import tech.pegasys.teku.spec.executionlayer.PayloadBuildingAttributes; -import tech.pegasys.teku.spec.executionlayer.PayloadStatus; -import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -public class ElectraExecutionClientHandlerTest extends ExecutionHandlerClientTest { - - @BeforeEach - void setup() { - spec = TestSpecFactory.createMinimalElectra(); - dataStructureUtil = new DataStructureUtil(spec); - } - - @Test - void engineGetPayload_shouldCallGetPayloadV4() throws ExecutionException, InterruptedException { - final ExecutionClientHandler handler = getHandler(); - final ExecutionPayloadContext context = randomContext(); - final SafeFuture> dummyResponse = - SafeFuture.completedFuture( - new Response<>( - new GetPayloadV4Response( - ExecutionPayloadV4.fromInternalExecutionPayload( - dataStructureUtil.randomExecutionPayload()), - UInt256.MAX_VALUE, - BlobsBundleV1.fromInternalBlobsBundle(dataStructureUtil.randomBlobsBundle()), - true))); - when(executionEngineClient.getPayloadV4(context.getPayloadId())).thenReturn(dummyResponse); - - final UInt64 slot = dataStructureUtil.randomUInt64(1_000_000); - final SafeFuture future = handler.engineGetPayload(context, slot); - verify(executionEngineClient).getPayloadV4(context.getPayloadId()); - assertThat(future).isCompleted(); - assertThat(future.get().getExecutionPayload()).isInstanceOf(ExecutionPayloadElectra.class); - assertThat(future.get().getExecutionPayloadValue()).isEqualTo(UInt256.MAX_VALUE); - assertThat(future.get().getBlobsBundle()).isPresent(); - assertThat(future.get().getShouldOverrideBuilder()).isTrue(); - } - - @Test - void engineNewPayload_shouldCallNewPayloadV4() { - final ExecutionClientHandler handler = getHandler(); - final ExecutionPayload payload = dataStructureUtil.randomExecutionPayload(); - final Bytes32 parentBeaconBlockRoot = dataStructureUtil.randomBytes32(); - final List versionedHashes = dataStructureUtil.randomVersionedHashes(3); - final NewPayloadRequest newPayloadRequest = - new NewPayloadRequest(payload, versionedHashes, parentBeaconBlockRoot); - final ExecutionPayloadV4 payloadV4 = ExecutionPayloadV4.fromInternalExecutionPayload(payload); - final SafeFuture> dummyResponse = - SafeFuture.completedFuture( - new Response<>( - new PayloadStatusV1( - ExecutionPayloadStatus.ACCEPTED, dataStructureUtil.randomBytes32(), null))); - when(executionEngineClient.newPayloadV4(payloadV4, versionedHashes, parentBeaconBlockRoot)) - .thenReturn(dummyResponse); - final SafeFuture future = handler.engineNewPayload(newPayloadRequest); - verify(executionEngineClient).newPayloadV4(payloadV4, versionedHashes, parentBeaconBlockRoot); - assertThat(future).isCompleted(); - } - - @Test - void engineForkChoiceUpdated_shouldCallEngineForkChoiceUpdatedV3() { - final ExecutionClientHandler handler = getHandler(); - final ForkChoiceState forkChoiceState = dataStructureUtil.randomForkChoiceState(false); - final ForkChoiceStateV1 forkChoiceStateV1 = - ForkChoiceStateV1.fromInternalForkChoiceState(forkChoiceState); - final PayloadBuildingAttributes attributes = - new PayloadBuildingAttributes( - dataStructureUtil.randomUInt64(), - dataStructureUtil.randomUInt64(), - dataStructureUtil.randomUInt64(), - dataStructureUtil.randomBytes32(), - dataStructureUtil.randomEth1Address(), - Optional.empty(), - Optional.of(List.of()), - dataStructureUtil.randomBytes32()); - final Optional payloadAttributes = - PayloadAttributesV3.fromInternalPayloadBuildingAttributesV3(Optional.of(attributes)); - final SafeFuture> dummyResponse = - SafeFuture.completedFuture( - new Response<>( - new ForkChoiceUpdatedResult( - new PayloadStatusV1( - ExecutionPayloadStatus.ACCEPTED, dataStructureUtil.randomBytes32(), ""), - dataStructureUtil.randomBytes8()))); - when(executionEngineClient.forkChoiceUpdatedV3(forkChoiceStateV1, payloadAttributes)) - .thenReturn(dummyResponse); - final SafeFuture future = - handler.engineForkChoiceUpdated(forkChoiceState, Optional.of(attributes)); - verify(executionEngineClient).forkChoiceUpdatedV3(forkChoiceStateV1, payloadAttributes); - assertThat(future).isCompleted(); - } - - private ExecutionPayloadContext randomContext() { - return new ExecutionPayloadContext( - dataStructureUtil.randomBytes8(), - dataStructureUtil.randomForkChoiceState(false), - dataStructureUtil.randomPayloadBuildingAttributes(false)); - } -} diff --git a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolverTest.java b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolverTest.java index be9921cc3ce..eb30f4108b4 100644 --- a/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolverTest.java +++ b/ethereum/executionlayer/src/test/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolverTest.java @@ -36,12 +36,10 @@ import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV1; import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV2; import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.methods.EngineGetPayloadV4; import tech.pegasys.teku.ethereum.executionclient.methods.EngineJsonRpcMethod; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV1; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV2; import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV3; -import tech.pegasys.teku.ethereum.executionclient.methods.EngineNewPayloadV4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; @@ -164,46 +162,10 @@ private static Stream denebMethods() { arguments(ENGINE_FORK_CHOICE_UPDATED, EngineForkChoiceUpdatedV3.class)); } - @Test - void electraMilestoneMethodIsNotSupportedInDeneb() { - final Spec capellaSpec = TestSpecFactory.createMinimalDeneb(); - - final MilestoneBasedEngineJsonRpcMethodsResolver engineMethodsResolver = - new MilestoneBasedEngineJsonRpcMethodsResolver(capellaSpec, executionEngineClient); - - assertThatThrownBy( - () -> - engineMethodsResolver.getMethod( - ENGINE_GET_PAYLOAD, () -> SpecMilestone.ELECTRA, Object.class)) - .hasMessage("Can't find method with name engine_getPayload for milestone ELECTRA"); - } - - @ParameterizedTest - @MethodSource("electraMethods") - void shouldProvideExpectedMethodsForElectra( - EngineApiMethod method, Class> expectedMethodClass) { - final Spec electraSpec = TestSpecFactory.createMinimalElectra(); - - final MilestoneBasedEngineJsonRpcMethodsResolver engineMethodsResolver = - new MilestoneBasedEngineJsonRpcMethodsResolver(electraSpec, executionEngineClient); - - final EngineJsonRpcMethod providedMethod = - engineMethodsResolver.getMethod(method, () -> SpecMilestone.ELECTRA, Object.class); - - assertThat(providedMethod).isExactlyInstanceOf(expectedMethodClass); - } - - private static Stream electraMethods() { - return Stream.of( - arguments(ENGINE_NEW_PAYLOAD, EngineNewPayloadV4.class), - arguments(ENGINE_GET_PAYLOAD, EngineGetPayloadV4.class), - arguments(ENGINE_FORK_CHOICE_UPDATED, EngineForkChoiceUpdatedV3.class)); - } - @Test void getsCapabilities() { final Spec spec = - TestSpecFactory.createMinimalWithCapellaDenebAndElectraForkEpoch( + TestSpecFactory.createMinimalWithCapellaDenebAndEip7594ForkEpoch( UInt64.ONE, UInt64.valueOf(2), UInt64.valueOf(3)); final MilestoneBasedEngineJsonRpcMethodsResolver engineMethodsResolver = @@ -221,8 +183,6 @@ void getsCapabilities() { "engine_forkchoiceUpdatedV2", "engine_newPayloadV3", "engine_getPayloadV3", - "engine_forkchoiceUpdatedV3", - "engine_newPayloadV4", - "engine_getPayloadV4"); + "engine_forkchoiceUpdatedV3"); } } diff --git a/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java b/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java index 80cae793083..dbe0569a48f 100644 --- a/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java +++ b/ethereum/networks/src/main/java/tech/pegasys/teku/networks/Eth2NetworkConfiguration.java @@ -87,7 +87,7 @@ public class Eth2NetworkConfiguration { private final Optional bellatrixForkEpoch; private final Optional capellaForkEpoch; private final Optional denebForkEpoch; - private final Optional electraForkEpoch; + private final Optional eip7594ForkEpoch; private final Eth1Address eth1DepositContractAddress; private final Optional eth1DepositContractDeployBlock; private final Optional trustedSetup; @@ -117,7 +117,7 @@ private Eth2NetworkConfiguration( final Optional bellatrixForkEpoch, final Optional capellaForkEpoch, final Optional denebForkEpoch, - final Optional electraForkEpoch, + final Optional eip7594ForkEpoch, final Optional terminalBlockHashOverride, final Optional totalTerminalDifficultyOverride, final Optional terminalBlockHashEpochOverride, @@ -139,7 +139,7 @@ private Eth2NetworkConfiguration( this.bellatrixForkEpoch = bellatrixForkEpoch; this.capellaForkEpoch = capellaForkEpoch; this.denebForkEpoch = denebForkEpoch; - this.electraForkEpoch = electraForkEpoch; + this.eip7594ForkEpoch = eip7594ForkEpoch; this.eth1DepositContractAddress = eth1DepositContractAddress == null ? spec.getGenesisSpecConfig().getDepositContractAddress() @@ -219,7 +219,7 @@ public Optional getForkEpoch(final SpecMilestone specMilestone) { case BELLATRIX -> bellatrixForkEpoch; case CAPELLA -> capellaForkEpoch; case DENEB -> denebForkEpoch; - case ELECTRA -> electraForkEpoch; + case EIP7594 -> eip7594ForkEpoch; default -> Optional.empty(); }; } @@ -299,7 +299,7 @@ public boolean equals(final Object o) { && Objects.equals(bellatrixForkEpoch, that.bellatrixForkEpoch) && Objects.equals(capellaForkEpoch, that.capellaForkEpoch) && Objects.equals(denebForkEpoch, that.denebForkEpoch) - && Objects.equals(electraForkEpoch, that.electraForkEpoch) + && Objects.equals(eip7594ForkEpoch, that.eip7594ForkEpoch) && Objects.equals(eth1DepositContractAddress, that.eth1DepositContractAddress) && Objects.equals(eth1DepositContractDeployBlock, that.eth1DepositContractDeployBlock) && Objects.equals(trustedSetup, that.trustedSetup) @@ -323,7 +323,7 @@ public int hashCode() { bellatrixForkEpoch, capellaForkEpoch, denebForkEpoch, - electraForkEpoch, + eip7594ForkEpoch, eth1DepositContractAddress, eth1DepositContractDeployBlock, trustedSetup, @@ -363,7 +363,7 @@ public static class Builder { private Optional bellatrixForkEpoch = Optional.empty(); private Optional capellaForkEpoch = Optional.empty(); private Optional denebForkEpoch = Optional.empty(); - private Optional electraForkEpoch = Optional.empty(); + private Optional eip7594ForkEpoch = Optional.empty(); private Optional terminalBlockHashOverride = Optional.empty(); private Optional totalTerminalDifficultyOverride = Optional.empty(); private Optional terminalBlockHashEpochOverride = Optional.empty(); @@ -414,9 +414,9 @@ public Eth2NetworkConfiguration build() { denebBuilder.epochsStoreBlobs(maybeEpochsStoreBlobs); } }); - builder.electraBuilder( - electraBuilder -> - electraForkEpoch.ifPresent(electraBuilder::electraForkEpoch)); + builder.eip7594Builder( + eip7594Builder -> + eip7594ForkEpoch.ifPresent(eip7594Builder::eip7594ForkEpoch)); }); } if (spec.getForkSchedule().getSupportedMilestones().contains(SpecMilestone.DENEB) @@ -448,7 +448,7 @@ public Eth2NetworkConfiguration build() { bellatrixForkEpoch, capellaForkEpoch, denebForkEpoch, - electraForkEpoch, + eip7594ForkEpoch, terminalBlockHashOverride, totalTerminalDifficultyOverride, terminalBlockHashEpochOverride, @@ -641,8 +641,8 @@ public Builder denebForkEpoch(final UInt64 denebForkEpoch) { return this; } - public Builder electraForkEpoch(final UInt64 electraForkEpoch) { - this.electraForkEpoch = Optional.of(electraForkEpoch); + public Builder eip7594ForkEpoch(final UInt64 eip7594ForkEpoch) { + this.eip7594ForkEpoch = Optional.of(eip7594ForkEpoch); return this; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index ae4ed60aa92..1642035525a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -17,7 +17,7 @@ import static tech.pegasys.teku.infrastructure.time.TimeUtilities.millisToSeconds; import static tech.pegasys.teku.infrastructure.time.TimeUtilities.secondsToMillis; import static tech.pegasys.teku.spec.SpecMilestone.DENEB; -import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.EIP7594; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; @@ -47,16 +47,16 @@ import tech.pegasys.teku.spec.cache.IndexedAttestationCache; import tech.pegasys.teku.spec.config.NetworkingSpecConfig; import tech.pegasys.teku.spec.config.NetworkingSpecConfigDeneb; -import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; +import tech.pegasys.teku.spec.config.NetworkingSpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.config.SpecConfigAltair; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.constants.Domain; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; @@ -220,14 +220,13 @@ public Optional getNetworkingConfigDeneb() { } /** - * Networking config with Electra constants. Use {@link - * tech.pegasys.teku.spec.config.SpecConfigElectra#required(SpecConfig)} when you are sure that - * Electra is available, otherwise use this method + * Networking config with EIP7594 constants. Use {@link SpecConfigEip7594#required(SpecConfig)} + * when you are sure that EIP7594 is available, otherwise use this method */ - public Optional getNetworkingConfigElectra() { - return Optional.ofNullable(forMilestone(ELECTRA)) + public Optional getNetworkingConfigEip7594() { + return Optional.ofNullable(forMilestone(EIP7594)) .map(SpecVersion::getConfig) - .map(specConfig -> (NetworkingSpecConfigElectra) specConfig.getNetworkingConfig()); + .map(specConfig -> (NetworkingSpecConfigEip7594) specConfig.getNetworkingConfig()); } public SchemaDefinitions getGenesisSchemaDefinitions() { @@ -424,10 +423,10 @@ public ExecutionPayloadHeader deserializeJsonExecutionPayloadHeader( public DataColumnSidecar deserializeSidecar(final Bytes serializedSidecar, final UInt64 slot) { return atSlot(slot) .getSchemaDefinitions() - .toVersionElectra() + .toVersionEip7594() .orElseThrow( () -> - new RuntimeException("Electra milestone is required to deserialize column sidecar")) + new RuntimeException("EIP7594 milestone is required to deserialize column sidecar")) .getDataColumnSidecarSchema() .sszDeserialize(serializedSidecar); } @@ -958,25 +957,25 @@ public UInt64 computeSubnetForBlobSidecar(final BlobSidecar blobSidecar) { } public Optional getNumberOfDataColumns() { - return getSpecConfigElectra().map(SpecConfigElectra::getNumberOfColumns); + return getSpecConfigEip7594().map(SpecConfigEip7594::getNumberOfColumns); } public boolean isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( final ReadOnlyStore store, final UInt64 epoch) { - if (!forkSchedule.getSpecMilestoneAtEpoch(epoch).isGreaterThanOrEqualTo(ELECTRA)) { + if (!forkSchedule.getSpecMilestoneAtEpoch(epoch).isGreaterThanOrEqualTo(EIP7594)) { return false; } final SpecConfig config = atEpoch(epoch).getConfig(); - final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(config); + final SpecConfigEip7594 specConfigEip7594 = SpecConfigEip7594.required(config); return getCurrentEpoch(store) .minusMinZero(epoch) - .isLessThanOrEqualTo(specConfigElectra.getMinEpochsForDataColumnSidecarsRequests()); + .isLessThanOrEqualTo(specConfigEip7594.getMinEpochsForDataColumnSidecarsRequests()); } public UInt64 computeSubnetForDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { final SpecConfig config = atSlot(dataColumnSidecar.getSlot()).getConfig(); - final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(config); - return dataColumnSidecar.getIndex().mod(specConfigElectra.getDataColumnSidecarSubnetCount()); + final SpecConfigEip7594 specConfigEip7594 = SpecConfigEip7594.required(config); + return dataColumnSidecar.getIndex().mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); } public Optional computeFirstSlotWithBlobSupport() { @@ -985,11 +984,6 @@ public Optional computeFirstSlotWithBlobSupport() { .map(this::computeStartSlotAtEpoch); } - // Electra Utils - public boolean isFormerDepositMechanismDisabled(BeaconState state) { - return atState(state).miscHelpers().isFormerDepositMechanismDisabled(state); - } - // Deneb private helpers private Optional getSpecConfigDeneb() { final SpecMilestone highestSupportedMilestone = @@ -1003,13 +997,13 @@ private Optional getSpecConfigDeneb(final UInt64 slot) { return atSlot(slot).getConfig().toVersionDeneb(); } - // Electra private helpers - private Optional getSpecConfigElectra() { + // EIP7594 private helpers + private Optional getSpecConfigEip7594() { final SpecMilestone highestSupportedMilestone = getForkSchedule().getHighestSupportedMilestone(); return Optional.ofNullable(forMilestone(highestSupportedMilestone)) .map(SpecVersion::getConfig) - .flatMap(SpecConfig::toVersionElectra); + .flatMap(SpecConfig::toVersionEip7594); } // Private helpers diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java index 0ee2b79d022..a76e7e31b1a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecFactory.java @@ -17,7 +17,7 @@ import static tech.pegasys.teku.spec.SpecMilestone.BELLATRIX; import static tech.pegasys.teku.spec.SpecMilestone.CAPELLA; import static tech.pegasys.teku.spec.SpecMilestone.DENEB; -import static tech.pegasys.teku.spec.SpecMilestone.ELECTRA; +import static tech.pegasys.teku.spec.SpecMilestone.EIP7594; import static tech.pegasys.teku.spec.SpecMilestone.PHASE0; import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; @@ -28,7 +28,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; @@ -58,15 +58,15 @@ public static Spec create(final SpecConfig config) { .orElse(FAR_FUTURE_EPOCH); final UInt64 denebForkEpoch = config.toVersionDeneb().map(SpecConfigDeneb::getDenebForkEpoch).orElse(FAR_FUTURE_EPOCH); - final UInt64 electraForkEpoch = + final UInt64 eip7594ForkEpoch = config - .toVersionElectra() - .map(SpecConfigElectra::getElectraForkEpoch) + .toVersionEip7594() + .map(SpecConfigEip7594::getEip7594ForkEpoch) .orElse(FAR_FUTURE_EPOCH); final SpecMilestone highestMilestoneSupported; - if (!electraForkEpoch.equals(FAR_FUTURE_EPOCH)) { - highestMilestoneSupported = ELECTRA; + if (!eip7594ForkEpoch.equals(FAR_FUTURE_EPOCH)) { + highestMilestoneSupported = EIP7594; } else if (!denebForkEpoch.equals(FAR_FUTURE_EPOCH)) { highestMilestoneSupported = DENEB; } else if (!capellaForkEpoch.equals(FAR_FUTURE_EPOCH)) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java index 648129a11d4..a865759da1f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecMilestone.java @@ -26,7 +26,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; public enum SpecMilestone { PHASE0, @@ -34,7 +34,7 @@ public enum SpecMilestone { BELLATRIX, CAPELLA, DENEB, - ELECTRA; + EIP7594; /** * Returns true if this milestone is at or after the supplied milestone ({@code other}) @@ -113,7 +113,7 @@ static Optional getForkVersion( .map(SpecConfigBellatrix::getBellatrixForkVersion); case CAPELLA -> specConfig.toVersionCapella().map(SpecConfigCapella::getCapellaForkVersion); case DENEB -> specConfig.toVersionDeneb().map(SpecConfigDeneb::getDenebForkVersion); - case ELECTRA -> specConfig.toVersionElectra().map(SpecConfigElectra::getElectraForkVersion); + case EIP7594 -> specConfig.toVersionEip7594().map(SpecConfigEip7594::getEip7594ForkVersion); }; } @@ -129,7 +129,7 @@ static Optional getForkEpoch(final SpecConfig specConfig, final SpecMile .map(SpecConfigBellatrix::getBellatrixForkEpoch); case CAPELLA -> specConfig.toVersionCapella().map(SpecConfigCapella::getCapellaForkEpoch); case DENEB -> specConfig.toVersionDeneb().map(SpecConfigDeneb::getDenebForkEpoch); - case ELECTRA -> specConfig.toVersionElectra().map(SpecConfigElectra::getElectraForkEpoch); + case EIP7594 -> specConfig.toVersionEip7594().map(SpecConfigEip7594::getEip7594ForkEpoch); }; } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java index d06660b2e18..79a7d430700 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/SpecVersion.java @@ -19,21 +19,21 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.logic.DelegatingSpecLogic; import tech.pegasys.teku.spec.logic.SpecLogic; import tech.pegasys.teku.spec.logic.versions.altair.SpecLogicAltair; import tech.pegasys.teku.spec.logic.versions.bellatrix.SpecLogicBellatrix; import tech.pegasys.teku.spec.logic.versions.capella.SpecLogicCapella; import tech.pegasys.teku.spec.logic.versions.deneb.SpecLogicDeneb; -import tech.pegasys.teku.spec.logic.versions.electra.SpecLogicElectra; +import tech.pegasys.teku.spec.logic.versions.eip7594.SpecLogicEip7594; import tech.pegasys.teku.spec.logic.versions.phase0.SpecLogicPhase0; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsAltair; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsPhase0; public class SpecVersion extends DelegatingSpecLogic { @@ -60,7 +60,7 @@ public static Optional create( case BELLATRIX -> specConfig.toVersionBellatrix().map(SpecVersion::createBellatrix); case CAPELLA -> specConfig.toVersionCapella().map(SpecVersion::createCapella); case DENEB -> specConfig.toVersionDeneb().map(SpecVersion::createDeneb); - case ELECTRA -> specConfig.toVersionElectra().map(SpecVersion::createElectra); + case EIP7594 -> specConfig.toVersionEip7594().map(SpecVersion::createEip7594); }; } @@ -94,10 +94,10 @@ static SpecVersion createDeneb(final SpecConfigDeneb specConfig) { return new SpecVersion(SpecMilestone.DENEB, specConfig, schemaDefinitions, specLogic); } - static SpecVersion createElectra(final SpecConfigElectra specConfig) { - final SchemaDefinitionsElectra schemaDefinitions = new SchemaDefinitionsElectra(specConfig); - final SpecLogicElectra specLogic = SpecLogicElectra.create(specConfig, schemaDefinitions); - return new SpecVersion(SpecMilestone.ELECTRA, specConfig, schemaDefinitions, specLogic); + static SpecVersion createEip7594(final SpecConfigEip7594 specConfig) { + final SchemaDefinitionsEip7594 schemaDefinitions = new SchemaDefinitionsEip7594(specConfig); + final SpecLogicEip7594 specLogic = SpecLogicEip7594.create(specConfig, schemaDefinitions); + return new SpecVersion(SpecMilestone.EIP7594, specConfig, schemaDefinitions, specLogic); } public SpecMilestone getMilestone() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7594.java similarity index 93% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7594.java index f0bcac5e86c..68c45b9a2d3 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/NetworkingSpecConfigEip7594.java @@ -19,7 +19,7 @@ *

These constants are unified among forks and are not overridden, new constant name is used if * it's changed in the new fork */ -public interface NetworkingSpecConfigElectra extends NetworkingSpecConfig { +public interface NetworkingSpecConfigEip7594 extends NetworkingSpecConfig { int getDataColumnSidecarSubnetCount(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java index dcc2ce1b8af..2616309ccac 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfig.java @@ -183,7 +183,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigDeneb.java index aa4296251be..1f25c8822c0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigDeneb.java @@ -54,6 +54,7 @@ static SpecConfigDeneb required(final SpecConfig specConfig) { int getMaxBlobsPerBlock(); + /** BlobSidecar's */ int getKzgCommitmentInclusionProofDepth(); int getEpochsStoreBlobs(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java new file mode 100644 index 00000000000..d03e7de6f8e --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java @@ -0,0 +1,49 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.config; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public interface SpecConfigEip7594 extends SpecConfigDeneb, NetworkingSpecConfigEip7594 { + + static SpecConfigEip7594 required(final SpecConfig specConfig) { + return specConfig + .toVersionEip7594() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected EIP7594 spec config but got: " + + specConfig.getClass().getSimpleName())); + } + + Bytes4 getEip7594ForkVersion(); + + UInt64 getEip7594ForkEpoch(); + + UInt64 getFieldElementsPerCell(); + + UInt64 getFieldElementsPerExtBlob(); + + /** DataColumnSidecar's */ + UInt64 getKzgCommitmentsInclusionProofDepth(); + + default UInt64 getNumberOfColumns() { + return getFieldElementsPerExtBlob().dividedBy(getFieldElementsPerCell()); + } + + @Override + Optional toVersionEip7594(); +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java new file mode 100644 index 00000000000..be40556553a --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java @@ -0,0 +1,142 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.config; + +import java.util.Objects; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; + +public class SpecConfigEip7594Impl extends DelegatingSpecConfigDeneb implements SpecConfigEip7594 { + + private final Bytes4 eip7594ForkVersion; + private final UInt64 eip7594ForkEpoch; + + private final int dataColumnSidecarSubnetCount; + private final int custodyRequirement; + private final UInt64 fieldElementsPerCell; + private final UInt64 fieldElementsPerExtBlob; + private final UInt64 kzgCommitmentsInclusionProofDepth; + private final int minEpochsForDataColumnSidecarsRequests; + private final int maxRequestDataColumnSidecars; + + public SpecConfigEip7594Impl( + final SpecConfigDeneb specConfig, + final Bytes4 eip7594ForkVersion, + final UInt64 eip7594ForkEpoch, + final UInt64 fieldElementsPerCell, + final UInt64 fieldElementsPerExtBlob, + final UInt64 kzgCommitmentsInclusionProofDepth, + final int dataColumnSidecarSubnetCount, + final int custodyRequirement, + final int minEpochsForDataColumnSidecarsRequests, + final int maxRequestDataColumnSidecars) { + super(specConfig); + this.eip7594ForkVersion = eip7594ForkVersion; + this.eip7594ForkEpoch = eip7594ForkEpoch; + this.fieldElementsPerCell = fieldElementsPerCell; + this.fieldElementsPerExtBlob = fieldElementsPerExtBlob; + this.kzgCommitmentsInclusionProofDepth = kzgCommitmentsInclusionProofDepth; + this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; + this.custodyRequirement = custodyRequirement; + this.minEpochsForDataColumnSidecarsRequests = minEpochsForDataColumnSidecarsRequests; + this.maxRequestDataColumnSidecars = maxRequestDataColumnSidecars; + } + + @Override + public Bytes4 getEip7594ForkVersion() { + return eip7594ForkVersion; + } + + @Override + public UInt64 getEip7594ForkEpoch() { + return eip7594ForkEpoch; + } + + @Override + public UInt64 getFieldElementsPerCell() { + return fieldElementsPerCell; + } + + @Override + public UInt64 getFieldElementsPerExtBlob() { + return fieldElementsPerExtBlob; + } + + @Override + public UInt64 getKzgCommitmentsInclusionProofDepth() { + return kzgCommitmentsInclusionProofDepth; + } + + @Override + public int getDataColumnSidecarSubnetCount() { + return dataColumnSidecarSubnetCount; + } + + @Override + public int getCustodyRequirement() { + return custodyRequirement; + } + + @Override + public int getMinEpochsForDataColumnSidecarsRequests() { + return minEpochsForDataColumnSidecarsRequests; + } + + @Override + public int getMaxRequestDataColumnSidecars() { + return maxRequestDataColumnSidecars; + } + + @Override + public Optional toVersionEip7594() { + return Optional.of(this); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final SpecConfigEip7594Impl that = (SpecConfigEip7594Impl) o; + return Objects.equals(specConfig, that.specConfig) + && Objects.equals(eip7594ForkVersion, that.eip7594ForkVersion) + && Objects.equals(eip7594ForkEpoch, that.eip7594ForkEpoch) + && Objects.equals(fieldElementsPerCell, that.fieldElementsPerCell) + && Objects.equals(fieldElementsPerExtBlob, that.fieldElementsPerExtBlob) + && Objects.equals(kzgCommitmentsInclusionProofDepth, that.kzgCommitmentsInclusionProofDepth) + && dataColumnSidecarSubnetCount == that.dataColumnSidecarSubnetCount + && custodyRequirement == that.custodyRequirement + && minEpochsForDataColumnSidecarsRequests == that.minEpochsForDataColumnSidecarsRequests + && maxRequestDataColumnSidecars == that.maxRequestDataColumnSidecars; + } + + @Override + public int hashCode() { + return Objects.hash( + specConfig, + eip7594ForkVersion, + eip7594ForkEpoch, + dataColumnSidecarSubnetCount, + custodyRequirement, + fieldElementsPerCell, + fieldElementsPerExtBlob, + kzgCommitmentsInclusionProofDepth, + minEpochsForDataColumnSidecarsRequests, + maxRequestDataColumnSidecars); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java deleted file mode 100644 index e4ffd98016f..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectra.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.config; - -import java.util.Optional; -import tech.pegasys.teku.infrastructure.bytes.Bytes4; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - -public interface SpecConfigElectra extends SpecConfigDeneb, NetworkingSpecConfigElectra { - - UInt64 UNSET_DEPOSIT_RECEIPTS_START_INDEX = UInt64.MAX_VALUE; - - static SpecConfigElectra required(final SpecConfig specConfig) { - return specConfig - .toVersionElectra() - .orElseThrow( - () -> - new IllegalArgumentException( - "Expected Electra spec config but got: " - + specConfig.getClass().getSimpleName())); - } - - UInt64 getMinActivationBalance(); - - UInt64 getMaxEffectiveBalanceElectra(); - - int getPendingBalanceDepositsLimit(); - - int getPendingPartialWithdrawalsLimit(); - - int getPendingConsolidationsLimit(); - - int getWhistleblowerRewardQuotientElectra(); - - int getMinSlashingPenaltyQuotientElectra(); - - int getMaxAttesterSlashingsElectra(); - - int getMaxAttestationsElectra(); - - int getMaxConsolidations(); - - int getMaxPartialWithdrawalsPerPayload(); - - UInt64 getMinPerEpochChurnLimitElectra(); - - UInt64 getMaxPerEpochActivationExitChurnLimit(); - - Bytes4 getElectraForkVersion(); - - UInt64 getElectraForkEpoch(); - - int getMaxDepositReceiptsPerPayload(); - - int getMaxExecutionLayerExits(); - - UInt64 getFieldElementsPerCell(); - - default UInt64 getFieldElementsPerExtendedBlob() { - return UInt64.valueOf(getFieldElementsPerBlob()).times(2); - } - - default UInt64 getNumberOfColumns() { - return getFieldElementsPerExtendedBlob().dividedBy(getFieldElementsPerCell()); - } - - @Override - Optional toVersionElectra(); -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java deleted file mode 100644 index 96bad6b3835..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigElectraImpl.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.config; - -import java.util.Objects; -import java.util.Optional; -import tech.pegasys.teku.infrastructure.bytes.Bytes4; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - -public class SpecConfigElectraImpl extends DelegatingSpecConfigDeneb implements SpecConfigElectra { - - private final Bytes4 electraForkVersion; - private final UInt64 electraForkEpoch; - private final UInt64 minPerEpochChurnLimitElectra; - private final UInt64 maxPerEpochActivationExitChurnLimit; - - private final int maxDepositReceiptsPerPayload; - private final int maxExecutionLayerExits; - private final UInt64 minActivationBalance; - private final UInt64 maxEffectiveBalanceElectra; - private final int pendingBalanceDepositsLimit; - private final int pendingPartialWithdrawalsLimit; - private final int pendingConsolidationsLimit; - private final int whistleblowerRewardQuotientElectra; - private final int minSlashingPenaltyQuotientElectra; - private final int maxPartialWithdrawalsPerPayload; - private final int maxAttesterSlashingsElectra; - private final int maxAttestationsElectra; - private final int maxConsolidations; - private final int dataColumnSidecarSubnetCount; - private final int custodyRequirement; - private final UInt64 fieldElementsPerCell; - private final int minEpochsForDataColumnSidecarsRequests; - private final int maxRequestDataColumnSidecars; - - public SpecConfigElectraImpl( - final SpecConfigDeneb specConfig, - final Bytes4 electraForkVersion, - final UInt64 electraForkEpoch, - final int maxDepositReceiptsPerPayload, - final int maxExecutionLayerExits, - final UInt64 minPerEpochChurnLimitElectra, - final UInt64 maxPerEpochActivationExitChurnLimit, - final UInt64 minActivationBalance, - final UInt64 maxEffectiveBalanceElectra, - final int pendingBalanceDepositsLimit, - final int pendingPartialWithdrawalsLimit, - final int pendingConsolidationsLimit, - final int whistleblowerRewardQuotientElectra, - final int minSlashingPenaltyQuotientElectra, - final int maxPartialWithdrawalsPerPayload, - final int maxAttesterSlashingsElectra, - final int maxAttestationsElectra, - final int maxConsolidations, - final UInt64 fieldElementsPerCell, - final int dataColumnSidecarSubnetCount, - final int custodyRequirement, - final int minEpochsForDataColumnSidecarsRequests, - final int maxRequestDataColumnSidecars) { - super(specConfig); - this.electraForkVersion = electraForkVersion; - this.electraForkEpoch = electraForkEpoch; - this.maxDepositReceiptsPerPayload = maxDepositReceiptsPerPayload; - this.maxExecutionLayerExits = maxExecutionLayerExits; - this.minPerEpochChurnLimitElectra = minPerEpochChurnLimitElectra; - this.maxPerEpochActivationExitChurnLimit = maxPerEpochActivationExitChurnLimit; - this.minActivationBalance = minActivationBalance; - this.maxEffectiveBalanceElectra = maxEffectiveBalanceElectra; - this.pendingBalanceDepositsLimit = pendingBalanceDepositsLimit; - this.pendingPartialWithdrawalsLimit = pendingPartialWithdrawalsLimit; - this.pendingConsolidationsLimit = pendingConsolidationsLimit; - this.whistleblowerRewardQuotientElectra = whistleblowerRewardQuotientElectra; - this.minSlashingPenaltyQuotientElectra = minSlashingPenaltyQuotientElectra; - this.maxPartialWithdrawalsPerPayload = maxPartialWithdrawalsPerPayload; - this.maxAttesterSlashingsElectra = maxAttesterSlashingsElectra; - this.maxAttestationsElectra = maxAttestationsElectra; - this.maxConsolidations = maxConsolidations; - this.fieldElementsPerCell = fieldElementsPerCell; - this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; - this.custodyRequirement = custodyRequirement; - this.minEpochsForDataColumnSidecarsRequests = minEpochsForDataColumnSidecarsRequests; - this.maxRequestDataColumnSidecars = maxRequestDataColumnSidecars; - } - - @Override - public Bytes4 getElectraForkVersion() { - return electraForkVersion; - } - - @Override - public UInt64 getElectraForkEpoch() { - return electraForkEpoch; - } - - @Override - public int getMaxDepositReceiptsPerPayload() { - return maxDepositReceiptsPerPayload; - } - - @Override - public int getMaxExecutionLayerExits() { - return maxExecutionLayerExits; - } - - @Override - public UInt64 getMinActivationBalance() { - return minActivationBalance; - } - - @Override - public UInt64 getMaxEffectiveBalanceElectra() { - return maxEffectiveBalanceElectra; - } - - @Override - public int getPendingBalanceDepositsLimit() { - return pendingBalanceDepositsLimit; - } - - @Override - public int getPendingPartialWithdrawalsLimit() { - return pendingPartialWithdrawalsLimit; - } - - @Override - public int getPendingConsolidationsLimit() { - return pendingConsolidationsLimit; - } - - @Override - public int getWhistleblowerRewardQuotientElectra() { - return whistleblowerRewardQuotientElectra; - } - - @Override - public int getMinSlashingPenaltyQuotientElectra() { - return minSlashingPenaltyQuotientElectra; - } - - @Override - public int getMaxAttesterSlashingsElectra() { - return maxAttesterSlashingsElectra; - } - - @Override - public int getMaxAttestationsElectra() { - return maxAttestationsElectra; - } - - @Override - public int getMaxConsolidations() { - return maxConsolidations; - } - - @Override - public int getMaxPartialWithdrawalsPerPayload() { - return maxPartialWithdrawalsPerPayload; - } - - @Override - public UInt64 getMinPerEpochChurnLimitElectra() { - return minPerEpochChurnLimitElectra; - } - - @Override - public UInt64 getMaxPerEpochActivationExitChurnLimit() { - return maxPerEpochActivationExitChurnLimit; - } - - @Override - public UInt64 getFieldElementsPerCell() { - return fieldElementsPerCell; - } - - @Override - public int getDataColumnSidecarSubnetCount() { - return dataColumnSidecarSubnetCount; - } - - @Override - public int getCustodyRequirement() { - return custodyRequirement; - } - - @Override - public int getMinEpochsForDataColumnSidecarsRequests() { - return minEpochsForDataColumnSidecarsRequests; - } - - @Override - public int getMaxRequestDataColumnSidecars() { - return maxRequestDataColumnSidecars; - } - - @Override - public Optional toVersionElectra() { - return Optional.of(this); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - final SpecConfigElectraImpl that = (SpecConfigElectraImpl) o; - return Objects.equals(specConfig, that.specConfig) - && Objects.equals(electraForkVersion, that.electraForkVersion) - && Objects.equals(electraForkEpoch, that.electraForkEpoch) - && maxDepositReceiptsPerPayload == that.maxDepositReceiptsPerPayload - && maxExecutionLayerExits == that.maxExecutionLayerExits; - } - - @Override - public int hashCode() { - return Objects.hash( - specConfig, - electraForkVersion, - electraForkEpoch, - maxDepositReceiptsPerPayload, - maxExecutionLayerExits); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java index 683e6491302..643ca14bd09 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigLoader.java @@ -32,7 +32,7 @@ public class SpecConfigLoader { private static final Logger LOG = LogManager.getLogger(); private static final List AVAILABLE_PRESETS = - List.of("phase0", "altair", "bellatrix", "capella", "deneb", "electra"); + List.of("phase0", "altair", "bellatrix", "capella", "deneb", "eip7594"); private static final String CONFIG_PATH = "configs/"; private static final String PRESET_PATH = "presets/"; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java index 01fe04b5102..85ae6256752 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigReader.java @@ -48,7 +48,7 @@ import tech.pegasys.teku.spec.config.builder.BellatrixBuilder; import tech.pegasys.teku.spec.config.builder.CapellaBuilder; import tech.pegasys.teku.spec.config.builder.DenebBuilder; -import tech.pegasys.teku.spec.config.builder.ElectraBuilder; +import tech.pegasys.teku.spec.config.builder.Eip7594Builder; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; public class SpecConfigReader { @@ -197,13 +197,13 @@ public void loadFromMap( unprocessedConfig.remove(constantKey); }); - // Process electra config - streamConfigSetters(ElectraBuilder.class) + // Process EIP7594 config + streamConfigSetters(Eip7594Builder.class) .forEach( setter -> { final String constantKey = camelToSnakeCase(setter.getName()); final Object rawValue = unprocessedConfig.get(constantKey); - invokeSetter(setter, configBuilder::electraBuilder, constantKey, rawValue); + invokeSetter(setter, configBuilder::eip7594Builder, constantKey, rawValue); unprocessedConfig.remove(constantKey); }); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java new file mode 100644 index 00000000000..30e9627f3c4 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java @@ -0,0 +1,149 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.config.builder; + +import static com.google.common.base.Preconditions.checkNotNull; +import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import tech.pegasys.teku.infrastructure.bytes.Bytes4; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.config.SpecConfigEip7594Impl; + +public class Eip7594Builder implements ForkConfigBuilder { + + private Bytes4 eip7594ForkVersion; + private UInt64 eip7594ForkEpoch; + // division by 0 defaults protection + private UInt64 fieldElementsPerCell = UInt64.ONE; + private UInt64 fieldElementsPerExtBlob; + private UInt64 kzgCommitmentsInclusionProofDepth; + private Integer dataColumnSidecarSubnetCount; + private Integer custodyRequirement; + private Integer minEpochsForDataColumnSidecarsRequests; + private Integer maxRequestDataColumnSidecars; + + Eip7594Builder() {} + + @Override + public SpecConfigEip7594 build(final SpecConfigDeneb specConfig) { + return new SpecConfigEip7594Impl( + specConfig, + eip7594ForkVersion, + eip7594ForkEpoch, + fieldElementsPerCell, + fieldElementsPerExtBlob, + kzgCommitmentsInclusionProofDepth, + dataColumnSidecarSubnetCount, + custodyRequirement, + minEpochsForDataColumnSidecarsRequests, + maxRequestDataColumnSidecars); + } + + public Eip7594Builder eip7594ForkEpoch(final UInt64 eip7594ForkEpoch) { + checkNotNull(eip7594ForkEpoch); + this.eip7594ForkEpoch = eip7594ForkEpoch; + return this; + } + + public Eip7594Builder eip7594ForkVersion(final Bytes4 eip7594ForkVersion) { + checkNotNull(eip7594ForkVersion); + this.eip7594ForkVersion = eip7594ForkVersion; + return this; + } + + public Eip7594Builder fieldElementsPerCell(final UInt64 fieldElementsPerCell) { + checkNotNull(fieldElementsPerCell); + this.fieldElementsPerCell = fieldElementsPerCell; + return this; + } + + public Eip7594Builder fieldElementsPerExtBlob(final UInt64 fieldElementsPerExtBlob) { + checkNotNull(fieldElementsPerExtBlob); + this.fieldElementsPerExtBlob = fieldElementsPerExtBlob; + return this; + } + + public Eip7594Builder kzgCommitmentsInclusionProofDepth( + final UInt64 kzgCommitmentsInclusionProofDepth) { + checkNotNull(kzgCommitmentsInclusionProofDepth); + this.kzgCommitmentsInclusionProofDepth = kzgCommitmentsInclusionProofDepth; + return this; + } + + public Eip7594Builder dataColumnSidecarSubnetCount(final Integer blobSidecarSubnetCount) { + this.dataColumnSidecarSubnetCount = blobSidecarSubnetCount; + return this; + } + + public Eip7594Builder custodyRequirement(final Integer custodyRequirement) { + checkNotNull(custodyRequirement); + this.custodyRequirement = custodyRequirement; + return this; + } + + public Eip7594Builder minEpochsForDataColumnSidecarsRequests(final Integer custodyEpochs) { + checkNotNull(custodyEpochs); + this.minEpochsForDataColumnSidecarsRequests = custodyEpochs; + return this; + } + + public Eip7594Builder maxRequestDataColumnSidecars(final Integer maxRequestDataColumnSidecars) { + checkNotNull(maxRequestDataColumnSidecars); + this.maxRequestDataColumnSidecars = maxRequestDataColumnSidecars; + return this; + } + + @Override + public void validate() { + if (eip7594ForkEpoch == null) { + eip7594ForkEpoch = SpecConfig.FAR_FUTURE_EPOCH; + eip7594ForkVersion = SpecBuilderUtil.PLACEHOLDER_FORK_VERSION; + } + + // Fill default zeros if fork is unsupported + if (eip7594ForkEpoch.equals(FAR_FUTURE_EPOCH)) { + SpecBuilderUtil.fillMissingValuesWithZeros(this); + } + + validateConstants(); + } + + @Override + public Map getValidationMap() { + final Map constants = new HashMap<>(); + + constants.put("eip7594ForkEpoch", eip7594ForkEpoch); + constants.put("eip7594ForkVersion", eip7594ForkVersion); + constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); + constants.put("custodyRequirement", custodyRequirement); + constants.put("fieldElementsPerCell", fieldElementsPerCell); + constants.put("fieldElementsPerExtBlob", fieldElementsPerExtBlob); + constants.put("kzgCommitmentsInclusionProofDepth", kzgCommitmentsInclusionProofDepth); + constants.put("minEpochsForDataColumnSidecarsRequests", minEpochsForDataColumnSidecarsRequests); + constants.put("maxRequestDataColumnSidecars", maxRequestDataColumnSidecars); + + return constants; + } + + @Override + public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) { + rawConfig.accept("EIP7594_FORK_EPOCH", eip7594ForkEpoch); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java deleted file mode 100644 index 7d11426c159..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/ElectraBuilder.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.config.builder; - -import static com.google.common.base.Preconditions.checkNotNull; -import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiConsumer; -import tech.pegasys.teku.infrastructure.bytes.Bytes4; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.config.SpecConfigElectraImpl; - -public class ElectraBuilder implements ForkConfigBuilder { - - private Bytes4 electraForkVersion; - private UInt64 electraForkEpoch; - // TODO: remove default when EIP-7251 become part of the Electra - private UInt64 minPerEpochChurnLimitElectra = UInt64.ZERO; - // TODO: remove default when EIP-7251 become part of the Electra - private UInt64 maxPerEpochActivationExitChurnLimit = UInt64.ZERO; - private Integer maxDepositReceiptsPerPayload; - private Integer maxExecutionLayerExits; - private UInt64 minActivationBalance; - private UInt64 maxEffectiveBalanceElectra; - private Integer pendingBalanceDepositsLimit; - private Integer pendingPartialWithdrawalsLimit; - private Integer pendingConsolidationsLimit; - private Integer whistleblowerRewardQuotientElectra; - private Integer minSlashingPenaltyQuotientElectra; - private Integer maxPartialWithdrawalsPerPayload; - private Integer maxAttesterSlashingsElectra; - private Integer maxAttestationsElectra; - private Integer maxConsolidations; - // TODO: Remove default when EIP-7459 becomes part of the Electra - private UInt64 fieldElementsPerCell = UInt64.ONE; - private Integer dataColumnSidecarSubnetCount; - private Integer custodyRequirement; - private Integer minEpochsForDataColumnSidecarsRequests; - private Integer maxRequestDataColumnSidecars; - - ElectraBuilder() {} - - @Override - public SpecConfigElectra build(final SpecConfigDeneb specConfig) { - return new SpecConfigElectraImpl( - specConfig, - electraForkVersion, - electraForkEpoch, - maxDepositReceiptsPerPayload, - maxExecutionLayerExits, - minPerEpochChurnLimitElectra, - maxPerEpochActivationExitChurnLimit, - minActivationBalance, - maxEffectiveBalanceElectra, - pendingBalanceDepositsLimit, - pendingPartialWithdrawalsLimit, - pendingConsolidationsLimit, - whistleblowerRewardQuotientElectra, - minSlashingPenaltyQuotientElectra, - maxPartialWithdrawalsPerPayload, - maxAttesterSlashingsElectra, - maxAttestationsElectra, - maxConsolidations, - fieldElementsPerCell, - dataColumnSidecarSubnetCount, - custodyRequirement, - minEpochsForDataColumnSidecarsRequests, - maxRequestDataColumnSidecars); - } - - public ElectraBuilder electraForkEpoch(final UInt64 electraForkEpoch) { - checkNotNull(electraForkEpoch); - this.electraForkEpoch = electraForkEpoch; - return this; - } - - public ElectraBuilder electraForkVersion(final Bytes4 electraForkVersion) { - checkNotNull(electraForkVersion); - this.electraForkVersion = electraForkVersion; - return this; - } - - public ElectraBuilder maxDepositReceiptsPerPayload(final Integer maxDepositReceiptsPerPayload) { - checkNotNull(maxDepositReceiptsPerPayload); - this.maxDepositReceiptsPerPayload = maxDepositReceiptsPerPayload; - return this; - } - - public ElectraBuilder maxExecutionLayerExits(final Integer maxExecutionLayerExits) { - checkNotNull(maxExecutionLayerExits); - this.maxExecutionLayerExits = maxExecutionLayerExits; - return this; - } - - public ElectraBuilder fieldElementsPerCell(final UInt64 fieldElementsPerCell) { - checkNotNull(fieldElementsPerCell); - this.fieldElementsPerCell = fieldElementsPerCell; - return this; - } - - public ElectraBuilder minPerEpochChurnLimitElectra(final UInt64 minPerEpochChurnLimitElectra) { - checkNotNull(minPerEpochChurnLimitElectra); - this.minPerEpochChurnLimitElectra = minPerEpochChurnLimitElectra; - return this; - } - - public ElectraBuilder maxPerEpochActivationExitChurnLimit( - final UInt64 maxPerEpochActivationExitChurnLimit) { - checkNotNull(maxPerEpochActivationExitChurnLimit); - this.maxPerEpochActivationExitChurnLimit = maxPerEpochActivationExitChurnLimit; - return this; - } - - public ElectraBuilder minActivationBalance(final UInt64 minActivationBalance) { - checkNotNull(minActivationBalance); - this.minActivationBalance = minActivationBalance; - return this; - } - - public ElectraBuilder maxEffectiveBalanceElectra(final UInt64 maxEffectiveBalanceElectra) { - checkNotNull(maxEffectiveBalanceElectra); - this.maxEffectiveBalanceElectra = maxEffectiveBalanceElectra; - return this; - } - - public ElectraBuilder pendingBalanceDepositsLimit(final Integer pendingBalanceDepositsLimit) { - checkNotNull(pendingBalanceDepositsLimit); - this.pendingBalanceDepositsLimit = pendingBalanceDepositsLimit; - return this; - } - - public ElectraBuilder pendingPartialWithdrawalsLimit( - final Integer pendingPartialWithdrawalsLimit) { - checkNotNull(pendingPartialWithdrawalsLimit); - this.pendingPartialWithdrawalsLimit = pendingPartialWithdrawalsLimit; - return this; - } - - public ElectraBuilder pendingConsolidationsLimit(final Integer pendingConsolidationsLimit) { - checkNotNull(pendingConsolidationsLimit); - this.pendingConsolidationsLimit = pendingConsolidationsLimit; - return this; - } - - public ElectraBuilder whistleblowerRewardQuotientElectra( - final Integer whistleblowerRewardQuotientElectra) { - checkNotNull(whistleblowerRewardQuotientElectra); - this.whistleblowerRewardQuotientElectra = whistleblowerRewardQuotientElectra; - return this; - } - - public ElectraBuilder minSlashingPenaltyQuotientElectra( - final Integer minSlashingPenaltyQuotientElectra) { - checkNotNull(minSlashingPenaltyQuotientElectra); - this.minSlashingPenaltyQuotientElectra = minSlashingPenaltyQuotientElectra; - return this; - } - - public ElectraBuilder maxPartialWithdrawalsPerPayload( - final Integer maxPartialWithdrawalsPerPayload) { - checkNotNull(maxPartialWithdrawalsPerPayload); - this.maxPartialWithdrawalsPerPayload = maxPartialWithdrawalsPerPayload; - return this; - } - - public ElectraBuilder maxAttesterSlashingsElectra(final Integer maxAttesterSlashingsElectra) { - checkNotNull(maxAttesterSlashingsElectra); - this.maxAttesterSlashingsElectra = maxAttesterSlashingsElectra; - return this; - } - - public ElectraBuilder maxAttestationsElectra(final Integer maxAttestationsElectra) { - checkNotNull(maxAttestationsElectra); - this.maxAttestationsElectra = maxAttestationsElectra; - return this; - } - - public ElectraBuilder maxConsolidations(final Integer maxConsolidations) { - checkNotNull(maxConsolidations); - this.maxConsolidations = maxConsolidations; - return this; - } - - public ElectraBuilder dataColumnSidecarSubnetCount(final Integer blobSidecarSubnetCount) { - this.dataColumnSidecarSubnetCount = blobSidecarSubnetCount; - return this; - } - - public ElectraBuilder custodyRequirement(final Integer custodyRequirement) { - checkNotNull(custodyRequirement); - this.custodyRequirement = custodyRequirement; - return this; - } - - public ElectraBuilder minEpochsForDataColumnSidecarsRequests(final Integer custodyEpochs) { - checkNotNull(custodyEpochs); - this.minEpochsForDataColumnSidecarsRequests = custodyEpochs; - return this; - } - - public ElectraBuilder maxRequestDataColumnSidecars(final Integer maxRequestDataColumnSidecars) { - checkNotNull(maxRequestDataColumnSidecars); - this.maxRequestDataColumnSidecars = maxRequestDataColumnSidecars; - return this; - } - - @Override - public void validate() { - if (electraForkEpoch == null) { - electraForkEpoch = SpecConfig.FAR_FUTURE_EPOCH; - electraForkVersion = SpecBuilderUtil.PLACEHOLDER_FORK_VERSION; - } - - // Fill default zeros if fork is unsupported - if (electraForkEpoch.equals(FAR_FUTURE_EPOCH)) { - SpecBuilderUtil.fillMissingValuesWithZeros(this); - } - - validateConstants(); - } - - @Override - public Map getValidationMap() { - final Map constants = new HashMap<>(); - - constants.put("electraForkEpoch", electraForkEpoch); - constants.put("electraForkVersion", electraForkVersion); - constants.put("maxDepositReceiptsPerPayload", maxDepositReceiptsPerPayload); - constants.put("minPerEpochChurnLimitElectra", minPerEpochChurnLimitElectra); - constants.put("maxExecutionLayerExits", maxExecutionLayerExits); - constants.put("minActivationBalance", minActivationBalance); - constants.put("maxEffectiveBalanceElectra", maxEffectiveBalanceElectra); - constants.put("pendingBalanceDepositsLimit", pendingBalanceDepositsLimit); - constants.put("pendingPartialWithdrawalsLimit", pendingPartialWithdrawalsLimit); - constants.put("pendingConsolidationsLimit", pendingConsolidationsLimit); - constants.put("whistleblowerRewardQuotientElectra", whistleblowerRewardQuotientElectra); - constants.put("minSlashingPenaltyQuotientElectra", minSlashingPenaltyQuotientElectra); - constants.put("maxPartialWithdrawalsPerPayload", maxPartialWithdrawalsPerPayload); - constants.put("maxAttesterSlashingsElectra", maxAttesterSlashingsElectra); - constants.put("maxAttestationsElectra", maxAttestationsElectra); - constants.put("maxConsolidations", maxConsolidations); - constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); - constants.put("custodyRequirement", custodyRequirement); - constants.put("minEpochsForDataColumnSidecarsRequests", minEpochsForDataColumnSidecarsRequests); - constants.put("maxRequestDataColumnSidecars", maxRequestDataColumnSidecars); - - return constants; - } - - @Override - public void addOverridableItemsToRawConfig(final BiConsumer rawConfig) { - rawConfig.accept("ELECTRA_FORK_EPOCH", electraForkEpoch); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java index 72abe2516d5..868ea7df9e1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/SpecConfigBuilder.java @@ -28,7 +28,7 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfigPhase0; @SuppressWarnings({"UnusedReturnValue", "unused"}) @@ -130,12 +130,12 @@ public class SpecConfigBuilder { private Integer reorgParentWeightThreshold = 160; - private final BuilderChain builderChain = + private final BuilderChain builderChain = BuilderChain.create(new AltairBuilder()) .appendBuilder(new BellatrixBuilder()) .appendBuilder(new CapellaBuilder()) .appendBuilder(new DenebBuilder()) - .appendBuilder(new ElectraBuilder()); + .appendBuilder(new Eip7594Builder()); public SpecConfig build() { builderChain.addOverridableItemsToRawConfig( @@ -727,8 +727,8 @@ public SpecConfigBuilder denebBuilder(final Consumer consumer) { return this; } - public SpecConfigBuilder electraBuilder(final Consumer consumer) { - builderChain.withBuilder(ElectraBuilder.class, consumer); + public SpecConfigBuilder eip7594Builder(final Consumer consumer) { + builderChain.withBuilder(Eip7594Builder.class, consumer); return this; } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/Cell.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/Cell.java similarity index 94% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/Cell.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/Cell.java index ec86d044910..729bcb2c9e9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/Cell.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/Cell.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import org.apache.tuweni.bytes.Bytes; import tech.pegasys.teku.infrastructure.ssz.collections.impl.SszByteVectorImpl; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/CellSchema.java similarity index 87% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/CellSchema.java index 0f425109141..0135682971d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/CellSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/CellSchema.java @@ -11,17 +11,17 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import org.apache.tuweni.bytes.Bytes; import tech.pegasys.teku.infrastructure.ssz.schema.collections.impl.SszByteVectorSchemaImpl; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; public class CellSchema extends SszByteVectorSchemaImpl { - public CellSchema(final SpecConfigElectra specConfig) { + public CellSchema(final SpecConfigEip7594 specConfig) { super( SpecConfigDeneb.BYTES_PER_FIELD_ELEMENT.longValue() * specConfig.getFieldElementsPerCell().longValue()); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumn.java similarity index 93% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumn.java index 09747dbdb0b..c6f5b2e80e8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumn.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumn.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.impl.SszListImpl; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSchema.java similarity index 86% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSchema.java index 97464c8e4fb..0e104ec375e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSchema.java @@ -11,16 +11,16 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import java.util.List; import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; public class DataColumnSchema extends AbstractSszListSchema { - public DataColumnSchema(final SpecConfigElectra specConfig) { + public DataColumnSchema(final SpecConfigEip7594 specConfig) { super(new CellSchema(specConfig), specConfig.getMaxBlobCommitmentsPerBlock()); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java similarity index 98% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java index a9787ee97ad..214c7b6d794 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecar.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import java.util.List; import org.apache.tuweni.bytes.Bytes32; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java similarity index 94% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java index 99d70b62681..f40c10bf230 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/electra/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blobs.versions.electra; +package tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594; import java.util.List; import org.apache.tuweni.bytes.Bytes32; @@ -27,7 +27,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZGCommitment; import tech.pegasys.teku.kzg.KZGProof; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeaderSchema; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; @@ -55,7 +55,7 @@ public class DataColumnSidecarSchema DataColumnSidecarSchema( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, final DataColumnSchema dataColumnSchema, - final SpecConfigElectra specConfig) { + final SpecConfigEip7594 specConfig) { super( "DataColumnSidecar", namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), @@ -71,7 +71,8 @@ public class DataColumnSidecarSchema namedSchema(FIELD_SIGNED_BLOCK_HEADER, signedBeaconBlockHeaderSchema), namedSchema( FIELD_KZG_COMMITMENT_INCLUSION_PROOF, - SszBytes32VectorSchema.create(specConfig.getKzgCommitmentInclusionProofDepth()))); + SszBytes32VectorSchema.create( + specConfig.getKzgCommitmentsInclusionProofDepth().intValue()))); } @SuppressWarnings("unchecked") @@ -152,7 +153,7 @@ public DataColumnSidecar create( public static DataColumnSidecarSchema create( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, final DataColumnSchema dataColumnSchema, - final SpecConfigElectra specConfig) { + final SpecConfigEip7594 specConfig) { return new DataColumnSidecarSchema(signedBeaconBlockHeaderSchema, dataColumnSchema, specConfig); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java index 2240f7549e7..082b8fa6dbf 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBody.java @@ -28,8 +28,8 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BlindedBeaconBlockBodyCapella; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BlindedBeaconBlockBodyDeneb; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodyElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyEip7594; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BlindedBeaconBlockBodyEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary; @@ -118,7 +118,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } @@ -126,7 +126,7 @@ default Optional toBlindedVersionDeneb() { return Optional.empty(); } - default Optional toBlindedVersionElectra() { + default Optional toBlindedVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java index 633678af8b7..fb1d76fc7cd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/BeaconBlockBodySchema.java @@ -24,7 +24,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.bellatrix.BeaconBlockBodySchemaBellatrix; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BeaconBlockBodySchemaCapella; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodySchemaDeneb; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -66,7 +66,7 @@ default Optional> toVersionDeneb() { return Optional.empty(); } - default Optional> toVersionElectra() { + default Optional> toVersionEip7594() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyBuilderElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyBuilderEip7594.java similarity index 73% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyBuilderElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyBuilderEip7594.java index 8f1bf8d71fa..7eab2263082 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyBuilderElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyBuilderEip7594.java @@ -11,21 +11,21 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyBuilderDeneb; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectraImpl; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectraImpl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594Impl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderEip7594Impl; import tech.pegasys.teku.spec.datastructures.type.SszSignature; -public class BeaconBlockBodyBuilderElectra extends BeaconBlockBodyBuilderDeneb { +public class BeaconBlockBodyBuilderEip7594 extends BeaconBlockBodyBuilderDeneb { - public BeaconBlockBodyBuilderElectra( - final BeaconBlockBodySchema schema, - final BeaconBlockBodySchema blindedSchema) { + public BeaconBlockBodyBuilderEip7594( + final BeaconBlockBodySchema schema, + final BeaconBlockBodySchema blindedSchema) { super(schema, blindedSchema); } @@ -38,9 +38,9 @@ protected void validate() { public BeaconBlockBody build() { validate(); if (isBlinded()) { - final BlindedBeaconBlockBodySchemaElectraImpl schema = - getAndValidateSchema(true, BlindedBeaconBlockBodySchemaElectraImpl.class); - return new BlindedBeaconBlockBodyElectraImpl( + final BlindedBeaconBlockBodySchemaEip7594Impl schema = + getAndValidateSchema(true, BlindedBeaconBlockBodySchemaEip7594Impl.class); + return new BlindedBeaconBlockBodyEip7594Impl( schema, new SszSignature(randaoReveal), eth1Data, @@ -51,15 +51,15 @@ public BeaconBlockBody build() { deposits, voluntaryExits, syncAggregate, - (ExecutionPayloadHeaderElectraImpl) - executionPayloadHeader.toVersionElectra().orElseThrow(), + (ExecutionPayloadHeaderEip7594Impl) + executionPayloadHeader.toVersionEip7594().orElseThrow(), getBlsToExecutionChanges(), getBlobKzgCommitments()); } - final BeaconBlockBodySchemaElectraImpl schema = - getAndValidateSchema(false, BeaconBlockBodySchemaElectraImpl.class); - return new BeaconBlockBodyElectraImpl( + final BeaconBlockBodySchemaEip7594Impl schema = + getAndValidateSchema(false, BeaconBlockBodySchemaEip7594Impl.class); + return new BeaconBlockBodyEip7594Impl( schema, new SszSignature(randaoReveal), eth1Data, @@ -70,7 +70,7 @@ public BeaconBlockBody build() { deposits, voluntaryExits, syncAggregate, - (ExecutionPayloadElectraImpl) executionPayload.toVersionElectra().orElseThrow(), + (ExecutionPayloadEip7594Impl) executionPayload.toVersionEip7594().orElseThrow(), getBlsToExecutionChanges(), getBlobKzgCommitments()); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594.java similarity index 72% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594.java index 85bac9bad45..9921802f69a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594.java @@ -11,30 +11,30 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import java.util.Optional; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594; -public interface BeaconBlockBodyElectra extends BeaconBlockBodyDeneb { - static BeaconBlockBodyElectra required(final BeaconBlockBody body) { - return body.toVersionElectra() +public interface BeaconBlockBodyEip7594 extends BeaconBlockBodyDeneb { + static BeaconBlockBodyEip7594 required(final BeaconBlockBody body) { + return body.toVersionEip7594() .orElseThrow( () -> new IllegalArgumentException( - "Expected Electra block body but got " + body.getClass().getSimpleName())); + "Expected EIP7594 block body but got " + body.getClass().getSimpleName())); } @Override - BeaconBlockBodySchemaElectra getSchema(); + BeaconBlockBodySchemaEip7594 getSchema(); @Override - ExecutionPayloadElectra getExecutionPayload(); + ExecutionPayloadEip7594 getExecutionPayload(); @Override - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.of(this); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594Impl.java similarity index 83% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594Impl.java index 1675aef0b71..a0bd1d12d57 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodyElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodyEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import static com.google.common.base.Preconditions.checkArgument; @@ -24,8 +24,8 @@ import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectraImpl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594Impl; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -35,9 +35,9 @@ import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.type.SszSignature; -public class BeaconBlockBodyElectraImpl +public class BeaconBlockBodyEip7594Impl extends Container12< - BeaconBlockBodyElectraImpl, + BeaconBlockBodyEip7594Impl, SszSignature, Eth1Data, SszBytes32, @@ -47,13 +47,13 @@ public class BeaconBlockBodyElectraImpl SszList, SszList, SyncAggregate, - ExecutionPayloadElectraImpl, + ExecutionPayloadEip7594Impl, SszList, SszList> - implements BeaconBlockBodyElectra { + implements BeaconBlockBodyEip7594 { - BeaconBlockBodyElectraImpl( - BeaconBlockBodySchemaElectraImpl type, + BeaconBlockBodyEip7594Impl( + BeaconBlockBodySchemaEip7594Impl type, SszSignature randaoReveal, Eth1Data eth1Data, SszBytes32 graffiti, @@ -63,7 +63,7 @@ public class BeaconBlockBodyElectraImpl SszList deposits, SszList voluntaryExits, SyncAggregate syncAggregate, - ExecutionPayloadElectraImpl executionPayload, + ExecutionPayloadEip7594Impl executionPayload, SszList blsToExecutionChanges, SszList blobKzgCommitments) { super( @@ -82,21 +82,21 @@ public class BeaconBlockBodyElectraImpl blobKzgCommitments); } - BeaconBlockBodyElectraImpl(final BeaconBlockBodySchemaElectraImpl type) { + BeaconBlockBodyEip7594Impl(final BeaconBlockBodySchemaEip7594Impl type) { super(type); } - BeaconBlockBodyElectraImpl( - final BeaconBlockBodySchemaElectraImpl type, final TreeNode backingNode) { + BeaconBlockBodyEip7594Impl( + final BeaconBlockBodySchemaEip7594Impl type, final TreeNode backingNode) { super(type, backingNode); } - public static BeaconBlockBodyElectraImpl required(final BeaconBlockBody body) { + public static BeaconBlockBodyEip7594Impl required(final BeaconBlockBody body) { checkArgument( - body instanceof BeaconBlockBodyElectraImpl, - "Expected Electra block body but got %s", + body instanceof BeaconBlockBodyEip7594Impl, + "Expected EIP7594 block body but got %s", body.getClass()); - return (BeaconBlockBodyElectraImpl) body; + return (BeaconBlockBodyEip7594Impl) body; } @Override @@ -155,7 +155,7 @@ public SyncAggregate getSyncAggregate() { } @Override - public ExecutionPayloadElectra getExecutionPayload() { + public ExecutionPayloadEip7594 getExecutionPayload() { return getField9(); } @@ -170,7 +170,7 @@ public SszList getBlobKzgCommitments() { } @Override - public BeaconBlockBodySchemaElectraImpl getSchema() { - return (BeaconBlockBodySchemaElectraImpl) super.getSchema(); + public BeaconBlockBodySchemaEip7594Impl getSchema() { + return (BeaconBlockBodySchemaEip7594Impl) super.getSchema(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594.java similarity index 73% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594.java index cc0aaf4002d..25464319424 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import static com.google.common.base.Preconditions.checkArgument; @@ -19,19 +19,19 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodySchemaDeneb; -public interface BeaconBlockBodySchemaElectra +public interface BeaconBlockBodySchemaEip7594 extends BeaconBlockBodySchemaDeneb { - static BeaconBlockBodySchemaElectra required(final BeaconBlockBodySchema schema) { + static BeaconBlockBodySchemaEip7594 required(final BeaconBlockBodySchema schema) { checkArgument( - schema instanceof BeaconBlockBodySchemaElectra, - "Expected a BeaconBlockBodySchemaElectra but was %s", + schema instanceof BeaconBlockBodySchemaEip7594, + "Expected a BeaconBlockBodySchemaEip7594 but was %s", schema.getClass()); - return (BeaconBlockBodySchemaElectra) schema; + return (BeaconBlockBodySchemaEip7594) schema; } @Override - default Optional> toVersionElectra() { + default Optional> toVersionEip7594() { return Optional.of(this); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594Impl.java similarity index 91% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594Impl.java index a432989db64..4f644d4b2c8 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BeaconBlockBodySchemaElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BeaconBlockBodySchemaEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import it.unimi.dsi.fastutil.longs.LongList; import java.util.function.Function; @@ -23,7 +23,7 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.tree.GIndexUtil; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobKzgCommitmentsSchema; import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; @@ -32,8 +32,8 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregateSchema; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectraImpl; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadSchemaElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594Impl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadSchemaEip7594; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.Attestation.AttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -47,9 +47,9 @@ import tech.pegasys.teku.spec.datastructures.type.SszSignature; import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; -public class BeaconBlockBodySchemaElectraImpl +public class BeaconBlockBodySchemaEip7594Impl extends ContainerSchema12< - BeaconBlockBodyElectraImpl, + BeaconBlockBodyEip7594Impl, SszSignature, Eth1Data, SszBytes32, @@ -59,12 +59,12 @@ public class BeaconBlockBodySchemaElectraImpl SszList, SszList, SyncAggregate, - ExecutionPayloadElectraImpl, + ExecutionPayloadEip7594Impl, SszList, SszList> - implements BeaconBlockBodySchemaElectra { + implements BeaconBlockBodySchemaEip7594 { - protected BeaconBlockBodySchemaElectraImpl( + protected BeaconBlockBodySchemaEip7594Impl( final String containerName, final NamedSchema randaoRevealSchema, final NamedSchema eth1DataSchema, @@ -75,7 +75,7 @@ protected BeaconBlockBodySchemaElectraImpl( final NamedSchema> depositsSchema, final NamedSchema> voluntaryExitsSchema, final NamedSchema syncAggregateSchema, - final NamedSchema executionPayloadSchema, + final NamedSchema executionPayloadSchema, final NamedSchema> blsToExecutionChange, final NamedSchema> blobKzgCommitments) { super( @@ -94,13 +94,13 @@ protected BeaconBlockBodySchemaElectraImpl( blobKzgCommitments); } - public static BeaconBlockBodySchemaElectraImpl create( - final SpecConfigElectra specConfig, + public static BeaconBlockBodySchemaEip7594Impl create( + final SpecConfigEip7594 specConfig, final AttesterSlashingSchema attesterSlashingSchema, final SignedBlsToExecutionChangeSchema blsToExecutionChangeSchema, final BlobKzgCommitmentsSchema blobKzgCommitmentsSchema, final String containerName) { - return new BeaconBlockBodySchemaElectraImpl( + return new BeaconBlockBodySchemaEip7594Impl( containerName, namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), namedSchema(BlockBodyFields.ETH1_DATA, Eth1Data.SSZ_SCHEMA), @@ -127,7 +127,7 @@ public static BeaconBlockBodySchemaElectraImpl create( BlockBodyFields.SYNC_AGGREGATE, SyncAggregateSchema.create(specConfig.getSyncCommitteeSize())), namedSchema( - BlockBodyFields.EXECUTION_PAYLOAD, new ExecutionPayloadSchemaElectra(specConfig)), + BlockBodyFields.EXECUTION_PAYLOAD, new ExecutionPayloadSchemaEip7594(specConfig)), namedSchema( BlockBodyFields.BLS_TO_EXECUTION_CHANGES, SszListSchema.create( @@ -138,13 +138,13 @@ BlockBodyFields.EXECUTION_PAYLOAD, new ExecutionPayloadSchemaElectra(specConfig) @Override public SafeFuture createBlockBody( final Function> bodyBuilder) { - final BeaconBlockBodyBuilderElectra builder = new BeaconBlockBodyBuilderElectra(this, null); + final BeaconBlockBodyBuilderEip7594 builder = new BeaconBlockBodyBuilderEip7594(this, null); return bodyBuilder.apply(builder).thenApply(__ -> builder.build()); } @Override public BeaconBlockBody createEmpty() { - return new BeaconBlockBodyElectraImpl(this); + return new BeaconBlockBodyEip7594Impl(this); } @SuppressWarnings("unchecked") @@ -187,8 +187,8 @@ public SyncAggregateSchema getSyncAggregateSchema() { } @Override - public BeaconBlockBodyElectraImpl createFromBackingNode(final TreeNode node) { - return new BeaconBlockBodyElectraImpl(this, node); + public BeaconBlockBodyEip7594Impl createFromBackingNode(final TreeNode node) { + return new BeaconBlockBodyEip7594Impl(this, node); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594.java similarity index 74% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594.java index e4118f7a27e..14947e525c9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594.java @@ -11,27 +11,27 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import java.util.Optional; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BlindedBeaconBlockBodyDeneb; -public interface BlindedBeaconBlockBodyElectra extends BlindedBeaconBlockBodyDeneb { - static BlindedBeaconBlockBodyElectra required(final BeaconBlockBody body) { - return body.toBlindedVersionElectra() +public interface BlindedBeaconBlockBodyEip7594 extends BlindedBeaconBlockBodyDeneb { + static BlindedBeaconBlockBodyEip7594 required(final BeaconBlockBody body) { + return body.toBlindedVersionEip7594() .orElseThrow( () -> new IllegalArgumentException( - "Expected an Electra blinded block body but got: " + "Expected an EIP7594 blinded block body but got: " + body.getClass().getSimpleName())); } @Override - default Optional toBlindedVersionElectra() { + default Optional toBlindedVersionEip7594() { return Optional.of(this); } @Override - BlindedBeaconBlockBodySchemaElectra getSchema(); + BlindedBeaconBlockBodySchemaEip7594 getSchema(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594Impl.java similarity index 83% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594Impl.java index afb0f4634fd..0439fd8db72 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodyElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodyEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import static com.google.common.base.Preconditions.checkArgument; @@ -25,7 +25,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectraImpl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderEip7594Impl; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.Deposit; @@ -35,9 +35,9 @@ import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.type.SszSignature; -class BlindedBeaconBlockBodyElectraImpl +class BlindedBeaconBlockBodyEip7594Impl extends Container12< - BlindedBeaconBlockBodyElectraImpl, + BlindedBeaconBlockBodyEip7594Impl, SszSignature, Eth1Data, SszBytes32, @@ -47,22 +47,22 @@ class BlindedBeaconBlockBodyElectraImpl SszList, SszList, SyncAggregate, - ExecutionPayloadHeaderElectraImpl, + ExecutionPayloadHeaderEip7594Impl, SszList, SszList> - implements BlindedBeaconBlockBodyElectra { + implements BlindedBeaconBlockBodyEip7594 { - BlindedBeaconBlockBodyElectraImpl(final BlindedBeaconBlockBodySchemaElectraImpl type) { + BlindedBeaconBlockBodyEip7594Impl(final BlindedBeaconBlockBodySchemaEip7594Impl type) { super(type); } - BlindedBeaconBlockBodyElectraImpl( - final BlindedBeaconBlockBodySchemaElectraImpl type, final TreeNode backingNode) { + BlindedBeaconBlockBodyEip7594Impl( + final BlindedBeaconBlockBodySchemaEip7594Impl type, final TreeNode backingNode) { super(type, backingNode); } - BlindedBeaconBlockBodyElectraImpl( - final BlindedBeaconBlockBodySchemaElectraImpl type, + BlindedBeaconBlockBodyEip7594Impl( + final BlindedBeaconBlockBodySchemaEip7594Impl type, final SszSignature randaoReveal, final Eth1Data eth1Data, final SszBytes32 graffiti, @@ -72,7 +72,7 @@ class BlindedBeaconBlockBodyElectraImpl final SszList deposits, final SszList voluntaryExits, final SyncAggregate syncAggregate, - final ExecutionPayloadHeaderElectraImpl executionPayloadHeader, + final ExecutionPayloadHeaderEip7594Impl executionPayloadHeader, final SszList blsToExecutionChanges, final SszList blobKzgCommitments) { super( @@ -91,12 +91,12 @@ class BlindedBeaconBlockBodyElectraImpl blobKzgCommitments); } - public static BlindedBeaconBlockBodyElectraImpl required(final BeaconBlockBody body) { + public static BlindedBeaconBlockBodyEip7594Impl required(final BeaconBlockBody body) { checkArgument( - body instanceof BlindedBeaconBlockBodyElectraImpl, - "Expected Electra blinded block body but got %s", + body instanceof BlindedBeaconBlockBodyEip7594Impl, + "Expected EIP7594 blinded block body but got %s", body.getClass()); - return (BlindedBeaconBlockBodyElectraImpl) body; + return (BlindedBeaconBlockBodyEip7594Impl) body; } @Override @@ -170,7 +170,7 @@ public SszList getBlobKzgCommitments() { } @Override - public BlindedBeaconBlockBodySchemaElectraImpl getSchema() { - return (BlindedBeaconBlockBodySchemaElectraImpl) super.getSchema(); + public BlindedBeaconBlockBodySchemaEip7594Impl getSchema() { + return (BlindedBeaconBlockBodySchemaEip7594Impl) super.getSchema(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594.java similarity index 74% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594.java index 58cdb1d860f..8561d6fd35e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594.java @@ -11,21 +11,21 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import static com.google.common.base.Preconditions.checkArgument; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BlindedBeaconBlockBodySchemaDeneb; -public interface BlindedBeaconBlockBodySchemaElectra +public interface BlindedBeaconBlockBodySchemaEip7594 extends BlindedBeaconBlockBodySchemaDeneb { - static BlindedBeaconBlockBodySchemaElectra required(final BeaconBlockBodySchema schema) { + static BlindedBeaconBlockBodySchemaEip7594 required(final BeaconBlockBodySchema schema) { checkArgument( - schema instanceof BlindedBeaconBlockBodySchemaElectra, - "Expected a BlindedBeaconBlockBodySchemaElectra but was %s", + schema instanceof BlindedBeaconBlockBodySchemaEip7594, + "Expected a BlindedBeaconBlockBodySchemaEip7594 but was %s", schema.getClass()); - return (BlindedBeaconBlockBodySchemaElectra) schema; + return (BlindedBeaconBlockBodySchemaEip7594) schema; } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594Impl.java similarity index 88% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594Impl.java index f19d45bfe4d..30625bde096 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/electra/BlindedBeaconBlockBodySchemaElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blocks/blockbody/versions/eip7594/BlindedBeaconBlockBodySchemaEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra; +package tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594; import it.unimi.dsi.fastutil.longs.LongList; import java.util.function.Function; @@ -23,7 +23,7 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.tree.GIndexUtil; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobKzgCommitmentsSchema; import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; @@ -31,8 +31,8 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.common.BlockBodyFields; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregate; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.SyncAggregateSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectraImpl; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderSchemaElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderEip7594Impl; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderSchemaEip7594; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.Attestation.AttestationSchema; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -46,9 +46,9 @@ import tech.pegasys.teku.spec.datastructures.type.SszSignature; import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; -public class BlindedBeaconBlockBodySchemaElectraImpl +public class BlindedBeaconBlockBodySchemaEip7594Impl extends ContainerSchema12< - BlindedBeaconBlockBodyElectraImpl, + BlindedBeaconBlockBodyEip7594Impl, SszSignature, Eth1Data, SszBytes32, @@ -58,12 +58,12 @@ public class BlindedBeaconBlockBodySchemaElectraImpl SszList, SszList, SyncAggregate, - ExecutionPayloadHeaderElectraImpl, + ExecutionPayloadHeaderEip7594Impl, SszList, SszList> - implements BlindedBeaconBlockBodySchemaElectra { + implements BlindedBeaconBlockBodySchemaEip7594 { - private BlindedBeaconBlockBodySchemaElectraImpl( + private BlindedBeaconBlockBodySchemaEip7594Impl( final String containerName, final NamedSchema randaoReveal, final NamedSchema eth1Data, @@ -74,7 +74,7 @@ private BlindedBeaconBlockBodySchemaElectraImpl( final NamedSchema> deposits, final NamedSchema> voluntaryExits, final NamedSchema syncAggregate, - final NamedSchema executionPayloadHeader, + final NamedSchema executionPayloadHeader, final NamedSchema> blsToExecutionChanges, final NamedSchema> blobKzgCommitments) { super( @@ -93,13 +93,13 @@ private BlindedBeaconBlockBodySchemaElectraImpl( blobKzgCommitments); } - public static BlindedBeaconBlockBodySchemaElectraImpl create( - final SpecConfigElectra specConfig, + public static BlindedBeaconBlockBodySchemaEip7594Impl create( + final SpecConfigEip7594 specConfig, final AttesterSlashingSchema attesterSlashingSchema, final SignedBlsToExecutionChangeSchema signedBlsToExecutionChangeSchema, final BlobKzgCommitmentsSchema blobKzgCommitmentsSchema, final String containerName) { - return new BlindedBeaconBlockBodySchemaElectraImpl( + return new BlindedBeaconBlockBodySchemaEip7594Impl( containerName, namedSchema(BlockBodyFields.RANDAO_REVEAL, SszSignatureSchema.INSTANCE), namedSchema(BlockBodyFields.ETH1_DATA, Eth1Data.SSZ_SCHEMA), @@ -127,7 +127,7 @@ public static BlindedBeaconBlockBodySchemaElectraImpl create( SyncAggregateSchema.create(specConfig.getSyncCommitteeSize())), namedSchema( BlockBodyFields.EXECUTION_PAYLOAD_HEADER, - new ExecutionPayloadHeaderSchemaElectra(specConfig)), + new ExecutionPayloadHeaderSchemaEip7594(specConfig)), namedSchema( BlockBodyFields.BLS_TO_EXECUTION_CHANGES, SszListSchema.create( @@ -138,13 +138,13 @@ public static BlindedBeaconBlockBodySchemaElectraImpl create( @Override public SafeFuture createBlockBody( final Function> bodyBuilder) { - final BeaconBlockBodyBuilderElectra builder = new BeaconBlockBodyBuilderElectra(null, this); + final BeaconBlockBodyBuilderEip7594 builder = new BeaconBlockBodyBuilderEip7594(null, this); return bodyBuilder.apply(builder).thenApply(__ -> builder.build()); } @Override - public BlindedBeaconBlockBodyElectraImpl createEmpty() { - return new BlindedBeaconBlockBodyElectraImpl(this); + public BlindedBeaconBlockBodyEip7594Impl createEmpty() { + return new BlindedBeaconBlockBodyEip7594Impl(this); } @SuppressWarnings("unchecked") @@ -187,13 +187,13 @@ public SyncAggregateSchema getSyncAggregateSchema() { } @Override - public BlindedBeaconBlockBodyElectraImpl createFromBackingNode(final TreeNode node) { - return new BlindedBeaconBlockBodyElectraImpl(this, node); + public BlindedBeaconBlockBodyEip7594Impl createFromBackingNode(final TreeNode node) { + return new BlindedBeaconBlockBodyEip7594Impl(this, node); } @Override - public ExecutionPayloadHeaderSchemaElectra getExecutionPayloadHeaderSchema() { - return (ExecutionPayloadHeaderSchemaElectra) + public ExecutionPayloadHeaderSchemaEip7594 getExecutionPayloadHeaderSchema() { + return (ExecutionPayloadHeaderSchemaEip7594) getChildSchema(getFieldIndex(BlockBodyFields.EXECUTION_PAYLOAD_HEADER)); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java index 848ca04357a..bcf3dc3656f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayload.java @@ -25,7 +25,7 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.capella.ExecutionPayloadCapella; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadDeneb; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadEip7594; public interface ExecutionPayload extends ExecutionPayloadSummary, SszContainer, BuilderPayload { @@ -57,7 +57,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadBuilder.java index 3304bf19458..056e3f8052a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadBuilder.java @@ -21,8 +21,6 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; public interface ExecutionPayloadBuilder { ExecutionPayloadBuilder parentHash(Bytes32 parentHash); @@ -59,9 +57,5 @@ public interface ExecutionPayloadBuilder { ExecutionPayloadBuilder excessBlobGas(Supplier excessBlobGasSupplier); - ExecutionPayloadBuilder depositReceipts(Supplier> depositReceiptsSupplier); - - ExecutionPayloadBuilder exits(Supplier> exitsSupplier); - ExecutionPayload build(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java index 13a8a645cf5..ae2d7803630 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadFields.java @@ -35,11 +35,7 @@ public enum ExecutionPayloadFields implements SszFieldName { TRANSACTIONS_ROOT, WITHDRAWALS_ROOT, BLOB_GAS_USED, - EXCESS_BLOB_GAS, - DEPOSIT_RECEIPTS, - DEPOSIT_RECEIPTS_ROOT, - EXITS, - EXITS_ROOT; + EXCESS_BLOB_GAS; private final String sszFieldName; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java index 964692b03f4..ba20035c13c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeader.java @@ -19,7 +19,7 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.bellatrix.ExecutionPayloadHeaderBellatrix; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.ExecutionPayloadHeaderCapella; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadHeaderDeneb; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderEip7594; public interface ExecutionPayloadHeader extends ExecutionPayloadSummary, SszContainer { @@ -39,7 +39,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java index b7412ed2a3d..ead0c8f0bd4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadHeaderBuilder.java @@ -55,9 +55,5 @@ public interface ExecutionPayloadHeaderBuilder { ExecutionPayloadHeaderBuilder excessBlobGas(Supplier excessBlobGasSupplier); - ExecutionPayloadHeaderBuilder depositReceiptsRoot(Supplier depositReceiptsRootSupplier); - - ExecutionPayloadHeaderBuilder exitsRoot(Supplier exitsRootSupplier); - ExecutionPayloadHeader build(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadSchema.java index 82ee260fb40..a7fb9238aa5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/ExecutionPayloadSchema.java @@ -22,10 +22,6 @@ import tech.pegasys.teku.spec.datastructures.builder.BuilderPayloadSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.WithdrawalSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; public interface ExecutionPayloadSchema extends SszContainerSchema, BuilderPayloadSchema { @@ -39,16 +35,6 @@ public interface ExecutionPayloadSchema WithdrawalSchema getWithdrawalSchemaRequired(); - SszListSchema> - getDepositReceiptsSchemaRequired(); - - DepositReceiptSchema getDepositReceiptSchemaRequired(); - - SszListSchema> - getExecutionLayerExitsSchemaRequired(); - - ExecutionLayerExitSchema getExecutionLayerExitSchemaRequired(); - LongList getBlindedNodeGeneralizedIndices(); ExecutionPayload createExecutionPayload(Consumer builderConsumer); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadBuilderBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadBuilderBellatrix.java index 3f4d73cfb92..58999ccbd8b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadBuilderBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadBuilderBellatrix.java @@ -29,8 +29,6 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; public class ExecutionPayloadBuilderBellatrix implements ExecutionPayloadBuilder { private ExecutionPayloadSchemaBellatrix schema; @@ -154,17 +152,6 @@ public ExecutionPayloadBuilder excessBlobGas(final Supplier excessBlobGa return this; } - @Override - public ExecutionPayloadBuilder depositReceipts( - final Supplier> depositReceiptsSupplier) { - return this; - } - - @Override - public ExecutionPayloadBuilder exits(final Supplier> exitsSupplier) { - return this; - } - protected void validateSchema() { checkNotNull(schema, "schema must be specified"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java index f6d5d0bd9d5..2df8d8af280 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadHeaderBuilderBellatrix.java @@ -152,17 +152,6 @@ public ExecutionPayloadHeaderBuilder excessBlobGas(final Supplier excess return this; } - @Override - public ExecutionPayloadHeaderBuilder depositReceiptsRoot( - final Supplier depositReceiptsRootSupplier) { - return this; - } - - @Override - public ExecutionPayloadHeaderBuilder exitsRoot(final Supplier exitsRootSupplier) { - return this; - } - protected void validateSchema() { checkNotNull(schema, "schema must be specified"); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadSchemaBellatrix.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadSchemaBellatrix.java index c2475809eca..1df2a1c9ea0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadSchemaBellatrix.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/bellatrix/ExecutionPayloadSchemaBellatrix.java @@ -51,10 +51,6 @@ import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.WithdrawalSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; public class ExecutionPayloadSchemaBellatrix extends ContainerSchema14< @@ -116,29 +112,6 @@ public WithdrawalSchema getWithdrawalSchemaRequired() { throw new IllegalStateException("Attempted to get a withdrawal schema from bellatrix"); } - @Override - public SszListSchema> - getDepositReceiptsSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipts schema from bellatrix"); - } - - @Override - public DepositReceiptSchema getDepositReceiptSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipt schema from bellatrix"); - } - - @Override - public SszListSchema> - getExecutionLayerExitsSchemaRequired() { - throw new IllegalStateException("Attempted to get execution layer exits schema from bellatrix"); - } - - @Override - public ExecutionLayerExitSchema getExecutionLayerExitSchemaRequired() { - throw new IllegalStateException( - "Attempted to get a execution layer exit schema from bellatrix"); - } - @Override public LongList getBlindedNodeGeneralizedIndices() { return LongList.of(getChildGeneralizedIndex(getFieldIndex(TRANSACTIONS))); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/capella/ExecutionPayloadSchemaCapella.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/capella/ExecutionPayloadSchemaCapella.java index 8d7f82b61d2..78a0761b9fd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/capella/ExecutionPayloadSchemaCapella.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/capella/ExecutionPayloadSchemaCapella.java @@ -50,10 +50,6 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; import tech.pegasys.teku.spec.datastructures.execution.Transaction; import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; public class ExecutionPayloadSchemaCapella extends ContainerSchema15< @@ -123,28 +119,6 @@ public WithdrawalSchema getWithdrawalSchemaRequired() { return getWithdrawalSchema(); } - @Override - public SszListSchema> - getDepositReceiptsSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipts schema from capella"); - } - - @Override - public DepositReceiptSchema getDepositReceiptSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipt schema from capella"); - } - - @Override - public SszListSchema> - getExecutionLayerExitsSchemaRequired() { - throw new IllegalStateException("Attempted to get execution layer exits schema from capella"); - } - - @Override - public ExecutionLayerExitSchema getExecutionLayerExitSchemaRequired() { - throw new IllegalStateException("Attempted to get a execution layer exit schema from capella"); - } - public WithdrawalSchema getWithdrawalSchema() { return (WithdrawalSchema) getWithdrawalsSchema().getElementSchema(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/deneb/ExecutionPayloadSchemaDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/deneb/ExecutionPayloadSchemaDeneb.java index 6d918738a0d..0f909dc564e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/deneb/ExecutionPayloadSchemaDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/deneb/ExecutionPayloadSchemaDeneb.java @@ -54,10 +54,6 @@ import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.WithdrawalSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; public class ExecutionPayloadSchemaDeneb extends ContainerSchema17< @@ -131,28 +127,6 @@ public WithdrawalSchema getWithdrawalSchemaRequired() { return getWithdrawalSchema(); } - @Override - public SszListSchema> - getDepositReceiptsSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipts schema from deneb"); - } - - @Override - public DepositReceiptSchema getDepositReceiptSchemaRequired() { - throw new IllegalStateException("Attempted to get a deposit receipt schema from deneb"); - } - - @Override - public SszListSchema> - getExecutionLayerExitsSchemaRequired() { - throw new IllegalStateException("Attempted to get execution layer exits schema from deneb"); - } - - @Override - public ExecutionLayerExitSchema getExecutionLayerExitSchemaRequired() { - throw new IllegalStateException("Attempted to get a execution layer exit schema from deneb"); - } - public WithdrawalSchema getWithdrawalSchema() { return (WithdrawalSchema) getWithdrawalsSchema().getElementSchema(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadBuilderElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadBuilderEip7594.java similarity index 63% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadBuilderElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadBuilderEip7594.java index 81b8a0e9314..4771480d184 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadBuilderElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadBuilderEip7594.java @@ -11,60 +11,34 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import static com.google.common.base.Preconditions.checkNotNull; -import java.util.List; -import java.util.function.Supplier; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadBuilderDeneb; -public class ExecutionPayloadBuilderElectra extends ExecutionPayloadBuilderDeneb { - private ExecutionPayloadSchemaElectra schema; +public class ExecutionPayloadBuilderEip7594 extends ExecutionPayloadBuilderDeneb { + private ExecutionPayloadSchemaEip7594 schema; - protected List depositReceipts; - protected List exits; - - public ExecutionPayloadBuilderElectra schema(final ExecutionPayloadSchemaElectra schema) { + public ExecutionPayloadBuilderEip7594 schema(final ExecutionPayloadSchemaEip7594 schema) { this.schema = schema; return this; } - @Override - public ExecutionPayloadBuilder depositReceipts( - final Supplier> depositReceiptsSupplier) { - this.depositReceipts = depositReceiptsSupplier.get(); - return this; - } - - @Override - public ExecutionPayloadBuilder exits(final Supplier> exitsSupplier) { - this.exits = exitsSupplier.get(); - return this; - } - @Override protected void validateSchema() { checkNotNull(schema, "schema must be specified"); } - @Override - protected void validate() { - super.validate(); - checkNotNull(depositReceipts, "depositReceipts must be specified"); - checkNotNull(exits, "exits must be specified"); - } - @Override public ExecutionPayload build() { validate(); - return new ExecutionPayloadElectraImpl( + return new ExecutionPayloadEip7594Impl( schema, SszBytes32.of(parentHash), SszByteVector.fromBytes(feeRecipient.getWrappedBytes()), @@ -84,8 +58,6 @@ public ExecutionPayload build() { .collect(schema.getTransactionsSchema().collector()), schema.getWithdrawalsSchema().createFromElements(withdrawals), SszUInt64.of(blobGasUsed), - SszUInt64.of(excessBlobGas), - schema.getDepositReceiptsSchema().createFromElements(depositReceipts), - schema.getExecutionLayerExitsSchema().createFromElements(exits)); + SszUInt64.of(excessBlobGas)); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594.java similarity index 72% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594.java index a895d948cdc..3fe8d54a03a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594.java @@ -11,37 +11,32 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadDeneb; -public interface ExecutionPayloadElectra extends ExecutionPayload, ExecutionPayloadDeneb { +public interface ExecutionPayloadEip7594 extends ExecutionPayload, ExecutionPayloadDeneb { - static ExecutionPayloadElectra required(final ExecutionPayload payload) { + static ExecutionPayloadEip7594 required(final ExecutionPayload payload) { return payload - .toVersionElectra() + .toVersionEip7594() .orElseThrow( () -> new IllegalArgumentException( - "Expected Electra execution payload but got " + "Expected EIP7594 execution payload but got " + payload.getClass().getSimpleName())); } - SszList getDepositReceipts(); - - SszList getExits(); - @Override - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.of(this); } @Override default SpecMilestone getMilestone() { - return SpecMilestone.ELECTRA; + return SpecMilestone.EIP7594; } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594Impl.java similarity index 80% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594Impl.java index c8e7bbb0f1f..7865c21a38e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import java.util.List; import java.util.Optional; @@ -22,8 +22,8 @@ import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.Container19; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema19; +import tech.pegasys.teku.infrastructure.ssz.containers.Container17; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema17; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; @@ -32,9 +32,9 @@ import tech.pegasys.teku.spec.datastructures.execution.Transaction; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; -public class ExecutionPayloadElectraImpl - extends Container19< - ExecutionPayloadElectraImpl, +public class ExecutionPayloadEip7594Impl + extends Container17< + ExecutionPayloadEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -51,14 +51,12 @@ public class ExecutionPayloadElectraImpl SszList, SszList, SszUInt64, - SszUInt64, - SszList, - SszList> - implements ExecutionPayloadElectra { + SszUInt64> + implements ExecutionPayloadEip7594 { - public ExecutionPayloadElectraImpl( - ContainerSchema19< - ExecutionPayloadElectraImpl, + public ExecutionPayloadEip7594Impl( + ContainerSchema17< + ExecutionPayloadEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -75,16 +73,14 @@ public ExecutionPayloadElectraImpl( SszList, SszList, SszUInt64, - SszUInt64, - SszList, - SszList> + SszUInt64> schema, TreeNode backingNode) { super(schema, backingNode); } - public ExecutionPayloadElectraImpl( - ExecutionPayloadSchemaElectra schema, + public ExecutionPayloadEip7594Impl( + ExecutionPayloadSchemaEip7594 schema, SszBytes32 parentHash, SszByteVector feeRecipient, SszBytes32 stateRoot, @@ -101,9 +97,7 @@ public ExecutionPayloadElectraImpl( SszList transactions, SszList withdrawals, SszUInt64 blobGasUsed, - SszUInt64 excessBlobGas, - SszList depositReceipts, - SszList exits) { + SszUInt64 excessBlobGas) { super( schema, parentHash, @@ -122,9 +116,7 @@ public ExecutionPayloadElectraImpl( transactions, withdrawals, blobGasUsed, - excessBlobGas, - depositReceipts, - exits); + excessBlobGas); } @Override @@ -138,8 +130,8 @@ public Optional getOptionalWithdrawalsRoot() { } @Override - public ExecutionPayloadSchemaElectra getSchema() { - return (ExecutionPayloadSchemaElectra) super.getSchema(); + public ExecutionPayloadSchemaEip7594 getSchema() { + return (ExecutionPayloadSchemaEip7594) super.getSchema(); } @Override @@ -232,22 +224,8 @@ public UInt64 getExcessBlobGas() { return getField16().get(); } - @Override - public SszList getDepositReceipts() { - return getField17(); - } - - @Override - public SszList getExits() { - return getField18(); - } - @Override public List getUnblindedTreeNodes() { - return List.of( - getTransactions().getBackingNode(), - getWithdrawals().getBackingNode(), - getDepositReceipts().getBackingNode(), - getExits().getBackingNode()); + return List.of(getTransactions().getBackingNode(), getWithdrawals().getBackingNode()); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderBuilderElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderBuilderEip7594.java similarity index 62% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderBuilderElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderBuilderEip7594.java index 31e699becf6..e0ca992259d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderBuilderElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderBuilderEip7594.java @@ -11,61 +11,35 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import static com.google.common.base.Preconditions.checkNotNull; -import java.util.function.Supplier; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderBuilder; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadHeaderBuilderDeneb; -public class ExecutionPayloadHeaderBuilderElectra extends ExecutionPayloadHeaderBuilderDeneb { - private ExecutionPayloadHeaderSchemaElectra schema; +public class ExecutionPayloadHeaderBuilderEip7594 extends ExecutionPayloadHeaderBuilderDeneb { + private ExecutionPayloadHeaderSchemaEip7594 schema; - protected Bytes32 depositReceiptsRoot; - protected Bytes32 exitsRoot; - - public ExecutionPayloadHeaderBuilderElectra schema( - final ExecutionPayloadHeaderSchemaElectra schema) { + public ExecutionPayloadHeaderBuilderEip7594 schema( + final ExecutionPayloadHeaderSchemaEip7594 schema) { this.schema = schema; return this; } - @Override - public ExecutionPayloadHeaderBuilder depositReceiptsRoot( - final Supplier depositReceiptsRootSupplier) { - this.depositReceiptsRoot = depositReceiptsRootSupplier.get(); - return this; - } - - @Override - public ExecutionPayloadHeaderBuilder exitsRoot(final Supplier exitsRootSupplier) { - this.exitsRoot = exitsRootSupplier.get(); - return this; - } - @Override protected void validateSchema() { checkNotNull(schema, "schema must be specified"); } - @Override - protected void validate() { - super.validate(); - checkNotNull(depositReceiptsRoot, "depositReceiptsRoot must be specified"); - checkNotNull(exitsRoot, "exitsRoot must be specified"); - } - @Override public ExecutionPayloadHeader build() { validate(); - return new ExecutionPayloadHeaderElectraImpl( + return new ExecutionPayloadHeaderEip7594Impl( schema, SszBytes32.of(parentHash), SszByteVector.fromBytes(feeRecipient.getWrappedBytes()), @@ -83,8 +57,6 @@ public ExecutionPayloadHeader build() { SszBytes32.of(transactionsRoot), SszBytes32.of(withdrawalsRoot), SszUInt64.of(blobGasUsed), - SszUInt64.of(excessBlobGas), - SszBytes32.of(depositReceiptsRoot), - SszBytes32.of(exitsRoot)); + SszUInt64.of(excessBlobGas)); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594.java similarity index 77% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594.java index 75656c1e70f..fa288730288 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594.java @@ -11,20 +11,15 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import java.util.Optional; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadHeaderDeneb; -public interface ExecutionPayloadHeaderElectra extends ExecutionPayloadHeaderDeneb { - - Bytes32 getDepositReceiptsRoot(); - - Bytes32 getExitsRoot(); +public interface ExecutionPayloadHeaderEip7594 extends ExecutionPayloadHeaderDeneb { @Override - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.of(this); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594Impl.java similarity index 82% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594Impl.java index db3c2d2a2cb..c1739e9f3f1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; @@ -19,17 +19,17 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.Container19; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema19; +import tech.pegasys.teku.infrastructure.ssz.containers.Container17; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema17; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -public class ExecutionPayloadHeaderElectraImpl - extends Container19< - ExecutionPayloadHeaderElectraImpl, +public class ExecutionPayloadHeaderEip7594Impl + extends Container17< + ExecutionPayloadHeaderEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -46,14 +46,12 @@ public class ExecutionPayloadHeaderElectraImpl SszBytes32, SszBytes32, SszUInt64, - SszUInt64, - SszBytes32, - SszBytes32> - implements ExecutionPayloadHeaderElectra { + SszUInt64> + implements ExecutionPayloadHeaderEip7594 { - protected ExecutionPayloadHeaderElectraImpl( - ContainerSchema19< - ExecutionPayloadHeaderElectraImpl, + protected ExecutionPayloadHeaderEip7594Impl( + ContainerSchema17< + ExecutionPayloadHeaderEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -70,16 +68,14 @@ protected ExecutionPayloadHeaderElectraImpl( SszBytes32, SszBytes32, SszUInt64, - SszUInt64, - SszBytes32, - SszBytes32> + SszUInt64> schema, TreeNode backingTree) { super(schema, backingTree); } - public ExecutionPayloadHeaderElectraImpl( - ExecutionPayloadHeaderSchemaElectra schema, + public ExecutionPayloadHeaderEip7594Impl( + ExecutionPayloadHeaderSchemaEip7594 schema, SszBytes32 parentHash, SszByteVector feeRecipient, SszBytes32 stateRoot, @@ -96,9 +92,7 @@ public ExecutionPayloadHeaderElectraImpl( SszBytes32 transactionsRoot, SszBytes32 withdrawalsRoot, SszUInt64 blobGasUsed, - SszUInt64 excessBlobGas, - SszBytes32 depositReceiptsRoot, - SszBytes32 exitsRoot) { + SszUInt64 excessBlobGas) { super( schema, parentHash, @@ -117,9 +111,7 @@ public ExecutionPayloadHeaderElectraImpl( transactionsRoot, withdrawalsRoot, blobGasUsed, - excessBlobGas, - depositReceiptsRoot, - exitsRoot); + excessBlobGas); } @Override @@ -128,8 +120,8 @@ public boolean isDefaultPayload() { } @Override - public ExecutionPayloadHeaderSchemaElectra getSchema() { - return (ExecutionPayloadHeaderSchemaElectra) super.getSchema(); + public ExecutionPayloadHeaderSchemaEip7594 getSchema() { + return (ExecutionPayloadHeaderSchemaEip7594) super.getSchema(); } @Override @@ -226,14 +218,4 @@ public UInt64 getBlobGasUsed() { public UInt64 getExcessBlobGas() { return getField16().get(); } - - @Override - public Bytes32 getDepositReceiptsRoot() { - return getField17().get(); - } - - @Override - public Bytes32 getExitsRoot() { - return getField18().get(); - } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderSchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderSchemaEip7594.java similarity index 78% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderSchemaElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderSchemaEip7594.java index 0ace0f4fac6..52cb0aa3140 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadHeaderSchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadHeaderSchemaEip7594.java @@ -11,15 +11,13 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BASE_FEE_PER_GAS; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOB_GAS_USED; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_HASH; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_NUMBER; -import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.DEPOSIT_RECEIPTS_ROOT; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXCESS_BLOB_GAS; -import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXITS_ROOT; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXTRA_DATA; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.FEE_RECIPIENT; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.GAS_LIMIT; @@ -38,7 +36,7 @@ import tech.pegasys.teku.infrastructure.bytes.Bytes20; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema19; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema17; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; @@ -46,15 +44,15 @@ import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteListSchema; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteVectorSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderBuilder; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; -public class ExecutionPayloadHeaderSchemaElectra - extends ContainerSchema19< - ExecutionPayloadHeaderElectraImpl, +public class ExecutionPayloadHeaderSchemaEip7594 + extends ContainerSchema17< + ExecutionPayloadHeaderEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -71,17 +69,15 @@ public class ExecutionPayloadHeaderSchemaElectra SszBytes32, SszBytes32, SszUInt64, - SszUInt64, - SszBytes32, - SszBytes32> - implements ExecutionPayloadHeaderSchema { + SszUInt64> + implements ExecutionPayloadHeaderSchema { - private final ExecutionPayloadHeaderElectraImpl defaultExecutionPayloadHeader; - private final ExecutionPayloadHeaderElectraImpl executionPayloadHeaderOfDefaultPayload; + private final ExecutionPayloadHeaderEip7594Impl defaultExecutionPayloadHeader; + private final ExecutionPayloadHeaderEip7594Impl executionPayloadHeaderOfDefaultPayload; - public ExecutionPayloadHeaderSchemaElectra(final SpecConfigElectra specConfig) { + public ExecutionPayloadHeaderSchemaEip7594(final SpecConfigEip7594 specConfig) { super( - "ExecutionPayloadHeaderElectra", + "ExecutionPayloadHeaderEip7594", namedSchema(PARENT_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), namedSchema(FEE_RECIPIENT, SszByteVectorSchema.create(Bytes20.SIZE)), namedSchema(STATE_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), @@ -98,12 +94,10 @@ public ExecutionPayloadHeaderSchemaElectra(final SpecConfigElectra specConfig) { namedSchema(TRANSACTIONS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), namedSchema(WITHDRAWALS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), namedSchema(BLOB_GAS_USED, SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema(EXCESS_BLOB_GAS, SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema(DEPOSIT_RECEIPTS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), - namedSchema(EXITS_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA)); + namedSchema(EXCESS_BLOB_GAS, SszPrimitiveSchemas.UINT64_SCHEMA)); - final ExecutionPayloadElectraImpl defaultExecutionPayload = - new ExecutionPayloadSchemaElectra(specConfig).getDefault(); + final ExecutionPayloadEip7594Impl defaultExecutionPayload = + new ExecutionPayloadSchemaEip7594(specConfig).getDefault(); this.executionPayloadHeaderOfDefaultPayload = createFromExecutionPayload(defaultExecutionPayload); @@ -119,40 +113,38 @@ public SszByteListSchema getExtraDataSchema() { public LongList getBlindedNodeGeneralizedIndices() { return LongList.of( getChildGeneralizedIndex(getFieldIndex(TRANSACTIONS_ROOT)), - getChildGeneralizedIndex(getFieldIndex(WITHDRAWALS_ROOT)), - getChildGeneralizedIndex(getFieldIndex(DEPOSIT_RECEIPTS_ROOT)), - getChildGeneralizedIndex(getFieldIndex(EXITS_ROOT))); + getChildGeneralizedIndex(getFieldIndex(WITHDRAWALS_ROOT))); } @Override public ExecutionPayloadHeader createExecutionPayloadHeader( final Consumer builderConsumer) { - final ExecutionPayloadHeaderBuilderElectra builder = - new ExecutionPayloadHeaderBuilderElectra().schema(this); + final ExecutionPayloadHeaderBuilderEip7594 builder = + new ExecutionPayloadHeaderBuilderEip7594().schema(this); builderConsumer.accept(builder); return builder.build(); } @Override - public ExecutionPayloadHeaderElectraImpl getDefault() { + public ExecutionPayloadHeaderEip7594Impl getDefault() { return defaultExecutionPayloadHeader; } @Override - public ExecutionPayloadHeaderElectra getHeaderOfDefaultPayload() { + public ExecutionPayloadHeaderEip7594 getHeaderOfDefaultPayload() { return executionPayloadHeaderOfDefaultPayload; } @Override - public ExecutionPayloadHeaderElectraImpl createFromBackingNode(final TreeNode node) { - return new ExecutionPayloadHeaderElectraImpl(this, node); + public ExecutionPayloadHeaderEip7594Impl createFromBackingNode(final TreeNode node) { + return new ExecutionPayloadHeaderEip7594Impl(this, node); } @Override - public ExecutionPayloadHeaderElectraImpl createFromExecutionPayload( + public ExecutionPayloadHeaderEip7594Impl createFromExecutionPayload( final ExecutionPayload payload) { - final ExecutionPayloadElectra executionPayload = ExecutionPayloadElectra.required(payload); - return new ExecutionPayloadHeaderElectraImpl( + final ExecutionPayloadEip7594 executionPayload = ExecutionPayloadEip7594.required(payload); + return new ExecutionPayloadHeaderEip7594Impl( this, SszBytes32.of(executionPayload.getParentHash()), SszByteVector.fromBytes(executionPayload.getFeeRecipient().getWrappedBytes()), @@ -170,8 +162,6 @@ public ExecutionPayloadHeaderElectraImpl createFromExecutionPayload( SszBytes32.of(executionPayload.getTransactions().hashTreeRoot()), SszBytes32.of(executionPayload.getWithdrawals().hashTreeRoot()), SszUInt64.of(executionPayload.getBlobGasUsed()), - SszUInt64.of(executionPayload.getExcessBlobGas()), - SszBytes32.of(executionPayload.getDepositReceipts().hashTreeRoot()), - SszBytes32.of(executionPayload.getExits().hashTreeRoot())); + SszUInt64.of(executionPayload.getExcessBlobGas())); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadSchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadSchemaEip7594.java similarity index 71% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadSchemaElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadSchemaEip7594.java index 0261a4d013e..12e9c4b1396 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionPayloadSchemaElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/eip7594/ExecutionPayloadSchemaEip7594.java @@ -11,15 +11,13 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; +package tech.pegasys.teku.spec.datastructures.execution.versions.eip7594; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BASE_FEE_PER_GAS; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOB_GAS_USED; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_HASH; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.BLOCK_NUMBER; -import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.DEPOSIT_RECEIPTS; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXCESS_BLOB_GAS; -import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXITS; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.EXTRA_DATA; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.FEE_RECIPIENT; import static tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadFields.GAS_LIMIT; @@ -39,7 +37,7 @@ import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteList; import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema19; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema17; import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt256; import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; @@ -48,7 +46,7 @@ import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteListSchema; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteVectorSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadBuilder; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; @@ -57,9 +55,9 @@ import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.WithdrawalSchema; -public class ExecutionPayloadSchemaElectra - extends ContainerSchema19< - ExecutionPayloadElectraImpl, +public class ExecutionPayloadSchemaEip7594 + extends ContainerSchema17< + ExecutionPayloadEip7594Impl, SszBytes32, SszByteVector, SszBytes32, @@ -76,16 +74,14 @@ public class ExecutionPayloadSchemaElectra SszList, SszList, SszUInt64, - SszUInt64, - SszList, - SszList> - implements ExecutionPayloadSchema { + SszUInt64> + implements ExecutionPayloadSchema { - private final ExecutionPayloadElectraImpl defaultExecutionPayload; + private final ExecutionPayloadEip7594Impl defaultExecutionPayload; - public ExecutionPayloadSchemaElectra(final SpecConfigElectra specConfig) { + public ExecutionPayloadSchemaEip7594(final SpecConfigEip7594 specConfig) { super( - "ExecutionPayloadElectra", + "ExecutionPayloadEip7594", namedSchema(PARENT_HASH, SszPrimitiveSchemas.BYTES32_SCHEMA), namedSchema(FEE_RECIPIENT, SszByteVectorSchema.create(Bytes20.SIZE)), namedSchema(STATE_ROOT, SszPrimitiveSchemas.BYTES32_SCHEMA), @@ -107,20 +103,12 @@ public ExecutionPayloadSchemaElectra(final SpecConfigElectra specConfig) { WITHDRAWALS, SszListSchema.create(Withdrawal.SSZ_SCHEMA, specConfig.getMaxWithdrawalsPerPayload())), namedSchema(BLOB_GAS_USED, SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema(EXCESS_BLOB_GAS, SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema( - DEPOSIT_RECEIPTS, - SszListSchema.create( - DepositReceipt.SSZ_SCHEMA, specConfig.getMaxDepositReceiptsPerPayload())), - namedSchema( - EXITS, - SszListSchema.create( - ExecutionLayerExit.SSZ_SCHEMA, specConfig.getMaxExecutionLayerExits()))); + namedSchema(EXCESS_BLOB_GAS, SszPrimitiveSchemas.UINT64_SCHEMA)); this.defaultExecutionPayload = createFromBackingNode(getDefaultTree()); } @Override - public ExecutionPayloadElectraImpl getDefault() { + public ExecutionPayloadEip7594Impl getDefault() { return defaultExecutionPayload; } @@ -139,61 +127,29 @@ public WithdrawalSchema getWithdrawalSchemaRequired() { return getWithdrawalSchema(); } - @Override - public SszListSchema> - getDepositReceiptsSchemaRequired() { - return getDepositReceiptsSchema(); - } - - @Override - public DepositReceiptSchema getDepositReceiptSchemaRequired() { - return getDepositReceiptSchema(); - } - - @Override - public SszListSchema> - getExecutionLayerExitsSchemaRequired() { - return getExecutionLayerExitsSchema(); - } - - @Override - public ExecutionLayerExitSchema getExecutionLayerExitSchemaRequired() { - return getExecutionLayerExitSchema(); - } - public WithdrawalSchema getWithdrawalSchema() { return (WithdrawalSchema) getWithdrawalsSchema().getElementSchema(); } - public DepositReceiptSchema getDepositReceiptSchema() { - return (DepositReceiptSchema) getDepositReceiptsSchema().getElementSchema(); - } - - public ExecutionLayerExitSchema getExecutionLayerExitSchema() { - return (ExecutionLayerExitSchema) getExecutionLayerExitsSchema().getElementSchema(); - } - @Override public LongList getBlindedNodeGeneralizedIndices() { return LongList.of( getChildGeneralizedIndex(getFieldIndex(TRANSACTIONS)), - getChildGeneralizedIndex(getFieldIndex(WITHDRAWALS)), - getChildGeneralizedIndex(getFieldIndex(DEPOSIT_RECEIPTS)), - getChildGeneralizedIndex(getFieldIndex(EXITS))); + getChildGeneralizedIndex(getFieldIndex(WITHDRAWALS))); } @Override public ExecutionPayload createExecutionPayload( final Consumer builderConsumer) { - final ExecutionPayloadBuilderElectra builder = - new ExecutionPayloadBuilderElectra().schema(this); + final ExecutionPayloadBuilderEip7594 builder = + new ExecutionPayloadBuilderEip7594().schema(this); builderConsumer.accept(builder); return builder.build(); } @Override - public ExecutionPayloadElectraImpl createFromBackingNode(final TreeNode node) { - return new ExecutionPayloadElectraImpl(this, node); + public ExecutionPayloadEip7594Impl createFromBackingNode(final TreeNode node) { + return new ExecutionPayloadEip7594Impl(this, node); } public SszByteListSchema getExtraDataSchema() { @@ -209,14 +165,4 @@ public SszByteListSchema getExtraDataSchema() { public SszListSchema getWithdrawalsSchema() { return (SszListSchema) getChildSchema(getFieldIndex(WITHDRAWALS)); } - - @SuppressWarnings("unchecked") - public SszListSchema getDepositReceiptsSchema() { - return (SszListSchema) getChildSchema(getFieldIndex(DEPOSIT_RECEIPTS)); - } - - @SuppressWarnings("unchecked") - public SszListSchema getExecutionLayerExitsSchema() { - return (SszListSchema) getChildSchema(getFieldIndex(EXITS)); - } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceipt.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceipt.java deleted file mode 100644 index f4f8c8dcb7d..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceipt.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.containers.Container5; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; - -public class DepositReceipt - extends Container5< - DepositReceipt, SszPublicKey, SszBytes32, SszUInt64, SszSignature, SszUInt64> { - - DepositReceipt( - final DepositReceiptSchema schema, - final BLSPublicKey pubkey, - final Bytes32 withdrawalCredentials, - final UInt64 amount, - final BLSSignature signature, - final UInt64 index) { - super( - schema, - new SszPublicKey(pubkey), - SszBytes32.of(withdrawalCredentials), - SszUInt64.of(amount), - new SszSignature(signature), - SszUInt64.of(index)); - } - - public static final DepositReceiptSchema SSZ_SCHEMA = new DepositReceiptSchema(); - - DepositReceipt(final DepositReceiptSchema type, final TreeNode backingNode) { - super(type, backingNode); - } - - public BLSPublicKey getPubkey() { - return getField0().getBLSPublicKey(); - } - - public Bytes32 getWithdrawalCredentials() { - return getField1().get(); - } - - public UInt64 getAmount() { - return getField2().get(); - } - - public BLSSignature getSignature() { - return getField3().getSignature(); - } - - public UInt64 getIndex() { - return getField4().get(); - } - - @Override - public DepositReceiptSchema getSchema() { - return (DepositReceiptSchema) super.getSchema(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptSchema.java deleted file mode 100644 index c9dd2633ce7..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptSchema.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema5; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszBytes32; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; -import tech.pegasys.teku.spec.datastructures.type.SszSignature; -import tech.pegasys.teku.spec.datastructures.type.SszSignatureSchema; - -public class DepositReceiptSchema - extends ContainerSchema5< - DepositReceipt, SszPublicKey, SszBytes32, SszUInt64, SszSignature, SszUInt64> { - - public DepositReceiptSchema() { - super( - "DepositReceipt", - namedSchema("pubkey", SszPublicKeySchema.INSTANCE), - namedSchema("withdrawal_credentials", SszPrimitiveSchemas.BYTES32_SCHEMA), - namedSchema("amount", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("signature", SszSignatureSchema.INSTANCE), - namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA)); - } - - public DepositReceipt create( - final BLSPublicKey pubkey, - final Bytes32 withdrawalCredentials, - final UInt64 amount, - final BLSSignature signature, - final UInt64 index) { - return new DepositReceipt(this, pubkey, withdrawalCredentials, amount, signature, index); - } - - @Override - public DepositReceipt createFromBackingNode(final TreeNode node) { - return new DepositReceipt(this, node); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExit.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExit.java deleted file mode 100644 index 511bb8eb306..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExit.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.Container2; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; - -public class ExecutionLayerExit - extends Container2 { - - public static final ExecutionLayerExitSchema SSZ_SCHEMA = new ExecutionLayerExitSchema(); - - protected ExecutionLayerExit( - final ExecutionLayerExitSchema schema, - final Bytes20 sourceAddress, - final BLSPublicKey validatorPublicKey) { - super( - schema, - SszByteVector.fromBytes(sourceAddress.getWrappedBytes()), - new SszPublicKey(validatorPublicKey)); - } - - ExecutionLayerExit(final ExecutionLayerExitSchema type, final TreeNode backingNode) { - super(type, backingNode); - } - - public Bytes20 getSourceAddress() { - return new Bytes20(getField0().getBytes()); - } - - public BLSPublicKey getValidatorPublicKey() { - return getField1().getBLSPublicKey(); - } - - @Override - public ExecutionLayerExitSchema getSchema() { - return (ExecutionLayerExitSchema) super.getSchema(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitSchema.java deleted file mode 100644 index f30bafbfe45..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitSchema.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.infrastructure.ssz.collections.SszByteVector; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszByteVectorSchema; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKey; -import tech.pegasys.teku.spec.datastructures.type.SszPublicKeySchema; - -public class ExecutionLayerExitSchema - extends ContainerSchema2 { - - public ExecutionLayerExitSchema() { - super( - "ExecutionLayerExit", - namedSchema("source_address", SszByteVectorSchema.create(Bytes20.SIZE)), - namedSchema("validator_pubkey", SszPublicKeySchema.INSTANCE)); - } - - public ExecutionLayerExit create( - final Bytes20 sourceAddress, final BLSPublicKey validatorPublicKey) { - return new ExecutionLayerExit(this, sourceAddress, validatorPublicKey); - } - - @Override - public ExecutionLayerExit createFromBackingNode(final TreeNode node) { - return new ExecutionLayerExit(this, node); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java index 3b8dce49f51..b7f91f8cae1 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRootRequestMessageSchema.java @@ -15,13 +15,13 @@ import tech.pegasys.teku.infrastructure.ssz.schema.impl.AbstractSszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; public class DataColumnSidecarsByRootRequestMessageSchema extends AbstractSszListSchema { - public DataColumnSidecarsByRootRequestMessageSchema(final SpecConfigElectra specConfigElectra) { - super(DataColumnIdentifier.SSZ_SCHEMA, specConfigElectra.getMaxRequestDataColumnSidecars()); + public DataColumnSidecarsByRootRequestMessageSchema(final SpecConfigEip7594 specConfigEip7594) { + super(DataColumnIdentifier.SSZ_SCHEMA, specConfigEip7594.getMaxRequestDataColumnSidecars()); } @Override diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java index 25d9e6545d6..6c91fd16cea 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/BeaconState.java @@ -39,7 +39,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateBellatrix; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateCapella; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.BeaconStatePhase0; public interface BeaconState extends SszContainer, ValidatorStats { @@ -189,7 +189,7 @@ default Optional toVersionDeneb() { return Optional.empty(); } - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java index 890cbee18ec..a526bfd1a27 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/MutableBeaconState.java @@ -40,7 +40,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.MutableBeaconStateBellatrix; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.MutableBeaconStateCapella; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.MutableBeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.MutableBeaconStateEip7594; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.MutableBeaconStatePhase0; public interface MutableBeaconState extends BeaconState, SszMutableRefContainer { @@ -220,7 +220,7 @@ default Optional toMutableVersionDeneb() { return Optional.empty(); } - default Optional toMutableVersionElectra() { + default Optional toMutableVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java index aeed13192f9..868fe63781e 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/common/BeaconStateFields.java @@ -68,18 +68,7 @@ public enum BeaconStateFields implements SszFieldName { // Capella fields NEXT_WITHDRAWAL_INDEX, NEXT_WITHDRAWAL_VALIDATOR_INDEX, - HISTORICAL_SUMMARIES, - // Electra fields - DEPOSIT_RECEIPTS_START_INDEX, - - DEPOSIT_BALANCE_TO_CONSUME, - EXIT_BALANCE_TO_CONSUME, - EARLIEST_EXIT_EPOCH, - CONSOLIDATION_BALANCE_TO_CONSUME, - EARLIEST_CONSOLIDATION_EPOCH, - PENDING_BALANCE_DEPOSITS, - PENDING_PARTIAL_WITHDRAWALS, - PENDING_CONSOLIDATIONS; + HISTORICAL_SUMMARIES; private final String sszFieldName; diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594.java new file mode 100644 index 00000000000..3a9389326ff --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594.java @@ -0,0 +1,51 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594; + +import com.google.common.base.MoreObjects; +import java.util.Optional; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; + +public interface BeaconStateEip7594 extends BeaconStateDeneb { + static BeaconStateEip7594 required(final BeaconState state) { + return state + .toVersionEip7594() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected an EIP7594 state but got: " + state.getClass().getSimpleName())); + } + + static void describeCustomEip7594Fields( + MoreObjects.ToStringHelper stringBuilder, BeaconStateDeneb state) { + BeaconStateDeneb.describeCustomDenebFields(stringBuilder, state); + } + + @Override + MutableBeaconStateEip7594 createWritableCopy(); + + default + BeaconStateEip7594 updatedEip7594( + final Mutator mutator) throws E1, E2, E3 { + MutableBeaconStateEip7594 writableCopy = createWritableCopy(); + mutator.mutate(writableCopy); + return writableCopy.commitChanges(); + } + + @Override + default Optional toVersionEip7594() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594Impl.java similarity index 77% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594Impl.java index 1d1263e31e1..98369e39027 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594; import com.google.common.base.MoreObjects; import tech.pegasys.teku.infrastructure.ssz.SszContainer; @@ -27,15 +27,15 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.TransitionCaches; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.ValidatorStatsAltair; -public class BeaconStateElectraImpl extends AbstractBeaconState - implements BeaconStateElectra, BeaconStateCache, ValidatorStatsAltair { +public class BeaconStateEip7594Impl extends AbstractBeaconState + implements BeaconStateEip7594, BeaconStateCache, ValidatorStatsAltair { - BeaconStateElectraImpl( - final BeaconStateSchema schema) { + BeaconStateEip7594Impl( + final BeaconStateSchema schema) { super(schema); } - BeaconStateElectraImpl( + BeaconStateEip7594Impl( final SszCompositeSchema type, final TreeNode backingNode, final IntCache cache, @@ -44,23 +44,23 @@ public class BeaconStateElectraImpl extends AbstractBeaconState type, final TreeNode backingNode) { super(type, backingNode); } @Override - public BeaconStateSchemaElectra getBeaconStateSchema() { - return (BeaconStateSchemaElectra) getSchema(); + public BeaconStateSchemaEip7594 getBeaconStateSchema() { + return (BeaconStateSchemaEip7594) getSchema(); } @Override - public MutableBeaconStateElectra createWritableCopy() { - return new MutableBeaconStateElectraImpl(this); + public MutableBeaconStateEip7594 createWritableCopy() { + return new MutableBeaconStateEip7594Impl(this); } @Override protected void describeCustomFields(final MoreObjects.ToStringHelper stringBuilder) { - BeaconStateElectra.describeCustomElectraFields(stringBuilder, this); + BeaconStateEip7594.describeCustomEip7594Fields(stringBuilder, this); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateSchemaEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateSchemaEip7594.java new file mode 100644 index 00000000000..771adc14cf7 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/BeaconStateSchemaEip7594.java @@ -0,0 +1,150 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594; + +import static com.google.common.base.Preconditions.checkArgument; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateSchemaBellatrix.LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.HISTORICAL_SUMMARIES_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_INDEX; +import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_VALIDATOR_INDEX; + +import com.google.common.annotations.VisibleForTesting; +import java.util.List; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszPrimitiveListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; +import tech.pegasys.teku.infrastructure.ssz.sos.SszField; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.AbstractBeaconStateSchema; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateSchemaAltair; +import tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary; + +public class BeaconStateSchemaEip7594 + extends AbstractBeaconStateSchema { + + @VisibleForTesting + BeaconStateSchemaEip7594(final SpecConfig specConfig) { + super("BeaconStateEip7594", getUniqueFields(specConfig), specConfig); + } + + private static List getUniqueFields(final SpecConfig specConfig) { + final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = + new HistoricalSummary.HistoricalSummarySchema(); + final SpecConfigEip7594 specConfigEip7594 = SpecConfigEip7594.required(specConfig); + final SszField latestExecutionPayloadHeaderField = + new SszField( + LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, + BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, + () -> new ExecutionPayloadHeaderSchemaEip7594(specConfigEip7594)); + final SszField nextWithdrawalIndexField = + new SszField( + NEXT_WITHDRAWAL_INDEX, + BeaconStateFields.NEXT_WITHDRAWAL_INDEX, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + final SszField nextWithdrawalValidatorIndexField = + new SszField( + NEXT_WITHDRAWAL_VALIDATOR_INDEX, + BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, + () -> SszPrimitiveSchemas.UINT64_SCHEMA); + + final SszField historicalSummariesField = + new SszField( + HISTORICAL_SUMMARIES_INDEX, + BeaconStateFields.HISTORICAL_SUMMARIES, + () -> + SszListSchema.create( + historicalSummarySchema, specConfig.getHistoricalRootsLimit())); + return Stream.concat( + BeaconStateSchemaAltair.getUniqueFields(specConfig).stream(), + Stream.of( + latestExecutionPayloadHeaderField, + nextWithdrawalIndexField, + nextWithdrawalValidatorIndexField, + historicalSummariesField)) + .toList(); + } + + @SuppressWarnings("unchecked") + public SszPrimitiveListSchema getPreviousEpochParticipationSchema() { + return (SszPrimitiveListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.PREVIOUS_EPOCH_PARTICIPATION)); + } + + @SuppressWarnings("unchecked") + public SszPrimitiveListSchema getCurrentEpochParticipationSchema() { + return (SszPrimitiveListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_EPOCH_PARTICIPATION)); + } + + public SszUInt64ListSchema getInactivityScoresSchema() { + return (SszUInt64ListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.INACTIVITY_SCORES)); + } + + public SyncCommittee.SyncCommitteeSchema getCurrentSyncCommitteeSchema() { + return (SyncCommittee.SyncCommitteeSchema) + getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_SYNC_COMMITTEE)); + } + + public ExecutionPayloadHeaderSchemaEip7594 getLastExecutionPayloadHeaderSchema() { + return (ExecutionPayloadHeaderSchemaEip7594) + getChildSchema(getFieldIndex(BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER)); + } + + @Override + public MutableBeaconStateEip7594 createBuilder() { + return new MutableBeaconStateEip7594Impl(createEmptyBeaconStateImpl(), true); + } + + public static BeaconStateSchemaEip7594 create(final SpecConfig specConfig) { + return new BeaconStateSchemaEip7594(specConfig); + } + + public static BeaconStateSchemaEip7594 required(final BeaconStateSchema schema) { + checkArgument( + schema instanceof BeaconStateSchemaEip7594, + "Expected a BeaconStateSchemaEip7594 but was %s", + schema.getClass()); + return (BeaconStateSchemaEip7594) schema; + } + + @SuppressWarnings("unchecked") + public SszListSchema getHistoricalSummariesSchema() { + return (SszListSchema) + getChildSchema(getFieldIndex(BeaconStateFields.HISTORICAL_SUMMARIES)); + } + + @Override + public BeaconStateEip7594 createEmpty() { + return createEmptyBeaconStateImpl(); + } + + private BeaconStateEip7594Impl createEmptyBeaconStateImpl() { + return new BeaconStateEip7594Impl(this); + } + + @Override + public BeaconStateEip7594Impl createFromBackingNode(TreeNode node) { + return new BeaconStateEip7594Impl(this, node); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594.java new file mode 100644 index 00000000000..7891fd2c829 --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594.java @@ -0,0 +1,37 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594; + +import java.util.Optional; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.MutableBeaconStateDeneb; + +public interface MutableBeaconStateEip7594 extends MutableBeaconStateDeneb, BeaconStateEip7594 { + static MutableBeaconStateEip7594 required(final MutableBeaconState state) { + return state + .toMutableVersionEip7594() + .orElseThrow( + () -> + new IllegalArgumentException( + "Expected an EIP7594 state but got: " + state.getClass().getSimpleName())); + } + + @Override + BeaconStateEip7594 commitChanges(); + + @Override + default Optional toMutableVersionEip7594() { + return Optional.of(this); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectraImpl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594Impl.java similarity index 71% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectraImpl.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594Impl.java index 79ebe2f6e43..73a2e702c37 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectraImpl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/eip7594/MutableBeaconStateEip7594Impl.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; +package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594; import com.google.common.base.MoreObjects; import tech.pegasys.teku.infrastructure.ssz.SszData; @@ -23,41 +23,41 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.TransitionCaches; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.ValidatorStatsAltair; -public class MutableBeaconStateElectraImpl - extends AbstractMutableBeaconState - implements MutableBeaconStateElectra, BeaconStateCache, ValidatorStatsAltair { +public class MutableBeaconStateEip7594Impl + extends AbstractMutableBeaconState + implements MutableBeaconStateEip7594, BeaconStateCache, ValidatorStatsAltair { - MutableBeaconStateElectraImpl(final BeaconStateElectraImpl backingImmutableView) { + MutableBeaconStateEip7594Impl(final BeaconStateEip7594Impl backingImmutableView) { super(backingImmutableView); } - MutableBeaconStateElectraImpl( - final BeaconStateElectraImpl backingImmutableView, final boolean builder) { + MutableBeaconStateEip7594Impl( + final BeaconStateEip7594Impl backingImmutableView, final boolean builder) { super(backingImmutableView, builder); } @Override - protected BeaconStateElectraImpl createImmutableBeaconState( + protected BeaconStateEip7594Impl createImmutableBeaconState( final TreeNode backingNode, final IntCache viewCache, final TransitionCaches transitionCaches, final SlotCaches slotCaches) { - return new BeaconStateElectraImpl( + return new BeaconStateEip7594Impl( getSchema(), backingNode, viewCache, transitionCaches, slotCaches); } @Override protected void addCustomFields(final MoreObjects.ToStringHelper stringBuilder) { - BeaconStateElectra.describeCustomElectraFields(stringBuilder, this); + BeaconStateEip7594.describeCustomEip7594Fields(stringBuilder, this); } @Override - public BeaconStateElectra commitChanges() { - return (BeaconStateElectra) super.commitChanges(); + public BeaconStateEip7594 commitChanges() { + return (BeaconStateEip7594) super.commitChanges(); } @Override - public MutableBeaconStateElectra createWritableCopy() { - return (MutableBeaconStateElectra) super.createWritableCopy(); + public MutableBeaconStateEip7594 createWritableCopy() { + return (MutableBeaconStateEip7594) super.createWritableCopy(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectra.java deleted file mode 100644 index 77f19625d08..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateElectra.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; - -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.DEPOSIT_RECEIPTS_START_INDEX; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.EARLIEST_EXIT_EPOCH; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.EXIT_BALANCE_TO_CONSUME; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.PENDING_BALANCE_DEPOSITS; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.PENDING_CONSOLIDATIONS; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS; - -import com.google.common.base.MoreObjects; -import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.SszList; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; - -public interface BeaconStateElectra extends BeaconStateDeneb { - static BeaconStateElectra required(final BeaconState state) { - return state - .toVersionElectra() - .orElseThrow( - () -> - new IllegalArgumentException( - "Expected an Electra state but got: " + state.getClass().getSimpleName())); - } - - static void describeCustomElectraFields( - MoreObjects.ToStringHelper stringBuilder, BeaconStateDeneb state) { - BeaconStateDeneb.describeCustomDenebFields(stringBuilder, state); - stringBuilder.add("deposit_receipts_start_index", state.getNextWithdrawalIndex()); - } - - @Override - MutableBeaconStateElectra createWritableCopy(); - - default - BeaconStateElectra updatedElectra( - final Mutator mutator) throws E1, E2, E3 { - MutableBeaconStateElectra writableCopy = createWritableCopy(); - mutator.mutate(writableCopy); - return writableCopy.commitChanges(); - } - - @Override - default Optional toVersionElectra() { - return Optional.of(this); - } - - default UInt64 getDepositReceiptsStartIndex() { - final int index = getSchema().getFieldIndex(DEPOSIT_RECEIPTS_START_INDEX); - return ((SszUInt64) get(index)).get(); - } - - default UInt64 getDepositBalanceToConsume() { - final int index = getSchema().getFieldIndex(DEPOSIT_BALANCE_TO_CONSUME); - return ((SszUInt64) get(index)).get(); - } - - default UInt64 getExitBalanceToConsume() { - final int index = getSchema().getFieldIndex(EXIT_BALANCE_TO_CONSUME); - return ((SszUInt64) get(index)).get(); - } - - default UInt64 getEarliestExitEpoch() { - final int index = getSchema().getFieldIndex(EARLIEST_EXIT_EPOCH); - return ((SszUInt64) get(index)).get(); - } - - default UInt64 getConsolidationBalanceToConsume() { - final int index = getSchema().getFieldIndex(CONSOLIDATION_BALANCE_TO_CONSUME); - return ((SszUInt64) get(index)).get(); - } - - default UInt64 getEarliestConsolidationEpoch() { - final int index = getSchema().getFieldIndex(EARLIEST_CONSOLIDATION_EPOCH); - return ((SszUInt64) get(index)).get(); - } - - default SszList getPendingBalanceDeposits() { - final int index = getSchema().getFieldIndex(PENDING_BALANCE_DEPOSITS); - return getAny(index); - } - - default SszList getPendingPartialWithdrawals() { - final int index = getSchema().getFieldIndex(PENDING_PARTIAL_WITHDRAWALS); - return getAny(index); - } - - default SszList getPendingConsolidations() { - final int index = getSchema().getFieldIndex(PENDING_CONSOLIDATIONS); - return getAny(index); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java deleted file mode 100644 index d09f52f863a..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/BeaconStateSchemaElectra.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; - -import static com.google.common.base.Preconditions.checkArgument; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateSchemaBellatrix.LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.HISTORICAL_SUMMARIES_INDEX; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_INDEX; -import static tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateSchemaCapella.NEXT_WITHDRAWAL_VALIDATOR_INDEX; - -import com.google.common.annotations.VisibleForTesting; -import java.util.List; -import java.util.stream.Stream; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; -import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszPrimitiveListSchema; -import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; -import tech.pegasys.teku.infrastructure.ssz.sos.SszField; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderSchemaElectra; -import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.AbstractBeaconStateSchema; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.altair.BeaconStateSchemaAltair; -import tech.pegasys.teku.spec.datastructures.state.versions.capella.HistoricalSummary; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; - -public class BeaconStateSchemaElectra - extends AbstractBeaconStateSchema { - public static final int DEPOSIT_RECEIPTS_START_INDEX = 28; - public static final int DEPOSIT_BALANCE_TO_CONSUME_INDEX = 29; - public static final int EXIT_BALANCE_TO_CONSUME_INDEX = 30; - public static final int EARLIEST_EXIT_EPOCH_INDEX = 31; - public static final int CONSOLIDATION_BALANCE_TO_CONSUME_INDEX = 32; - public static final int EARLIEST_CONSOLIDATION_EPOCH_INDEX = 33; - public static final int PENDING_BALANCE_DEPOSITS_INDEX = 34; - public static final int PENDING_PARTIAL_WITHDRAWALS_INDEX = 35; - public static final int PENDING_CONSOLIDATIONS_INDEX = 36; - - @VisibleForTesting - BeaconStateSchemaElectra(final SpecConfig specConfig) { - super("BeaconStateElectra", getUniqueFields(specConfig), specConfig); - } - - private static List getUniqueFields(final SpecConfig specConfig) { - final HistoricalSummary.HistoricalSummarySchema historicalSummarySchema = - new HistoricalSummary.HistoricalSummarySchema(); - final PendingBalanceDeposit.PendingBalanceDepositSchema pendingBalanceDepositSchema = - new PendingBalanceDeposit.PendingBalanceDepositSchema(); - final PendingPartialWithdrawal.PendingPartialWithdrawalSchema pendingPartialWithdrawalSchema = - new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); - final SpecConfigElectra specConfigElectra = SpecConfigElectra.required(specConfig); - final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema = - new PendingConsolidation.PendingConsolidationSchema(); - final SszField latestExecutionPayloadHeaderField = - new SszField( - LATEST_EXECUTION_PAYLOAD_HEADER_FIELD_INDEX, - BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER, - () -> new ExecutionPayloadHeaderSchemaElectra(specConfigElectra)); - final SszField nextWithdrawalIndexField = - new SszField( - NEXT_WITHDRAWAL_INDEX, - BeaconStateFields.NEXT_WITHDRAWAL_INDEX, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField nextWithdrawalValidatorIndexField = - new SszField( - NEXT_WITHDRAWAL_VALIDATOR_INDEX, - BeaconStateFields.NEXT_WITHDRAWAL_VALIDATOR_INDEX, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - - final SszField historicalSummariesField = - new SszField( - HISTORICAL_SUMMARIES_INDEX, - BeaconStateFields.HISTORICAL_SUMMARIES, - () -> - SszListSchema.create( - historicalSummarySchema, specConfig.getHistoricalRootsLimit())); - final SszField depositReceiptsStartIndexField = - new SszField( - DEPOSIT_RECEIPTS_START_INDEX, - BeaconStateFields.DEPOSIT_RECEIPTS_START_INDEX, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField depositBalanceToConsumeField = - new SszField( - DEPOSIT_BALANCE_TO_CONSUME_INDEX, - BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField exitBalanceToConsumeField = - new SszField( - EXIT_BALANCE_TO_CONSUME_INDEX, - BeaconStateFields.EXIT_BALANCE_TO_CONSUME, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField earliestExitEpochField = - new SszField( - EARLIEST_EXIT_EPOCH_INDEX, - BeaconStateFields.EARLIEST_EXIT_EPOCH, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField consolidationBalanceToConsumeField = - new SszField( - CONSOLIDATION_BALANCE_TO_CONSUME_INDEX, - BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField earliestConsolidationEpochField = - new SszField( - EARLIEST_CONSOLIDATION_EPOCH_INDEX, - BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH, - () -> SszPrimitiveSchemas.UINT64_SCHEMA); - final SszField pendingBalanceDepositsField = - new SszField( - PENDING_BALANCE_DEPOSITS_INDEX, - BeaconStateFields.PENDING_BALANCE_DEPOSITS, - () -> - SszListSchema.create( - pendingBalanceDepositSchema, - specConfigElectra.getPendingBalanceDepositsLimit())); - final SszField pendingPartialWithdrawalsField = - new SszField( - PENDING_PARTIAL_WITHDRAWALS_INDEX, - BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS, - () -> - SszListSchema.create( - pendingPartialWithdrawalSchema, - specConfigElectra.getPendingPartialWithdrawalsLimit())); - final SszField pendingConsolidationsField = - new SszField( - PENDING_CONSOLIDATIONS_INDEX, - BeaconStateFields.PENDING_CONSOLIDATIONS, - () -> - SszListSchema.create( - pendingConsolidationSchema, specConfigElectra.getPendingConsolidationsLimit())); - return Stream.concat( - BeaconStateSchemaAltair.getUniqueFields(specConfig).stream(), - Stream.of( - latestExecutionPayloadHeaderField, - nextWithdrawalIndexField, - nextWithdrawalValidatorIndexField, - historicalSummariesField, - depositReceiptsStartIndexField, - depositBalanceToConsumeField, - exitBalanceToConsumeField, - earliestExitEpochField, - consolidationBalanceToConsumeField, - earliestConsolidationEpochField, - pendingBalanceDepositsField, - pendingPartialWithdrawalsField, - pendingConsolidationsField)) - .toList(); - } - - @SuppressWarnings("unchecked") - public SszPrimitiveListSchema getPreviousEpochParticipationSchema() { - return (SszPrimitiveListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.PREVIOUS_EPOCH_PARTICIPATION)); - } - - @SuppressWarnings("unchecked") - public SszPrimitiveListSchema getCurrentEpochParticipationSchema() { - return (SszPrimitiveListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_EPOCH_PARTICIPATION)); - } - - public SszUInt64ListSchema getInactivityScoresSchema() { - return (SszUInt64ListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.INACTIVITY_SCORES)); - } - - public SyncCommittee.SyncCommitteeSchema getCurrentSyncCommitteeSchema() { - return (SyncCommittee.SyncCommitteeSchema) - getChildSchema(getFieldIndex(BeaconStateFields.CURRENT_SYNC_COMMITTEE)); - } - - public ExecutionPayloadHeaderSchemaElectra getLastExecutionPayloadHeaderSchema() { - return (ExecutionPayloadHeaderSchemaElectra) - getChildSchema(getFieldIndex(BeaconStateFields.LATEST_EXECUTION_PAYLOAD_HEADER)); - } - - @Override - public MutableBeaconStateElectra createBuilder() { - return new MutableBeaconStateElectraImpl(createEmptyBeaconStateImpl(), true); - } - - public static BeaconStateSchemaElectra create(final SpecConfig specConfig) { - return new BeaconStateSchemaElectra(specConfig); - } - - public static BeaconStateSchemaElectra required(final BeaconStateSchema schema) { - checkArgument( - schema instanceof BeaconStateSchemaElectra, - "Expected a BeaconStateSchemaElectra but was %s", - schema.getClass()); - return (BeaconStateSchemaElectra) schema; - } - - @SuppressWarnings("unchecked") - public SszListSchema getHistoricalSummariesSchema() { - return (SszListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.HISTORICAL_SUMMARIES)); - } - - @Override - public BeaconStateElectra createEmpty() { - return createEmptyBeaconStateImpl(); - } - - private BeaconStateElectraImpl createEmptyBeaconStateImpl() { - return new BeaconStateElectraImpl(this); - } - - @Override - public BeaconStateElectraImpl createFromBackingNode(TreeNode node) { - return new BeaconStateElectraImpl(this, node); - } - - @SuppressWarnings("unchecked") - public SszListSchema getPendingBalanceDepositsSchema() { - return (SszListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.PENDING_BALANCE_DEPOSITS)); - } - - @SuppressWarnings("unchecked") - public SszListSchema getPendingPartialWithdrawalsSchema() { - return (SszListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS)); - } - - @SuppressWarnings("unchecked") - public SszListSchema getPendingConsolidationsSchema() { - return (SszListSchema) - getChildSchema(getFieldIndex(BeaconStateFields.PENDING_CONSOLIDATIONS)); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java deleted file mode 100644 index 7db3042e85e..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/beaconstate/versions/electra/MutableBeaconStateElectra.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra; - -import java.util.Optional; -import tech.pegasys.teku.infrastructure.ssz.SszList; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.MutableBeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; - -public interface MutableBeaconStateElectra extends MutableBeaconStateDeneb, BeaconStateElectra { - static MutableBeaconStateElectra required(final MutableBeaconState state) { - return state - .toMutableVersionElectra() - .orElseThrow( - () -> - new IllegalArgumentException( - "Expected an Electra state but got: " + state.getClass().getSimpleName())); - } - - @Override - BeaconStateElectra commitChanges(); - - default void setDepositReceiptsStartIndex(final UInt64 depositReceiptsStartIndex) { - final int fieldIndex = - getSchema().getFieldIndex(BeaconStateFields.DEPOSIT_RECEIPTS_START_INDEX); - set(fieldIndex, SszUInt64.of(depositReceiptsStartIndex)); - } - - @Override - default Optional toMutableVersionElectra() { - return Optional.of(this); - } - - default void setDepositBalanceToConsume(final UInt64 depositBalanceToConsume) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.DEPOSIT_BALANCE_TO_CONSUME); - set(fieldIndex, SszUInt64.of(depositBalanceToConsume)); - } - - default void setExitBalanceToConsume(final UInt64 exitBalanceToConsume) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.EXIT_BALANCE_TO_CONSUME); - set(fieldIndex, SszUInt64.of(exitBalanceToConsume)); - } - - default void setEarliestExitEpoch(final UInt64 earliestExitEpoch) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.EARLIEST_EXIT_EPOCH); - set(fieldIndex, SszUInt64.of(earliestExitEpoch)); - } - - default void setConsolidationBalanceToConsume(final UInt64 consolidationBalanceToConsume) { - final int fieldIndex = - getSchema().getFieldIndex(BeaconStateFields.CONSOLIDATION_BALANCE_TO_CONSUME); - set(fieldIndex, SszUInt64.of(consolidationBalanceToConsume)); - } - - default void setEarliestConsolidationEpoch(final UInt64 earliestConsolidationEpoch) { - final int fieldIndex = - getSchema().getFieldIndex(BeaconStateFields.EARLIEST_CONSOLIDATION_EPOCH); - set(fieldIndex, SszUInt64.of(earliestConsolidationEpoch)); - } - - default void setPendingBalanceDeposits(SszList pendingBalanceDeposits) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_BALANCE_DEPOSITS); - set(fieldIndex, pendingBalanceDeposits); - } - - default void setPendingPartialWithdrawals( - SszList pendingPartialWithdrawals) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_PARTIAL_WITHDRAWALS); - set(fieldIndex, pendingPartialWithdrawals); - } - - default void setPendingConsolidations(SszList pendingConsolidations) { - final int fieldIndex = getSchema().getFieldIndex(BeaconStateFields.PENDING_CONSOLIDATIONS); - set(fieldIndex, pendingConsolidations); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java deleted file mode 100644 index 6973406b8d8..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingBalanceDeposit.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.versions.electra; - -import tech.pegasys.teku.infrastructure.ssz.containers.Container2; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - -public class PendingBalanceDeposit extends Container2 { - public static class PendingBalanceDepositSchema - extends ContainerSchema2 { - - public PendingBalanceDepositSchema() { - super( - "PendingBalanceDeposit", - namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("amount", SszPrimitiveSchemas.UINT64_SCHEMA)); - } - - @Override - public PendingBalanceDeposit createFromBackingNode(final TreeNode node) { - return new PendingBalanceDeposit(this, node); - } - - public PendingBalanceDeposit create(final SszUInt64 index, final SszUInt64 amount) { - return new PendingBalanceDeposit(this, index, amount); - } - - public SszUInt64 getIndexSchema() { - return (SszUInt64) getFieldSchema0(); - } - - public SszUInt64 getAmountSchema() { - return (SszUInt64) getFieldSchema1(); - } - } - - private PendingBalanceDeposit( - final PendingBalanceDepositSchema type, final TreeNode backingNode) { - super(type, backingNode); - } - - private PendingBalanceDeposit( - PendingBalanceDepositSchema type, final SszUInt64 index, final SszUInt64 amount) { - super(type, index, amount); - } - - public int getIndex() { - return ((SszUInt64) get(0)).get().intValue(); - } - - public UInt64 getAmount() { - return ((SszUInt64) get(1)).get(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java deleted file mode 100644 index f67b4a2329e..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingConsolidation.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.versions.electra; - -import tech.pegasys.teku.infrastructure.ssz.containers.Container2; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema2; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; - -public class PendingConsolidation extends Container2 { - protected PendingConsolidation( - ContainerSchema2 schema) { - super(schema); - } - - public PendingConsolidation( - final PendingConsolidationSchema pendingConsolidationSchema, - final SszUInt64 sourceIndex, - final SszUInt64 targetIndex) { - super(pendingConsolidationSchema, sourceIndex, targetIndex); - } - - public static class PendingConsolidationSchema - extends ContainerSchema2 { - public PendingConsolidationSchema() { - super( - "PendingConsolidation", - namedSchema("source_index", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("target_index", SszPrimitiveSchemas.UINT64_SCHEMA)); - } - - @Override - public PendingConsolidation createFromBackingNode(TreeNode node) { - return new PendingConsolidation(this, node); - } - - public PendingConsolidation create(final SszUInt64 sourceIndex, final SszUInt64 targetIndex) { - return new PendingConsolidation(this, sourceIndex, targetIndex); - } - } - - private PendingConsolidation(final PendingConsolidationSchema type, final TreeNode backingNode) { - super(type, backingNode); - } - - public int getSourceIndex() { - return ((SszUInt64) get(0)).get().intValue(); - } - - public int getTargetIndex() { - return ((SszUInt64) get(1)).get().intValue(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java deleted file mode 100644 index b0dacfae215..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/state/versions/electra/PendingPartialWithdrawal.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.state.versions.electra; - -import tech.pegasys.teku.infrastructure.ssz.containers.Container3; -import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; -import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; -import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; -import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; - -public class PendingPartialWithdrawal - extends Container3 { - - public static class PendingPartialWithdrawalSchema - extends ContainerSchema3 { - public PendingPartialWithdrawalSchema() { - super( - "PendingPartialWithdrawal", - namedSchema("index", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("amount", SszPrimitiveSchemas.UINT64_SCHEMA), - namedSchema("withdrawable_epoch", SszPrimitiveSchemas.UINT64_SCHEMA)); - } - - public PendingPartialWithdrawal create( - final SszUInt64 index, final SszUInt64 amount, final SszUInt64 withdrawableEpoch) { - return new PendingPartialWithdrawal(this, index, amount, withdrawableEpoch); - } - - public SszUInt64 getIndexSchema() { - return (SszUInt64) getFieldSchema0(); - } - - public SszUInt64 getAmountSchema() { - return (SszUInt64) getFieldSchema1(); - } - - public SszUInt64 getWithdrawableEpochSchema() { - return (SszUInt64) getFieldSchema2(); - } - - @Override - public PendingPartialWithdrawal createFromBackingNode(TreeNode node) { - return null; - } - } - - private PendingPartialWithdrawal( - PendingPartialWithdrawal.PendingPartialWithdrawalSchema type, - final SszUInt64 index, - final SszUInt64 amount, - final SszUInt64 withdrawableEpoch) { - super(type, index, amount, withdrawableEpoch); - } - - public int getIndex() { - return ((SszUInt64) get(0)).get().intValue(); - } - - public UInt64 getAmount() { - return ((SszUInt64) get(1)).get(); - } - - public UInt64 getWithdrawableEpoch() { - return ((SszUInt64) get(2)).get(); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java index eb65a8079c1..37041bdffce 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/ColumnSlotAndIdentifier.java @@ -17,7 +17,7 @@ import org.apache.tuweni.bytes.Bytes32; import org.jetbrains.annotations.NotNull; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public record ColumnSlotAndIdentifier(UInt64 slot, DataColumnIdentifier identifier) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java deleted file mode 100644 index 36a98bfeacd..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/DepositReceiptsUtil.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.util; - -import java.security.SecureRandom; -import java.util.List; -import java.util.stream.IntStream; -import org.apache.tuweni.bytes.Bytes32; -import tech.pegasys.teku.bls.BLS; -import tech.pegasys.teku.bls.BLSKeyPair; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.bls.BLSSignature; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.constants.Domain; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.operations.DepositMessage; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -public class DepositReceiptsUtil { - - private static final int MAX_NUMBER_OF_DEPOSITS_PER_BLOCK = 3; - - private final Spec spec; - - @SuppressWarnings("DoNotCreateSecureRandomDirectly") - private final SecureRandom random = new SecureRandom(); - - public DepositReceiptsUtil(final Spec spec) { - this.spec = spec; - } - - public List generateDepositReceipts(final BeaconState state) { - final UInt64 nextDepositReceiptIndex = UInt64.valueOf(state.getValidators().size()); - return IntStream.range(0, getNumberOfDepositReceiptsToGenerate()) - .mapToObj(i -> createDepositReceipt(state.getSlot(), nextDepositReceiptIndex.plus(i))) - .toList(); - } - - private int getNumberOfDepositReceiptsToGenerate() { - return random.nextInt(MAX_NUMBER_OF_DEPOSITS_PER_BLOCK + 1); - } - - private DepositReceipt createDepositReceipt(final UInt64 slot, final UInt64 index) { - final BLSKeyPair validatorKeyPair = BLSKeyPair.random(random); - final BLSPublicKey publicKey = validatorKeyPair.getPublicKey(); - final UInt64 depositAmount = UInt64.THIRTY_TWO_ETH; - final DepositMessage depositMessage = - new DepositMessage(publicKey, Bytes32.ZERO, depositAmount); - final MiscHelpers miscHelpers = spec.atSlot(slot).miscHelpers(); - final Bytes32 depositDomain = miscHelpers.computeDomain(Domain.DEPOSIT); - final BLSSignature signature = - BLS.sign( - validatorKeyPair.getSecretKey(), - miscHelpers.computeSigningRoot(depositMessage, depositDomain)); - return SchemaDefinitionsElectra.required(spec.atSlot(slot).getSchemaDefinitions()) - .getDepositReceiptSchema() - .create(publicKey, Bytes32.ZERO, depositAmount, signature, index); - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java index 8d1decb640f..e84d706c68a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/executionlayer/ExecutionLayerChannelStub.java @@ -61,11 +61,9 @@ import tech.pegasys.teku.spec.datastructures.execution.HeaderWithFallbackData; import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; import tech.pegasys.teku.spec.datastructures.execution.PowBlock; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.datastructures.util.BlobsUtil; -import tech.pegasys.teku.spec.datastructures.util.DepositReceiptsUtil; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; @@ -75,8 +73,6 @@ public class ExecutionLayerChannelStub implements ExecutionLayerChannel { private static final ClientVersion STUB_CLIENT_VERSION = new ClientVersion("SB", ExecutionLayerChannel.STUB_ENDPOINT_PREFIX, "0.0.0", Bytes4.ZERO); - private static final boolean GENERATE_DEPOSIT_RECEIPTS = false; - private final TimeProvider timeProvider; private final Map knownBlocks = new ConcurrentHashMap<>(); private final Map knownPosBlocks = new ConcurrentHashMap<>(); @@ -85,7 +81,6 @@ public class ExecutionLayerChannelStub implements ExecutionLayerChannel { private final Set requestedPowBlocks = new HashSet<>(); private final Spec spec; private final BlobsUtil blobsUtil; - private final DepositReceiptsUtil depositReceiptsUtil; private final Random random = new Random(); private PayloadStatus payloadStatus = PayloadStatus.VALID; @@ -127,7 +122,6 @@ public ExecutionLayerChannelStub( kzg = KZG.NOOP; } this.blobsUtil = new BlobsUtil(spec, kzg); - this.depositReceiptsUtil = new DepositReceiptsUtil(spec); } public ExecutionLayerChannelStub( @@ -275,9 +269,7 @@ public SafeFuture engineGetPayload( .transactions(transactions) .withdrawals(() -> payloadAttributes.getWithdrawals().orElse(List.of())) .blobGasUsed(() -> UInt64.ZERO) - .excessBlobGas(() -> UInt64.ZERO) - .depositReceipts(() -> generateDepositReceipts(state)) - .exits(List::of)); + .excessBlobGas(() -> UInt64.ZERO)); // we assume all blocks are produced locally lastValidBlock = @@ -559,18 +551,4 @@ private Bytes generateBlobsAndTransaction( return blobsUtil.generateRawBlobTransactionFromKzgCommitments(commitments); } - - private List generateDepositReceipts(final BeaconState state) { - return spec.atSlot(state.getSlot()) - .getConfig() - .toVersionElectra() - .map( - __ -> { - if (GENERATE_DEPOSIT_RECEIPTS) { - return depositReceiptsUtil.generateDepositReceipts(state); - } - return List.of(); - }) - .orElse(List.of()); - } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java index 252a9ff2448..f3b4b4b00fb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/AbstractBlockProcessor.java @@ -46,9 +46,6 @@ import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -443,8 +440,6 @@ protected void processOperationsNoValidation( processDeposits(state, body.getDeposits()); processVoluntaryExitsNoValidation( state, body.getVoluntaryExits(), validatorExitContextSupplier); - processExecutionLayerExits( - state, body.getOptionalExecutionPayload(), validatorExitContextSupplier); }); } @@ -877,30 +872,6 @@ protected BlockValidationResult verifyVoluntaryExits( return BlockValidationResult.SUCCESSFUL; } - protected void processExecutionLayerExits( - final MutableBeaconState state, - final Optional executionPayload, - final Supplier validatorExitContextSupplier) - throws BlockProcessingException { - // No ExecutionLayer exits until Electra - } - - @Override - public void processDepositReceipts( - final MutableBeaconState state, final SszList depositReceipts) - throws BlockProcessingException { - // No DepositReceipts until Electra - } - - @Override - public void processExecutionLayerExits( - final MutableBeaconState state, - final SszList exits, - final Supplier validatorExitContextSupplier) - throws BlockProcessingException { - // No ExecutionLayer exits until Electra - } - // Catch generic errors and wrap them in a BlockProcessingException protected void safelyProcess(BlockProcessingAction action) throws BlockProcessingException { try { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java index d125c5b938a..0f21a58f1a0 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/block/BlockProcessor.java @@ -32,8 +32,6 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSummary; import tech.pegasys.teku.spec.datastructures.execution.NewPayloadRequest; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -168,16 +166,6 @@ void processBlsToExecutionChanges( void processWithdrawals(MutableBeaconState state, ExecutionPayloadSummary payloadSummary) throws BlockProcessingException; - void processDepositReceipts( - final MutableBeaconState state, final SszList depositReceipts) - throws BlockProcessingException; - - void processExecutionLayerExits( - final MutableBeaconState state, - final SszList exits, - final Supplier validatorExitContextSupplier) - throws BlockProcessingException; - Optional> getExpectedWithdrawals(BeaconState preState); default Optional toVersionAltair() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 89a4602b6be..611f4749938 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -39,7 +39,7 @@ import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.constants.NetworkConstants; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.state.ForkData; import tech.pegasys.teku.spec.datastructures.state.SigningData; @@ -47,7 +47,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateCache; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.logic.versions.deneb.types.VersionedHash; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; public class MiscHelpers { @@ -381,15 +381,11 @@ public UInt64 getMaxRequestBlocks() { return UInt64.valueOf(specConfig.getNetworkingConfig().getMaxRequestBlocks()); } - public boolean isFormerDepositMechanismDisabled(final BeaconState state) { - return false; - } - public Optional toVersionDeneb() { return Optional.empty(); } - public Optional toVersionElectra() { + public Optional toVersionEip7594() { return Optional.empty(); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/SpecLogicEip7594.java similarity index 88% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/SpecLogicEip7594.java index bd8f8ee8e39..64a67394ab5 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/SpecLogicElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/SpecLogicEip7594.java @@ -11,10 +11,10 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.logic.versions.electra; +package tech.pegasys.teku.spec.logic.versions.eip7594; import java.util.Optional; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.logic.common.AbstractSpecLogic; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; @@ -41,16 +41,16 @@ import tech.pegasys.teku.spec.logic.versions.deneb.operations.validation.AttestationDataValidatorDeneb; import tech.pegasys.teku.spec.logic.versions.deneb.util.AttestationUtilDeneb; import tech.pegasys.teku.spec.logic.versions.deneb.util.ForkChoiceUtilDeneb; -import tech.pegasys.teku.spec.logic.versions.electra.block.BlockProcessorElectra; -import tech.pegasys.teku.spec.logic.versions.electra.forktransition.ElectraStateUpgrade; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.logic.versions.eip7594.block.BlockProcessorEip7594; +import tech.pegasys.teku.spec.logic.versions.eip7594.forktransition.Eip7594StateUpgrade; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; -public class SpecLogicElectra extends AbstractSpecLogic { +public class SpecLogicEip7594 extends AbstractSpecLogic { private final Optional syncCommitteeUtil; private final Optional lightClientUtil; - private SpecLogicElectra( + private SpecLogicEip7594( final Predicates predicates, final MiscHelpersDeneb miscHelpers, final BeaconStateAccessorsAltair beaconStateAccessors, @@ -68,7 +68,7 @@ private SpecLogicElectra( final BlindBlockUtil blindBlockUtil, final SyncCommitteeUtil syncCommitteeUtil, final LightClientUtil lightClientUtil, - final ElectraStateUpgrade stateUpgrade) { + final Eip7594StateUpgrade stateUpgrade) { super( predicates, miscHelpers, @@ -90,12 +90,12 @@ private SpecLogicElectra( this.lightClientUtil = Optional.of(lightClientUtil); } - public static SpecLogicElectra create( - final SpecConfigElectra config, final SchemaDefinitionsElectra schemaDefinitions) { + public static SpecLogicEip7594 create( + final SpecConfigEip7594 config, final SchemaDefinitionsEip7594 schemaDefinitions) { // Helpers final Predicates predicates = new Predicates(config); - final MiscHelpersElectra miscHelpers = - new MiscHelpersElectra(config, predicates, schemaDefinitions); + final MiscHelpersEip7594 miscHelpers = + new MiscHelpersEip7594(config, predicates, schemaDefinitions); final BeaconStateAccessorsDeneb beaconStateAccessors = new BeaconStateAccessorsDeneb(config, predicates, miscHelpers); final BeaconStateMutatorsBellatrix beaconStateMutators = @@ -141,8 +141,8 @@ public static SpecLogicElectra create( beaconStateAccessors, validatorsUtil, config, miscHelpers, schemaDefinitions); final LightClientUtil lightClientUtil = new LightClientUtil(beaconStateAccessors, syncCommitteeUtil, schemaDefinitions); - final BlockProcessorElectra blockProcessor = - new BlockProcessorElectra( + final BlockProcessorEip7594 blockProcessor = + new BlockProcessorEip7594( config, predicates, miscHelpers, @@ -164,10 +164,10 @@ public static SpecLogicElectra create( final BlindBlockUtilBellatrix blindBlockUtil = new BlindBlockUtilBellatrix(schemaDefinitions); // State upgrade - final ElectraStateUpgrade stateUpgrade = - new ElectraStateUpgrade(config, schemaDefinitions, beaconStateAccessors); + final Eip7594StateUpgrade stateUpgrade = + new Eip7594StateUpgrade(config, schemaDefinitions, beaconStateAccessors); - return new SpecLogicElectra( + return new SpecLogicEip7594( predicates, miscHelpers, beaconStateAccessors, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/block/BlockProcessorEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/block/BlockProcessorEip7594.java new file mode 100644 index 00000000000..2755ff7d0ac --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/block/BlockProcessorEip7594.java @@ -0,0 +1,60 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.logic.versions.eip7594.block; + +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; +import tech.pegasys.teku.spec.logic.common.helpers.Predicates; +import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; +import tech.pegasys.teku.spec.logic.common.operations.validation.OperationValidator; +import tech.pegasys.teku.spec.logic.common.util.AttestationUtil; +import tech.pegasys.teku.spec.logic.common.util.BeaconStateUtil; +import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; +import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; +import tech.pegasys.teku.spec.logic.versions.altair.helpers.BeaconStateAccessorsAltair; +import tech.pegasys.teku.spec.logic.versions.deneb.block.BlockProcessorDeneb; +import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; + +public class BlockProcessorEip7594 extends BlockProcessorDeneb { + + public BlockProcessorEip7594( + final SpecConfigEip7594 specConfig, + final Predicates predicates, + final MiscHelpersDeneb miscHelpers, + final SyncCommitteeUtil syncCommitteeUtil, + final BeaconStateAccessorsAltair beaconStateAccessors, + final BeaconStateMutators beaconStateMutators, + final OperationSignatureVerifier operationSignatureVerifier, + final BeaconStateUtil beaconStateUtil, + final AttestationUtil attestationUtil, + final ValidatorsUtil validatorsUtil, + final OperationValidator operationValidator, + final SchemaDefinitionsEip7594 schemaDefinitions) { + super( + specConfig, + predicates, + miscHelpers, + syncCommitteeUtil, + beaconStateAccessors, + beaconStateMutators, + operationSignatureVerifier, + beaconStateUtil, + attestationUtil, + validatorsUtil, + operationValidator, + SchemaDefinitionsDeneb.required(schemaDefinitions)); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/forktransition/ElectraStateUpgrade.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/forktransition/Eip7594StateUpgrade.java similarity index 82% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/forktransition/ElectraStateUpgrade.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/forktransition/Eip7594StateUpgrade.java index b1cd5131027..c1288b43c73 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/forktransition/ElectraStateUpgrade.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/forktransition/Eip7594StateUpgrade.java @@ -11,31 +11,30 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.logic.versions.electra.forktransition; +package tech.pegasys.teku.spec.logic.versions.eip7594.forktransition; -import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.execution.versions.deneb.ExecutionPayloadHeaderDeneb; import tech.pegasys.teku.spec.datastructures.state.Fork; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.spec.datastructures.state.beaconstate.common.BeaconStateFields; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; import tech.pegasys.teku.spec.logic.common.forktransition.StateUpgrade; import tech.pegasys.teku.spec.logic.versions.altair.helpers.BeaconStateAccessorsAltair; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; -public class ElectraStateUpgrade implements StateUpgrade { +public class Eip7594StateUpgrade implements StateUpgrade { - private final SpecConfigElectra specConfig; - private final SchemaDefinitionsElectra schemaDefinitions; + private final SpecConfigEip7594 specConfig; + private final SchemaDefinitionsEip7594 schemaDefinitions; private final BeaconStateAccessorsAltair beaconStateAccessors; - public ElectraStateUpgrade( - final SpecConfigElectra specConfig, - final SchemaDefinitionsElectra schemaDefinitions, + public Eip7594StateUpgrade( + final SpecConfigEip7594 specConfig, + final SchemaDefinitionsEip7594 schemaDefinitions, final BeaconStateAccessorsAltair beaconStateAccessors) { this.specConfig = specConfig; this.schemaDefinitions = schemaDefinitions; @@ -43,13 +42,13 @@ public ElectraStateUpgrade( } @Override - public BeaconStateElectra upgrade(final BeaconState preState) { + public BeaconStateEip7594 upgrade(final BeaconState preState) { final UInt64 epoch = beaconStateAccessors.getCurrentEpoch(preState); final BeaconStateDeneb preStateDeneb = BeaconStateDeneb.required(preState); return schemaDefinitions .getBeaconStateSchema() .createEmpty() - .updatedElectra( + .updatedEip7594( state -> { BeaconStateFields.copyCommonFieldsFromSource(state, preState); @@ -62,7 +61,7 @@ public BeaconStateElectra upgrade(final BeaconState preState) { state.setFork( new Fork( preState.getFork().getCurrentVersion(), - specConfig.getElectraForkVersion(), + specConfig.getEip7594ForkVersion(), epoch)); final ExecutionPayloadHeaderDeneb denebHeader = @@ -89,9 +88,7 @@ public BeaconStateElectra upgrade(final BeaconState preState) { .transactionsRoot(denebHeader.getTransactionsRoot()) .withdrawalsRoot(denebHeader::getWithdrawalsRoot) .blobGasUsed(denebHeader::getBlobGasUsed) - .excessBlobGas(denebHeader::getExcessBlobGas) - .depositReceiptsRoot(() -> Bytes32.ZERO) - .exitsRoot(() -> Bytes32.ZERO)); + .excessBlobGas(denebHeader::getExcessBlobGas)); state.setLatestExecutionPayloadHeader(upgradedExecutionPayloadHeader); @@ -99,8 +96,6 @@ public BeaconStateElectra upgrade(final BeaconState preState) { preStateDeneb.getNextWithdrawalValidatorIndex()); state.setNextWithdrawalIndex(preStateDeneb.getNextWithdrawalIndex()); state.setHistoricalSummaries(preStateDeneb.getHistoricalSummaries()); - state.setDepositReceiptsStartIndex( - SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX); }); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java similarity index 67% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index ecb01f021e6..e62c2621eac 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -11,7 +11,7 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.spec.logic.versions.electra.helpers; +package tech.pegasys.teku.spec.logic.versions.eip7594.helpers; import java.util.HashSet; import java.util.List; @@ -25,49 +25,37 @@ import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCell; import tech.pegasys.teku.kzg.KZGCellWithID; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; -public class MiscHelpersElectra extends MiscHelpersDeneb { +public class MiscHelpersEip7594 extends MiscHelpersDeneb { - public static MiscHelpersElectra required(final MiscHelpers miscHelpers) { + public static MiscHelpersEip7594 required(final MiscHelpers miscHelpers) { return miscHelpers - .toVersionElectra() + .toVersionEip7594() .orElseThrow( () -> new IllegalArgumentException( - "Expected Electra misc helpers but got: " + "Expected EIP7594 misc helpers but got: " + miscHelpers.getClass().getSimpleName())); } - private final SpecConfigElectra specConfigElectra; + private final SpecConfigEip7594 specConfigEip7594; - public MiscHelpersElectra( - final SpecConfigElectra specConfig, + public MiscHelpersEip7594( + final SpecConfigEip7594 specConfig, final Predicates predicates, - final SchemaDefinitionsElectra schemaDefinitions) { + final SchemaDefinitionsEip7594 schemaDefinitions) { super(specConfig, predicates, schemaDefinitions); - this.specConfigElectra = specConfig; - } - - @Override - public boolean isFormerDepositMechanismDisabled(final BeaconState state) { - // if the next deposit to be processed by Eth1Data poll has the index of the first deposit - // processed with the new deposit flow, i.e. `eth1_deposit_index == - // deposit_receipts_start_index`, we should stop Eth1Data deposits processing - return state - .getEth1DepositIndex() - .equals(BeaconStateElectra.required(state).getDepositReceiptsStartIndex()); + this.specConfigEip7594 = specConfig; } public UInt64 computeSubnetForDataColumnSidecar(UInt64 columnIndex) { - return columnIndex.mod(specConfigElectra.getDataColumnSidecarSubnetCount()); + return columnIndex.mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); } public Set computeCustodyColumnIndexes( @@ -76,7 +64,7 @@ public Set computeCustodyColumnIndexes( Set subnets = new HashSet<>(computeDataColumnSidecarBackboneSubnets(nodeId, epoch, subnetCount)); return Stream.iterate(UInt64.ZERO, UInt64::increment) - .limit(specConfigElectra.getNumberOfColumns().intValue()) + .limit(specConfigEip7594.getNumberOfColumns().intValue()) .filter(columnIndex -> subnets.contains(computeSubnetForDataColumnSidecar(columnIndex))) .collect(Collectors.toSet()); } @@ -106,7 +94,7 @@ public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataCo } @Override - public Optional toVersionElectra() { + public Optional toVersionEip7594() { return Optional.of(this); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java deleted file mode 100644 index 9e1c1975434..00000000000 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectra.java +++ /dev/null @@ -1,236 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.logic.versions.electra.block; - -import static com.google.common.base.Preconditions.checkArgument; -import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; - -import java.util.Optional; -import java.util.function.Supplier; -import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.infrastructure.ssz.SszList; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.cache.IndexedAttestationCache; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadElectra; -import tech.pegasys.teku.spec.datastructures.state.Validator; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.MutableBeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators; -import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; -import tech.pegasys.teku.spec.logic.common.helpers.Predicates; -import tech.pegasys.teku.spec.logic.common.operations.OperationSignatureVerifier; -import tech.pegasys.teku.spec.logic.common.operations.validation.OperationValidator; -import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; -import tech.pegasys.teku.spec.logic.common.util.AttestationUtil; -import tech.pegasys.teku.spec.logic.common.util.BeaconStateUtil; -import tech.pegasys.teku.spec.logic.common.util.SyncCommitteeUtil; -import tech.pegasys.teku.spec.logic.common.util.ValidatorsUtil; -import tech.pegasys.teku.spec.logic.versions.altair.helpers.BeaconStateAccessorsAltair; -import tech.pegasys.teku.spec.logic.versions.deneb.block.BlockProcessorDeneb; -import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -public class BlockProcessorElectra extends BlockProcessorDeneb { - - public BlockProcessorElectra( - final SpecConfigElectra specConfig, - final Predicates predicates, - final MiscHelpersDeneb miscHelpers, - final SyncCommitteeUtil syncCommitteeUtil, - final BeaconStateAccessorsAltair beaconStateAccessors, - final BeaconStateMutators beaconStateMutators, - final OperationSignatureVerifier operationSignatureVerifier, - final BeaconStateUtil beaconStateUtil, - final AttestationUtil attestationUtil, - final ValidatorsUtil validatorsUtil, - final OperationValidator operationValidator, - final SchemaDefinitionsElectra schemaDefinitions) { - super( - specConfig, - predicates, - miscHelpers, - syncCommitteeUtil, - beaconStateAccessors, - beaconStateMutators, - operationSignatureVerifier, - beaconStateUtil, - attestationUtil, - validatorsUtil, - operationValidator, - SchemaDefinitionsDeneb.required(schemaDefinitions)); - } - - @Override - protected void processOperationsNoValidation( - final MutableBeaconState state, - final BeaconBlockBody body, - final IndexedAttestationCache indexedAttestationCache) - throws BlockProcessingException { - super.processOperationsNoValidation(state, body, indexedAttestationCache); - - safelyProcess( - () -> - processDepositReceipts( - state, - body.getOptionalExecutionPayload() - .flatMap(ExecutionPayload::toVersionElectra) - .map(ExecutionPayloadElectra::getDepositReceipts) - .orElseThrow( - () -> - new BlockProcessingException( - "Deposit receipts were not found during block processing.")))); - } - - @Override - protected void verifyOutstandingDepositsAreProcessed( - final BeaconState state, final BeaconBlockBody body) { - final UInt64 eth1DepositIndexLimit = - state - .getEth1Data() - .getDepositCount() - .min(BeaconStateElectra.required(state).getDepositReceiptsStartIndex()); - - if (state.getEth1DepositIndex().isLessThan(eth1DepositIndexLimit)) { - final int expectedDepositCount = - Math.min( - specConfig.getMaxDeposits(), - eth1DepositIndexLimit.minus(state.getEth1DepositIndex()).intValue()); - - checkArgument( - body.getDeposits().size() == expectedDepositCount, - "process_operations: Verify that outstanding deposits are processed up to the maximum number of deposits"); - } else { - checkArgument( - body.getDeposits().isEmpty(), - "process_operations: Verify that former deposit mechanism has been disabled"); - } - } - - @Override - protected void processExecutionLayerExits( - final MutableBeaconState state, - final Optional executionPayload, - final Supplier validatorExitContextSupplier) - throws BlockProcessingException { - processExecutionLayerExits( - state, getExecutionLayerExitsFromBlock(executionPayload), validatorExitContextSupplier); - } - - /* - Implements process_execution_layer_exit from consensus-specs (EIP-7002) - */ - @Override - public void processExecutionLayerExits( - final MutableBeaconState state, - final SszList exits, - final Supplier validatorExitContextSupplier) - throws BlockProcessingException { - final UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(state.getSlot()); - - exits.forEach( - exit -> { - final Optional maybeValidatorIndex = - validatorsUtil.getValidatorIndex(state, exit.getValidatorPublicKey()); - if (maybeValidatorIndex.isEmpty()) { - return; - } - - final int validatorIndex = maybeValidatorIndex.get(); - final Validator validator = state.getValidators().get(validatorIndex); - - // Check if validator has eth1 credentials - boolean isExecutionAddress = predicates.hasEth1WithdrawalCredential(validator); - if (!isExecutionAddress) { - return; - } - - // Check exit source_address matches validator eth1 withdrawal credentials - final Bytes20 executionAddress = - new Bytes20(validator.getWithdrawalCredentials().slice(12)); - boolean isCorrectSourceAddress = executionAddress.equals(exit.getSourceAddress()); - if (!isCorrectSourceAddress) { - return; - } - - // Check if validator is active - final boolean isValidatorActive = predicates.isActiveValidator(validator, currentEpoch); - if (!isValidatorActive) { - return; - } - - // Check if validator has already initiated exit - boolean hasInitiatedExit = !validator.getExitEpoch().equals(FAR_FUTURE_EPOCH); - if (hasInitiatedExit) { - return; - } - - // Check if validator has been active long enough - final boolean validatorActiveLongEnough = - currentEpoch.isLessThan( - validator.getActivationEpoch().plus(specConfig.getShardCommitteePeriod())); - if (validatorActiveLongEnough) { - return; - } - - // If all conditions are ok, initiate exit - beaconStateMutators.initiateValidatorExit( - state, validatorIndex, validatorExitContextSupplier); - }); - } - - private SszList getExecutionLayerExitsFromBlock( - final Optional maybeExecutionPayload) throws BlockProcessingException { - return maybeExecutionPayload - .flatMap(ExecutionPayload::toVersionElectra) - .map(ExecutionPayloadElectra::getExits) - .orElseThrow( - () -> - new BlockProcessingException( - "Execution layer exits were not found during block processing.")); - } - - /* - Implements process_deposit_receipt from consensus-specs (EIP-6110) - */ - @Override - public void processDepositReceipts( - final MutableBeaconState state, final SszList depositReceipts) - throws BlockProcessingException { - final MutableBeaconStateElectra electraState = MutableBeaconStateElectra.required(state); - for (DepositReceipt depositReceipt : depositReceipts) { - // process_deposit_receipt - if (electraState - .getDepositReceiptsStartIndex() - .equals(SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX)) { - electraState.setDepositReceiptsStartIndex(depositReceipt.getIndex()); - } - applyDeposit( - state, - depositReceipt.getPubkey(), - depositReceipt.getWithdrawalCredentials(), - depositReceipt.getAmount(), - depositReceipt.getSignature(), - Optional.empty(), - false); - } - } -} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java index 0cdc5340f33..67ad42370b2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitions.java @@ -111,7 +111,7 @@ default Optional toVersionDeneb() { } @NonSchema - default Optional toVersionElectra() { + default Optional toVersionEip7594() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java similarity index 64% rename from ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java rename to ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java index abeaf602c08..a6c74862d44 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsElectra.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java @@ -16,10 +16,10 @@ import static com.google.common.base.Preconditions.checkArgument; import java.util.Optional; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.CellSchema; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSchema; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.CellSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSchema; import tech.pegasys.teku.spec.datastructures.blocks.BlockContainer; import tech.pegasys.teku.spec.datastructures.blocks.BlockContainerSchema; @@ -29,9 +29,9 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainerSchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyBuilderElectra; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodySchemaElectraImpl; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BlindedBeaconBlockBodySchemaElectraImpl; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyBuilderEip7594; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594Impl; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BlindedBeaconBlockBodySchemaEip7594Impl; import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlockContentsSchema; import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.SignedBlockContentsSchema; import tech.pegasys.teku.spec.datastructures.builder.BlobsBundleSchema; @@ -42,114 +42,90 @@ import tech.pegasys.teku.spec.datastructures.builder.versions.deneb.BuilderBidSchemaDeneb; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeaderSchema; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceiptSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadHeaderSchemaElectra; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionPayloadSchemaElectra; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadSchemaEip7594; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.MutableBeaconStateEip7594; -public class SchemaDefinitionsElectra extends SchemaDefinitionsDeneb { +public class SchemaDefinitionsEip7594 extends SchemaDefinitionsDeneb { - private final BeaconStateSchemaElectra beaconStateSchema; + private final BeaconStateSchemaEip7594 beaconStateSchema; - private final ExecutionPayloadSchemaElectra executionPayloadSchemaElectra; - private final ExecutionPayloadHeaderSchemaElectra executionPayloadHeaderSchemaElectra; + private final ExecutionPayloadSchemaEip7594 executionPayloadSchemaEip7594; + private final ExecutionPayloadHeaderSchemaEip7594 executionPayloadHeaderSchemaEip7594; - private final BeaconBlockBodySchemaElectraImpl beaconBlockBodySchema; - private final BlindedBeaconBlockBodySchemaElectraImpl blindedBeaconBlockBodySchema; + private final BeaconBlockBodySchemaEip7594Impl beaconBlockBodySchema; + private final BlindedBeaconBlockBodySchemaEip7594Impl blindedBeaconBlockBodySchema; private final BeaconBlockSchema beaconBlockSchema; private final BeaconBlockSchema blindedBeaconBlockSchema; private final SignedBeaconBlockSchema signedBeaconBlockSchema; private final SignedBeaconBlockSchema signedBlindedBeaconBlockSchema; - private final BuilderBidSchema builderBidSchemaElectra; - private final SignedBuilderBidSchema signedBuilderBidSchemaElectra; + private final BuilderBidSchema builderBidSchemaEip7594; + private final SignedBuilderBidSchema signedBuilderBidSchemaEip7594; private final BlockContentsSchema blockContentsSchema; private final SignedBlockContentsSchema signedBlockContentsSchema; private final BlobsBundleSchema blobsBundleSchema; private final ExecutionPayloadAndBlobsBundleSchema executionPayloadAndBlobsBundleSchema; - private final DepositReceiptSchema depositReceiptSchema; - - private final ExecutionLayerExitSchema executionLayerExitSchema; - - private final PendingBalanceDeposit.PendingBalanceDepositSchema pendingBalanceDepositSchema; - - private final PendingPartialWithdrawal.PendingPartialWithdrawalSchema - pendingPartialWithdrawalSchema; - private final PendingConsolidation.PendingConsolidationSchema pendingConsolidationSchema; - private final CellSchema cellSchema; private final DataColumnSchema dataColumnSchema; private final DataColumnSidecarSchema dataColumnSidecarSchema; private final DataColumnSidecarsByRootRequestMessageSchema dataColumnSidecarsByRootRequestMessageSchema; - public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { + public SchemaDefinitionsEip7594(final SpecConfigEip7594 specConfig) { super(specConfig); - this.executionPayloadSchemaElectra = new ExecutionPayloadSchemaElectra(specConfig); + this.executionPayloadSchemaEip7594 = new ExecutionPayloadSchemaEip7594(specConfig); - this.beaconStateSchema = BeaconStateSchemaElectra.create(specConfig); - this.executionPayloadHeaderSchemaElectra = + this.beaconStateSchema = BeaconStateSchemaEip7594.create(specConfig); + this.executionPayloadHeaderSchemaEip7594 = beaconStateSchema.getLastExecutionPayloadHeaderSchema(); this.beaconBlockBodySchema = - BeaconBlockBodySchemaElectraImpl.create( + BeaconBlockBodySchemaEip7594Impl.create( specConfig, getAttesterSlashingSchema(), getSignedBlsToExecutionChangeSchema(), getBlobKzgCommitmentsSchema(), - "BeaconBlockBodyElectra"); + "BeaconBlockBodyEip7594"); this.blindedBeaconBlockBodySchema = - BlindedBeaconBlockBodySchemaElectraImpl.create( + BlindedBeaconBlockBodySchemaEip7594Impl.create( specConfig, getAttesterSlashingSchema(), getSignedBlsToExecutionChangeSchema(), getBlobKzgCommitmentsSchema(), - "BlindedBlockBodyElectra"); - this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockElectra"); + "BlindedBlockBodyEip7594"); + this.beaconBlockSchema = new BeaconBlockSchema(beaconBlockBodySchema, "BeaconBlockEip7594"); this.blindedBeaconBlockSchema = - new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockElectra"); + new BeaconBlockSchema(blindedBeaconBlockBodySchema, "BlindedBlockEip7594"); this.signedBeaconBlockSchema = - new SignedBeaconBlockSchema(beaconBlockSchema, "SignedBeaconBlockElectra"); + new SignedBeaconBlockSchema(beaconBlockSchema, "SignedBeaconBlockEip7594"); this.signedBlindedBeaconBlockSchema = - new SignedBeaconBlockSchema(blindedBeaconBlockSchema, "SignedBlindedBlockElectra"); - this.builderBidSchemaElectra = + new SignedBeaconBlockSchema(blindedBeaconBlockSchema, "SignedBlindedBlockEip7594"); + this.builderBidSchemaEip7594 = new BuilderBidSchemaDeneb( - "BuilderBidElectra", - executionPayloadHeaderSchemaElectra, + "BuilderBidEip7594", + executionPayloadHeaderSchemaEip7594, getBlobKzgCommitmentsSchema()); - this.signedBuilderBidSchemaElectra = - new SignedBuilderBidSchema("SignedBuilderBidElectra", builderBidSchemaElectra); + this.signedBuilderBidSchemaEip7594 = + new SignedBuilderBidSchema("SignedBuilderBidEip7594", builderBidSchemaEip7594); this.blockContentsSchema = BlockContentsSchema.create( - specConfig, beaconBlockSchema, getBlobSchema(), "BlockContentsElectra"); + specConfig, beaconBlockSchema, getBlobSchema(), "BlockContentsEip7594"); this.signedBlockContentsSchema = SignedBlockContentsSchema.create( - specConfig, signedBeaconBlockSchema, getBlobSchema(), "SignedBlockContentsElectra"); + specConfig, signedBeaconBlockSchema, getBlobSchema(), "SignedBlockContentsEip7594"); this.blobsBundleSchema = new BlobsBundleSchema( - "BlobsBundleElectra", getBlobSchema(), getBlobKzgCommitmentsSchema(), specConfig); + "BlobsBundleEip7594", getBlobSchema(), getBlobKzgCommitmentsSchema(), specConfig); this.executionPayloadAndBlobsBundleSchema = - new ExecutionPayloadAndBlobsBundleSchema(executionPayloadSchemaElectra, blobsBundleSchema); - - this.depositReceiptSchema = DepositReceipt.SSZ_SCHEMA; - this.executionLayerExitSchema = ExecutionLayerExit.SSZ_SCHEMA; - this.pendingBalanceDepositSchema = new PendingBalanceDeposit.PendingBalanceDepositSchema(); - this.pendingPartialWithdrawalSchema = - new PendingPartialWithdrawal.PendingPartialWithdrawalSchema(); - this.pendingConsolidationSchema = new PendingConsolidation.PendingConsolidationSchema(); + new ExecutionPayloadAndBlobsBundleSchema(executionPayloadSchemaEip7594, blobsBundleSchema); this.cellSchema = new CellSchema(specConfig); this.dataColumnSchema = new DataColumnSchema(specConfig); @@ -160,17 +136,17 @@ public SchemaDefinitionsElectra(final SpecConfigElectra specConfig) { new DataColumnSidecarsByRootRequestMessageSchema(specConfig); } - public static SchemaDefinitionsElectra required(final SchemaDefinitions schemaDefinitions) { + public static SchemaDefinitionsEip7594 required(final SchemaDefinitions schemaDefinitions) { checkArgument( - schemaDefinitions instanceof SchemaDefinitionsElectra, + schemaDefinitions instanceof SchemaDefinitionsEip7594, "Expected definitions of type %s by got %s", - SchemaDefinitionsElectra.class, + SchemaDefinitionsEip7594.class, schemaDefinitions.getClass()); - return (SchemaDefinitionsElectra) schemaDefinitions; + return (SchemaDefinitionsEip7594) schemaDefinitions; } @Override - public BeaconStateSchema + public BeaconStateSchema getBeaconStateSchema() { return beaconStateSchema; } @@ -227,22 +203,22 @@ public SignedBlockContainerSchema getSignedBlindedBlockCon @Override public ExecutionPayloadSchema getExecutionPayloadSchema() { - return executionPayloadSchemaElectra; + return executionPayloadSchemaEip7594; } @Override public ExecutionPayloadHeaderSchema getExecutionPayloadHeaderSchema() { - return executionPayloadHeaderSchemaElectra; + return executionPayloadHeaderSchemaEip7594; } @Override public BuilderBidSchema getBuilderBidSchema() { - return builderBidSchemaElectra; + return builderBidSchemaEip7594; } @Override public SignedBuilderBidSchema getSignedBuilderBidSchema() { - return signedBuilderBidSchemaElectra; + return signedBuilderBidSchemaEip7594; } @Override @@ -252,7 +228,7 @@ public BuilderPayloadSchema getBuilderPayloadSchema() { @Override public BeaconBlockBodyBuilder createBeaconBlockBodyBuilder() { - return new BeaconBlockBodyBuilderElectra(beaconBlockBodySchema, blindedBeaconBlockBodySchema); + return new BeaconBlockBodyBuilderEip7594(beaconBlockBodySchema, blindedBeaconBlockBodySchema); } @Override @@ -275,32 +251,11 @@ public ExecutionPayloadAndBlobsBundleSchema getExecutionPayloadAndBlobsBundleSch return executionPayloadAndBlobsBundleSchema; } - public DepositReceiptSchema getDepositReceiptSchema() { - return depositReceiptSchema; - } - - public ExecutionLayerExitSchema getExecutionLayerExitSchema() { - return executionLayerExitSchema; - } - - public PendingBalanceDeposit.PendingBalanceDepositSchema getPendingBalanceDepositSchema() { - return pendingBalanceDepositSchema; - } - - public PendingPartialWithdrawal.PendingPartialWithdrawalSchema - getPendingPartialWithdrawalSchema() { - return pendingPartialWithdrawalSchema; - } - @Override - public Optional toVersionElectra() { + public Optional toVersionEip7594() { return Optional.of(this); } - public PendingConsolidation.PendingConsolidationSchema getPendingConsolidationSchema() { - return pendingConsolidationSchema; - } - public DataColumnSidecarSchema getDataColumnSidecarSchema() { return dataColumnSidecarSchema; } diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/holesky.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/holesky.yaml index 998bde00999..eb7e843eddd 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/holesky.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/holesky.yaml @@ -37,9 +37,9 @@ CAPELLA_FORK_EPOCH: 256 DENEB_FORK_VERSION: 0x05017000 DENEB_FORK_EPOCH: 29696 -# Electra -ELECTRA_FORK_VERSION: 0x06017000 -ELECTRA_FORK_EPOCH: 18446744073709551615 +## Electra +#ELECTRA_FORK_VERSION: 0x06017000 +#ELECTRA_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/less-swift.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/less-swift.yaml index 3eabebee76f..9bd617c0a80 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/less-swift.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/less-swift.yaml @@ -35,9 +35,9 @@ CAPELLA_FORK_EPOCH: 18446744073709551615 # Deneb DENEB_FORK_VERSION: 0x04000001 DENEB_FORK_EPOCH: 18446744073709551615 -# Electra -ELECTRA_FORK_VERSION: 0x05000001 -ELECTRA_FORK_EPOCH: 18446744073709551615 +## Electra +#ELECTRA_FORK_VERSION: 0x05000001 +#ELECTRA_FORK_EPOCH: 18446744073709551615 # Transition # --------------------------------------------------------------- diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index afa67a490c5..84ed7d3b279 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -50,10 +50,12 @@ CAPELLA_FORK_EPOCH: 194048 # April 12, 2023, 10:27:35pm UTC # Deneb DENEB_FORK_VERSION: 0x04000000 DENEB_FORK_EPOCH: 269568 # March 13, 2024, 01:55:35pm UTC -# Electra -ELECTRA_FORK_VERSION: 0x05000000 -ELECTRA_FORK_EPOCH: 18446744073709551615 - +## Electra +#ELECTRA_FORK_VERSION: 0x05000000 +#ELECTRA_FORK_EPOCH: 18446744073709551615 +# EIP7594 +EIP7594_FORK_VERSION: 0x06000000 +EIP7594_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- @@ -143,10 +145,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 -# [New in Electra:EIP7251] -MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000 # 2**7 * 10**9 (= 128,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) - -# [New in Electra:EIP7594] +# [New in EIP7594] DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index f276d2637a1..caba7a8fd6f 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -49,9 +49,12 @@ CAPELLA_FORK_EPOCH: 18446744073709551615 # Deneb DENEB_FORK_VERSION: 0x04000001 DENEB_FORK_EPOCH: 18446744073709551615 -# Electra -ELECTRA_FORK_VERSION: 0x05000001 -ELECTRA_FORK_EPOCH: 18446744073709551615 +## Electra +#ELECTRA_FORK_VERSION: 0x05000001 +#ELECTRA_FORK_EPOCH: 18446744073709551615 +# EIP7594 +EIP7594_FORK_VERSION: 0x06000001 +EIP7594_FORK_EPOCH: 18446744073709551615 # Time parameters @@ -143,10 +146,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` BLOB_SIDECAR_SUBNET_COUNT: 6 -# [New in Electra:EIP7251] -MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000 # 2**6 * 10**9 (= 64,000,000,000) -MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 256000000000 # 2**8 * 10**9 (= 256,000,000,000) - -# [New in Electra:EIP7594] +# [New in EIP7594] DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/sepolia.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/sepolia.yaml index 8b6bfc6f2d4..06c2c072ce4 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/sepolia.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/sepolia.yaml @@ -36,9 +36,9 @@ CAPELLA_FORK_EPOCH: 56832 DENEB_FORK_VERSION: 0x90000073 DENEB_FORK_EPOCH: 132608 -# Electra -ELECTRA_FORK_VERSION: 0x90000074 -ELECTRA_FORK_EPOCH: 18446744073709551615 +## Electra +#ELECTRA_FORK_VERSION: 0x90000074 +#ELECTRA_FORK_EPOCH: 18446744073709551615 # Time parameters # --------------------------------------------------------------- diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml index 528e58de02b..bdcdd82b52b 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml @@ -43,9 +43,12 @@ CAPELLA_FORK_EPOCH: 18446744073709551615 # Deneb DENEB_FORK_VERSION: 0x04000001 DENEB_FORK_EPOCH: 18446744073709551615 -# Electra -ELECTRA_FORK_VERSION: 0x05000001 -ELECTRA_FORK_EPOCH: 18446744073709551615 +## Electra +#ELECTRA_FORK_VERSION: 0x05000001 +#ELECTRA_FORK_EPOCH: 18446744073709551615 +# EIP7594 +EIP7594_FORK_VERSION: 0x06000001 +EIP7594_FORK_EPOCH: 18446744073709551615 # Time parameters @@ -136,4 +139,8 @@ MAX_REQUEST_BLOB_SIDECARS: 768 # `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 # `6` -BLOB_SIDECAR_SUBNET_COUNT: 6 \ No newline at end of file +BLOB_SIDECAR_SUBNET_COUNT: 6 + +# [New in EIP7594] +DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7594.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7594.yaml new file mode 100644 index 00000000000..84147dfa86e --- /dev/null +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/eip7594.yaml @@ -0,0 +1,14 @@ +# Mainnet preset - EIP7594 + +# Misc +# --------------------------------------------------------------- +# `uint64(2**6)` (= 64) +FIELD_ELEMENTS_PER_CELL: 64 +# `uint64(2 * 4096)` (= 8192) +FIELD_ELEMENTS_PER_EXT_BLOB: 8192 +# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) +KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4 + +# Not yet in official spec +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml deleted file mode 100644 index 8383ed55fa8..00000000000 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/mainnet/electra.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# Mainnet preset - Electra - -# Gwei values -# --------------------------------------------------------------- -# 2**5 * 10**9 (= 32,000,000,000) Gwei -MIN_ACTIVATION_BALANCE: 32000000000 -# 2**11 * 10**9 (= 2,048,000,000,000) Gwei -MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 - -# State list lengths -# --------------------------------------------------------------- -PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 -PENDING_PARTIAL_WITHDRAWALS_LIMIT: 134217728 -PENDING_CONSOLIDATIONS_LIMIT: 262144 - -# Reward and penalty quotients -# --------------------------------------------------------------- -MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 -WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 - -# # Max operations per block -# --------------------------------------------------------------- -# `uint64(2**0)` (= 1) -MAX_ATTESTER_SLASHINGS_ELECTRA: 1 -# `uint64(2 * 3)` (= 8) -MAX_ATTESTATIONS_ELECTRA: 8 -MAX_CONSOLIDATIONS: 1 - -# Execution -# --------------------------------------------------------------- -# 2**13 (= 8192) receipts -MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 8192 -# 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 -# 2**3 (= 8) partial withdrawals -MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 8 - -# DAS -# --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 -MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7594.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7594.yaml new file mode 100644 index 00000000000..5375752b60e --- /dev/null +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/eip7594.yaml @@ -0,0 +1,14 @@ +# Minimal preset - EIP7594 + +# Misc +# --------------------------------------------------------------- +# `uint64(2**6)` (= 64) +FIELD_ELEMENTS_PER_CELL: 64 +# `uint64(2 * 4096)` (= 8192) +FIELD_ELEMENTS_PER_EXT_BLOB: 8192 +# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) +KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4 + +# Not yet in official spec +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml deleted file mode 100644 index 5ecb8da4923..00000000000 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/minimal/electra.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# Minimal preset - Electra - -# Gwei values -# --------------------------------------------------------------- -# 2**5 * 10**9 (= 32,000,000,000) Gwei -MIN_ACTIVATION_BALANCE: 32000000000 -# 2**11 * 10**9 (= 2,048,000,000,000) Gwei -MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 - -# State list lengths -# --------------------------------------------------------------- -PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 -# [customized] smaller queue -PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 -# [customized] smaller queue -PENDING_CONSOLIDATIONS_LIMIT: 64 - -# Reward and penalty quotients -# --------------------------------------------------------------- -MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 -WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 - -# # Max operations per block -# --------------------------------------------------------------- -# `uint64(2**0)` (= 1) -MAX_ATTESTER_SLASHINGS_ELECTRA: 1 -# `uint64(2 * 3)` (= 8) -MAX_ATTESTATIONS_ELECTRA: 8 -MAX_CONSOLIDATIONS: 1 - -# Execution -# --------------------------------------------------------------- -# [customized] -MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 -# 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 -# [customized] 2**1 (= 2) -MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 - -# DAS -# --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 -MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/eip7594.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/eip7594.yaml new file mode 100644 index 00000000000..5375752b60e --- /dev/null +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/eip7594.yaml @@ -0,0 +1,14 @@ +# Minimal preset - EIP7594 + +# Misc +# --------------------------------------------------------------- +# `uint64(2**6)` (= 64) +FIELD_ELEMENTS_PER_CELL: 64 +# `uint64(2 * 4096)` (= 8192) +FIELD_ELEMENTS_PER_EXT_BLOB: 8192 +# uint64(floorlog2(get_generalized_index(BeaconBlockBody, 'blob_kzg_commitments')) +KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH: 4 + +# Not yet in official spec +CUSTODY_REQUIREMENT: 1 +MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml deleted file mode 100644 index 5ecb8da4923..00000000000 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/presets/swift/electra.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# Minimal preset - Electra - -# Gwei values -# --------------------------------------------------------------- -# 2**5 * 10**9 (= 32,000,000,000) Gwei -MIN_ACTIVATION_BALANCE: 32000000000 -# 2**11 * 10**9 (= 2,048,000,000,000) Gwei -MAX_EFFECTIVE_BALANCE_ELECTRA: 2048000000000 - -# State list lengths -# --------------------------------------------------------------- -PENDING_BALANCE_DEPOSITS_LIMIT: 134217728 -# [customized] smaller queue -PENDING_PARTIAL_WITHDRAWALS_LIMIT: 64 -# [customized] smaller queue -PENDING_CONSOLIDATIONS_LIMIT: 64 - -# Reward and penalty quotients -# --------------------------------------------------------------- -MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA: 4096 -WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA: 4096 - -# # Max operations per block -# --------------------------------------------------------------- -# `uint64(2**0)` (= 1) -MAX_ATTESTER_SLASHINGS_ELECTRA: 1 -# `uint64(2 * 3)` (= 8) -MAX_ATTESTATIONS_ELECTRA: 8 -MAX_CONSOLIDATIONS: 1 - -# Execution -# --------------------------------------------------------------- -# [customized] -MAX_DEPOSIT_RECEIPTS_PER_PAYLOAD: 4 -# 2**4 (= 16) exits -MAX_EXECUTION_LAYER_EXITS: 16 -# [customized] 2**1 (= 2) -MAX_PARTIAL_WITHDRAWALS_PER_PAYLOAD: 2 - -# DAS -# --------------------------------------------------------------- -FIELD_ELEMENTS_PER_CELL: 64 -CUSTODY_REQUIREMENT: 1 -MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS: 4096 \ No newline at end of file diff --git a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptPropertyTest.java b/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptPropertyTest.java deleted file mode 100644 index 0e6c0670a57..00000000000 --- a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/DepositReceiptPropertyTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import static tech.pegasys.teku.spec.propertytest.util.PropertyTestHelper.assertDeserializeMutatedThrowsExpected; -import static tech.pegasys.teku.spec.propertytest.util.PropertyTestHelper.assertRoundTrip; - -import com.fasterxml.jackson.core.JsonProcessingException; -import net.jqwik.api.ForAll; -import net.jqwik.api.Property; -import tech.pegasys.teku.spec.propertytest.suppliers.execution.versions.electra.DepositReceiptSupplier; - -public class DepositReceiptPropertyTest { - - @Property - void roundTrip( - @ForAll(supplier = DepositReceiptSupplier.class) final DepositReceipt depositReceipt) - throws JsonProcessingException { - assertRoundTrip(depositReceipt); - } - - @Property - void deserializeMutated( - @ForAll(supplier = DepositReceiptSupplier.class) final DepositReceipt depositReceipt, - @ForAll final int seed) { - assertDeserializeMutatedThrowsExpected(depositReceipt, seed); - } -} diff --git a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitPropertyTest.java b/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitPropertyTest.java deleted file mode 100644 index cda7cc5a850..00000000000 --- a/ethereum/spec/src/property-test/java/tech/pegasys/teku/spec/datastructures/execution/versions/electra/ExecutionLayerExitPropertyTest.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.execution.versions.electra; - -import static tech.pegasys.teku.spec.propertytest.util.PropertyTestHelper.assertDeserializeMutatedThrowsExpected; -import static tech.pegasys.teku.spec.propertytest.util.PropertyTestHelper.assertRoundTrip; - -import com.fasterxml.jackson.core.JsonProcessingException; -import net.jqwik.api.ForAll; -import net.jqwik.api.Property; -import tech.pegasys.teku.spec.propertytest.suppliers.execution.versions.electra.ExecutionLayerExitSupplier; - -public class ExecutionLayerExitPropertyTest { - - @Property - void roundTrip( - @ForAll(supplier = ExecutionLayerExitSupplier.class) - final ExecutionLayerExit executionLayerExit) - throws JsonProcessingException { - assertRoundTrip(executionLayerExit); - } - - @Property - void deserializeMutated( - @ForAll(supplier = ExecutionLayerExitSupplier.class) - final ExecutionLayerExit executionLayerExit, - @ForAll final int seed) { - assertDeserializeMutatedThrowsExpected(executionLayerExit, seed); - } -} diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java index 0fb4a59ea80..f49cedeff4c 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecMilestoneTest.java @@ -24,13 +24,13 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.networks.Eth2Network; public class SpecMilestoneTest { - private final SpecConfigElectra electraSpecConfig = - SpecConfigElectra.required(SpecConfigLoader.loadConfig(Eth2Network.MINIMAL.configName())); + private final SpecConfigEip7594 eip7594SpecConfig = + SpecConfigEip7594.required(SpecConfigLoader.loadConfig(Eth2Network.MINIMAL.configName())); private final SpecConfigDeneb denebSpecConfig = SpecConfigDeneb.required(SpecConfigLoader.loadConfig(Eth2Network.MINIMAL.configName())); private final SpecConfigCapella capellaSpecConfig = @@ -61,11 +61,11 @@ public void isGreaterThanOrEqualTo() { assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.BELLATRIX)).isTrue(); assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isTrue(); - assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)).isFalse(); + assertThat(SpecMilestone.DENEB.isGreaterThanOrEqualTo(SpecMilestone.EIP7594)).isFalse(); - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isTrue(); - assertThat(SpecMilestone.ELECTRA.isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)).isTrue(); + assertThat(SpecMilestone.EIP7594.isGreaterThanOrEqualTo(SpecMilestone.CAPELLA)).isTrue(); + assertThat(SpecMilestone.EIP7594.isGreaterThanOrEqualTo(SpecMilestone.DENEB)).isTrue(); + assertThat(SpecMilestone.EIP7594.isGreaterThanOrEqualTo(SpecMilestone.EIP7594)).isTrue(); } @Test @@ -75,7 +75,7 @@ public void getPreviousMilestone() { assertThat(SpecMilestone.BELLATRIX.getPreviousMilestone()).isEqualTo(SpecMilestone.ALTAIR); assertThat(SpecMilestone.CAPELLA.getPreviousMilestone()).isEqualTo(SpecMilestone.BELLATRIX); assertThat(SpecMilestone.DENEB.getPreviousMilestone()).isEqualTo(SpecMilestone.CAPELLA); - assertThat(SpecMilestone.ELECTRA.getPreviousMilestone()).isEqualTo(SpecMilestone.DENEB); + assertThat(SpecMilestone.EIP7594.getPreviousMilestone()).isEqualTo(SpecMilestone.DENEB); } @Test @@ -112,8 +112,8 @@ public void getAllPriorMilestones_deneb() { } @Test - public void getAllPriorMilestones_electra() { - assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.ELECTRA)) + public void getAllPriorMilestones_eip7594() { + assertThat(SpecMilestone.getAllPriorMilestones(SpecMilestone.EIP7594)) .contains( SpecMilestone.PHASE0, SpecMilestone.ALTAIR, @@ -157,8 +157,8 @@ public void getMilestonesUpTo_deneb() { } @Test - public void getMilestonesUpTo_electra() { - assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.ELECTRA)) + public void getMilestonesUpTo_eip7594() { + assertThat(SpecMilestone.getMilestonesUpTo(SpecMilestone.EIP7594)) .contains( SpecMilestone.PHASE0, SpecMilestone.ALTAIR, @@ -195,9 +195,9 @@ public void areMilestonesInOrder() { .isTrue(); assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.DENEB, SpecMilestone.CAPELLA)) .isFalse(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.DENEB, SpecMilestone.ELECTRA)) + assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.DENEB, SpecMilestone.EIP7594)) .isTrue(); - assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.ELECTRA, SpecMilestone.DENEB)) + assertThat(SpecMilestone.areMilestonesInOrder(SpecMilestone.EIP7594, SpecMilestone.DENEB)) .isFalse(); } @@ -237,9 +237,9 @@ public void getForkVersion_deneb() { } @Test - public void getForkVersion_electra() { - final Bytes4 expected = electraSpecConfig.getElectraForkVersion(); - assertThat(SpecMilestone.getForkVersion(electraSpecConfig, SpecMilestone.ELECTRA)) + public void getForkVersion_eip7594() { + final Bytes4 expected = eip7594SpecConfig.getEip7594ForkVersion(); + assertThat(SpecMilestone.getForkVersion(eip7594SpecConfig, SpecMilestone.EIP7594)) .contains(expected); } @@ -278,9 +278,9 @@ public void getForkEpoch_deneb() { } @Test - public void getForkEpoch_electra() { - final UInt64 expected = electraSpecConfig.getElectraForkEpoch(); - assertThat(SpecMilestone.getForkEpoch(electraSpecConfig, SpecMilestone.ELECTRA)) + public void getForkEpoch_eip7594() { + final UInt64 expected = eip7594SpecConfig.getEip7594ForkEpoch(); + assertThat(SpecMilestone.getForkEpoch(eip7594SpecConfig, SpecMilestone.EIP7594)) .contains(expected); } @@ -309,8 +309,8 @@ public void getForkEpoch_denebNotScheduled() { } @Test - public void getForkEpoch_electraNotScheduled() { - assertThat(SpecMilestone.getForkEpoch(denebSpecConfig, SpecMilestone.ELECTRA)) + public void getForkEpoch_eip7594NotScheduled() { + assertThat(SpecMilestone.getForkEpoch(denebSpecConfig, SpecMilestone.EIP7594)) .contains(UInt64.MAX_VALUE); } } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecVersionTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecVersionTest.java index 1794982f102..c6bac77b5a7 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecVersionTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/SpecVersionTest.java @@ -21,7 +21,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.networks.Eth2Network; @@ -89,13 +89,13 @@ void shouldCreateDenebSpec() { } @Test - void shouldCreateElectraSpec() { - final SpecConfigElectra electraSpecConfig = SpecConfigElectra.required(minimalConfig); - final SpecVersion expectedVersion = SpecVersion.createElectra(electraSpecConfig); + void shouldCreateEip7594Spec() { + final SpecConfigEip7594 eip7594SpecConfig = SpecConfigEip7594.required(minimalConfig); + final SpecVersion expectedVersion = SpecVersion.createEip7594(eip7594SpecConfig); final Optional actualVersion = - SpecVersion.create(SpecMilestone.ELECTRA, minimalConfig); + SpecVersion.create(SpecMilestone.EIP7594, minimalConfig); assertThat(actualVersion).isPresent(); - assertThat(actualVersion.get().getMilestone()).isEqualTo(SpecMilestone.ELECTRA); + assertThat(actualVersion.get().getMilestone()).isEqualTo(SpecMilestone.EIP7594); assertThat(actualVersion.get().getSchemaDefinitions()) .hasSameClassAs(expectedVersion.getSchemaDefinitions()); } diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigBuilderTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigBuilderTest.java index 27a916bea68..e20182159a4 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigBuilderTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigBuilderTest.java @@ -32,7 +32,7 @@ import tech.pegasys.teku.spec.config.builder.BellatrixBuilder; import tech.pegasys.teku.spec.config.builder.CapellaBuilder; import tech.pegasys.teku.spec.config.builder.DenebBuilder; -import tech.pegasys.teku.spec.config.builder.ElectraBuilder; +import tech.pegasys.teku.spec.config.builder.Eip7594Builder; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; import tech.pegasys.teku.spec.util.DataStructureUtil; @@ -47,7 +47,7 @@ class SpecConfigBuilderTest { BellatrixBuilder.class, CapellaBuilder.class, DenebBuilder.class, - ElectraBuilder.class); + Eip7594Builder.class); /** * Ensures Builders have actually non-primitive setters, because primitive setters are silently diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java similarity index 65% rename from ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java rename to ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java index e2f356dad41..c155d9b906a 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigElectraTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java @@ -20,15 +20,15 @@ import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.util.DataStructureUtil; -public class SpecConfigElectraTest { +public class SpecConfigEip7594Test { private final Spec spec = TestSpecFactory.createMinimalDeneb(); @Test public void equals_mainnet() { - final SpecConfigElectra configA = - SpecConfigLoader.loadConfig("mainnet").toVersionElectra().orElseThrow(); - final SpecConfigElectra configB = - SpecConfigLoader.loadConfig("mainnet").toVersionElectra().orElseThrow(); + final SpecConfigEip7594 configA = + SpecConfigLoader.loadConfig("mainnet").toVersionEip7594().orElseThrow(); + final SpecConfigEip7594 configB = + SpecConfigLoader.loadConfig("mainnet").toVersionEip7594().orElseThrow(); assertThat(configA).isEqualTo(configB); assertThat(configA.hashCode()).isEqualTo(configB.hashCode()); @@ -38,8 +38,8 @@ public void equals_mainnet() { public void equals_sameRandomValues() { final SpecConfigDeneb specConfigDeneb = SpecConfigLoader.loadConfig("mainnet").toVersionDeneb().orElseThrow(); - final SpecConfigElectra configA = createRandomElectraConfig(specConfigDeneb, 1); - final SpecConfigElectra configB = createRandomElectraConfig(specConfigDeneb, 1); + final SpecConfigEip7594 configA = createRandomEip7594Config(specConfigDeneb, 1); + final SpecConfigEip7594 configB = createRandomEip7594Config(specConfigDeneb, 1); assertThat(configA).isEqualTo(configB); assertThat(configA.hashCode()).isEqualTo(configB.hashCode()); @@ -49,8 +49,8 @@ public void equals_sameRandomValues() { public void equals_differentRandomValues() { final SpecConfigDeneb specConfigDeneb = SpecConfigLoader.loadConfig("mainnet").toVersionDeneb().orElseThrow(); - final SpecConfigElectra configA = createRandomElectraConfig(specConfigDeneb, 1); - final SpecConfigElectra configB = createRandomElectraConfig(specConfigDeneb, 2); + final SpecConfigEip7594 configA = createRandomEip7594Config(specConfigDeneb, 1); + final SpecConfigEip7594 configB = createRandomEip7594Config(specConfigDeneb, 2); assertThat(configA).isNotEqualTo(configB); assertThat(configA.hashCode()).isNotEqualTo(configB.hashCode()); @@ -67,37 +67,24 @@ public void equals_denebConfigDiffer() { .toVersionDeneb() .orElseThrow(); - final SpecConfigElectra configA = createRandomElectraConfig(denebA, 1); - final SpecConfigElectra configB = createRandomElectraConfig(denebB, 1); + final SpecConfigEip7594 configA = createRandomEip7594Config(denebA, 1); + final SpecConfigEip7594 configB = createRandomEip7594Config(denebB, 1); assertThat(configA).isNotEqualTo(configB); assertThat(configA.hashCode()).isNotEqualTo(configB.hashCode()); } - private SpecConfigElectra createRandomElectraConfig( + private SpecConfigEip7594 createRandomEip7594Config( final SpecConfigDeneb denebConfig, final int seed) { final DataStructureUtil dataStructureUtil = new DataStructureUtil(seed, spec); - return new SpecConfigElectraImpl( + return new SpecConfigEip7594Impl( denebConfig, dataStructureUtil.randomBytes4(), dataStructureUtil.randomUInt64(999_999), - dataStructureUtil.randomPositiveInt(16), - dataStructureUtil.randomPositiveInt(16), - dataStructureUtil.randomUInt64(128000000000L), - dataStructureUtil.randomUInt64(256000000000L), - dataStructureUtil.randomUInt64(32000000000L), - dataStructureUtil.randomUInt64(2048000000000L), - dataStructureUtil.randomPositiveInt(134217728), - dataStructureUtil.randomPositiveInt(134217728), - dataStructureUtil.randomPositiveInt(262144), - dataStructureUtil.randomPositiveInt(4096), - dataStructureUtil.randomPositiveInt(4096), - dataStructureUtil.randomPositiveInt(8), - dataStructureUtil.randomPositiveInt(8), - dataStructureUtil.randomPositiveInt(8), - dataStructureUtil.randomPositiveInt(8), dataStructureUtil.randomUInt64(64), + dataStructureUtil.randomUInt64(8192), + dataStructureUtil.randomUInt64(10), dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(4096), diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigLoaderTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigLoaderTest.java index 95a68ab6fc8..87168886ea5 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigLoaderTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigLoaderTest.java @@ -43,7 +43,7 @@ public class SpecConfigLoaderTest { public void shouldLoadAllKnownNetworks(final Eth2Network network) throws Exception { final SpecConfig config = SpecConfigLoader.loadConfigStrict(network.configName()); // testing latest SpecConfig ensures all fields will be asserted on - assertAllFieldsSet(config, SpecConfigElectra.class); + assertAllFieldsSet(config, SpecConfigEip7594.class); } /** diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/ExecutionLayerExitTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/ExecutionLayerExitTest.java deleted file mode 100644 index 012f30defa9..00000000000 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/datastructures/operations/ExecutionLayerExitTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2022 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.datastructures.operations; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.apache.tuweni.bytes.Bytes; -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.bls.BLSPublicKey; -import tech.pegasys.teku.infrastructure.bytes.Bytes20; -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExitSchema; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -class ExecutionLayerExitTest { - private final DataStructureUtil dataStructureUtil = - new DataStructureUtil(TestSpecFactory.createMinimal(SpecMilestone.ELECTRA)); - private final ExecutionLayerExitSchema executionLayerExitSchema = new ExecutionLayerExitSchema(); - private final Bytes20 sourceAddress = dataStructureUtil.randomBytes20(); - private final BLSPublicKey validatorPublicKey = dataStructureUtil.randomPublicKey(); - - @Test - public void objectEquality() { - final ExecutionLayerExit executionLayerExit1 = - executionLayerExitSchema.create(sourceAddress, validatorPublicKey); - final ExecutionLayerExit executionLayerExit2 = - executionLayerExitSchema.create(sourceAddress, validatorPublicKey); - - assertThat(executionLayerExit1).isEqualTo(executionLayerExit2); - } - - @Test - public void objectAccessorMethods() { - final ExecutionLayerExit executionLayerExit = - executionLayerExitSchema.create(sourceAddress, validatorPublicKey); - - assertThat(executionLayerExit.getSourceAddress()).isEqualTo(sourceAddress); - assertThat(executionLayerExit.getValidatorPublicKey()).isEqualTo(validatorPublicKey); - } - - @Test - public void roundTripSSZ() { - final ExecutionLayerExit executionLayerExit = - executionLayerExitSchema.create(sourceAddress, validatorPublicKey); - - final Bytes sszBytes = executionLayerExit.sszSerialize(); - final ExecutionLayerExit deserializedObject = executionLayerExitSchema.sszDeserialize(sszBytes); - - assertThat(executionLayerExit).isEqualTo(deserializedObject); - } -} diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java index 9b96db7da31..d652fe3c9ab 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/StateUpgradeTransitionTest.java @@ -38,7 +38,7 @@ import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.bellatrix.BeaconStateBellatrix; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.capella.BeaconStateCapella; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.deneb.BeaconStateDeneb; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.phase0.BeaconStatePhase0; import tech.pegasys.teku.spec.datastructures.util.DepositGenerator; @@ -48,7 +48,7 @@ SpecMilestone.BELLATRIX, SpecMilestone.CAPELLA, SpecMilestone.DENEB, - SpecMilestone.ELECTRA + SpecMilestone.EIP7594 }, doNotGenerateSpec = true) public class StateUpgradeTransitionTest { @@ -89,10 +89,10 @@ public void setup(SpecContext specContext) { afterBeaconStateClass = BeaconStateDeneb.class; yield TestSpecFactory.createMinimalWithDenebForkEpoch(milestoneTransitionEpoch); } - case ELECTRA -> { + case EIP7594 -> { beforeBeaconStateClass = BeaconStateDeneb.class; - afterBeaconStateClass = BeaconStateElectra.class; - yield TestSpecFactory.createMinimalWithElectraForkEpoch(milestoneTransitionEpoch); + afterBeaconStateClass = BeaconStateEip7594.class; + yield TestSpecFactory.createMinimalWithEip7594ForkEpoch(milestoneTransitionEpoch); } }; diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpersTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpersTest.java index 66bce5ee02a..65ac51aa561 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpersTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpersTest.java @@ -34,10 +34,8 @@ import org.junit.jupiter.params.provider.MethodSource; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.config.SpecConfig; -import tech.pegasys.teku.spec.networks.Eth2Network; import tech.pegasys.teku.spec.util.DataStructureUtil; class MiscHelpersTest { @@ -233,20 +231,6 @@ public void committeeComputationShouldNotOverflow(int activeValidatorsCount, int }); } - @Test - public void isFormerDepositReceiptMechanismDisabled_returnsFalseForAllForksPriorToElectra() { - SpecMilestone.getAllPriorMilestones(SpecMilestone.ELECTRA) - .forEach( - milestone -> { - final Spec spec = TestSpecFactory.create(milestone, Eth2Network.MINIMAL); - final MiscHelpers miscHelpers = spec.atSlot(UInt64.ZERO).miscHelpers(); - assertThat( - miscHelpers.isFormerDepositMechanismDisabled( - dataStructureUtil.randomBeaconState())) - .isFalse(); - }); - } - public static Stream getComputesSlotAtTimeArguments() { // 6 seconds per slot return Stream.of( diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/util/BlindBlockUtilTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/util/BlindBlockUtilTest.java index f2562b6a839..d9c61f8f9d8 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/util/BlindBlockUtilTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/common/util/BlindBlockUtilTest.java @@ -31,7 +31,7 @@ SpecMilestone.BELLATRIX, SpecMilestone.CAPELLA, SpecMilestone.DENEB, - SpecMilestone.ELECTRA + SpecMilestone.EIP7594 }) class BlindBlockUtilTest { diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java deleted file mode 100644 index e99b2fa8b03..00000000000 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/block/BlockProcessorElectraTest.java +++ /dev/null @@ -1,450 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.logic.versions.electra.block; - -import static org.assertj.core.api.Assertions.assertThat; -import static tech.pegasys.teku.spec.config.SpecConfig.FAR_FUTURE_EPOCH; - -import java.util.List; -import java.util.Optional; -import java.util.function.Supplier; -import java.util.stream.IntStream; -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.infrastructure.ssz.SszList; -import tech.pegasys.teku.infrastructure.ssz.SszMutableList; -import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blocks.Eth1Data; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; -import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayload; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.datastructures.state.Validator; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.logic.common.helpers.BeaconStateMutators.ValidatorExitContext; -import tech.pegasys.teku.spec.logic.common.statetransition.exceptions.BlockProcessingException; -import tech.pegasys.teku.spec.logic.versions.deneb.block.BlockProcessorDenebTest; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; - -class BlockProcessorElectraTest extends BlockProcessorDenebTest { - - @Override - protected Spec createSpec() { - return TestSpecFactory.createMainnetElectra(); - } - - @Test - public void verifiesOutstandingEth1DepositsAreProcessed() { - final BeaconState state = - createBeaconState() - .updated( - mutableState -> { - final UInt64 eth1DepositCount = UInt64.valueOf(25); - mutableState.setEth1Data( - new Eth1Data( - dataStructureUtil.randomBytes32(), - eth1DepositCount, - dataStructureUtil.randomBytes32())); - final UInt64 eth1DepositIndex = UInt64.valueOf(13); - mutableState.setEth1DepositIndex(eth1DepositIndex); - final UInt64 depositReceiptsStartIndex = UInt64.valueOf(20); - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex(depositReceiptsStartIndex); - }); - - final BeaconBlockBody body = - dataStructureUtil.randomBeaconBlockBody( - // 20 - 13 = 7 - builder -> builder.deposits(dataStructureUtil.randomSszDeposits(7))); - - getBlockProcessor(state).verifyOutstandingDepositsAreProcessed(state, body); - } - - @Test - public void - verifiesNoOutstandingEth1DepositsAreProcessedWhenFormerDepositMechanismHasBeenDisabled() { - final BeaconState state = - createBeaconState() - .updated( - mutableState -> { - final UInt64 eth1DepositCount = UInt64.valueOf(25); - mutableState.setEth1Data( - new Eth1Data( - dataStructureUtil.randomBytes32(), - eth1DepositCount, - dataStructureUtil.randomBytes32())); - final UInt64 eth1DepositIndex = UInt64.valueOf(20); - mutableState.setEth1DepositIndex(eth1DepositIndex); - final UInt64 depositReceiptsStartIndex = UInt64.valueOf(20); - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex(depositReceiptsStartIndex); - }); - - final BeaconBlockBody body = - dataStructureUtil.randomBeaconBlockBody( - // 20 - 20 = 0 - modifier -> modifier.deposits(dataStructureUtil.randomSszDeposits(0))); - - getBlockProcessor(state).verifyOutstandingDepositsAreProcessed(state, body); - } - - @Test - public void processesDepositReceipts() throws BlockProcessingException { - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex( - SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX))); - final int firstElectraDepositReceiptIndex = preState.getValidators().size(); - - final SszListSchema> depositReceiptsSchema = - SchemaDefinitionsElectra.required(spec.getGenesisSchemaDefinitions()) - .getExecutionPayloadSchema() - .getDepositReceiptsSchemaRequired(); - final int depositReceiptsCount = 3; - final List depositReceipts = - IntStream.range(0, depositReceiptsCount) - .mapToObj( - i -> - dataStructureUtil.randomDepositReceiptWithValidSignature( - UInt64.valueOf(firstElectraDepositReceiptIndex + i))) - .toList(); - - final BeaconStateElectra state = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processDepositReceipts( - MutableBeaconStateElectra.required(mutableState), - depositReceiptsSchema.createFromElements(depositReceipts)))); - - // verify deposit_receipts_start_index has been set - assertThat(state.getDepositReceiptsStartIndex()) - .isEqualTo(UInt64.valueOf(firstElectraDepositReceiptIndex)); - // verify validators have been added to the state - assertThat(state.getValidators().size()) - .isEqualTo(firstElectraDepositReceiptIndex + depositReceiptsCount); - } - - @Test - public void processExecutionLayerExits_WithEmptyExitsList_DoesNothing() - throws BlockProcessingException { - final BeaconStateElectra preState = BeaconStateElectra.required(createBeaconState()); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits(preState.getSlot(), List.of()); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForAbsentValidator_DoesNothing() - throws BlockProcessingException { - final BeaconStateElectra preState = BeaconStateElectra.required(createBeaconState()); - final ExecutionLayerExit executionLayerExit = dataStructureUtil.randomExecutionLayerExit(); - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits(preState.getSlot(), List.of(executionLayerExit)); - - // Assert the exit does not correspond to an existing validator - assertThat( - preState.getValidators().stream() - .filter( - validator -> - validator - .getPublicKey() - .equals(executionLayerExit.getValidatorPublicKey()))) - .isEmpty(); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForValidatorWithoutEth1Credentials_DoesNothing() - throws BlockProcessingException { - final Validator validator = - dataStructureUtil.validatorBuilder().withRandomBlsWithdrawalCredentials().build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - })); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits( - preState.getSlot(), List.of(dataStructureUtil.executionLayerExit(validator))); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitWithWrongSourceAddress_DoesNothing() - throws BlockProcessingException { - final Validator validator = - dataStructureUtil.validatorBuilder().withRandomEth1WithdrawalCredentials().build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - })); - - final ExecutionLayerExit exitWithInvalidSourceAddress = - dataStructureUtil.executionLayerExit( - dataStructureUtil.randomEth1Address(), validator.getPublicKey()); - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits(preState.getSlot(), List.of(exitWithInvalidSourceAddress)); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForInactiveValidator_DoesNothing() - throws BlockProcessingException { - final Validator validator = - dataStructureUtil - .validatorBuilder() - .withRandomEth1WithdrawalCredentials() - .activationEpoch(FAR_FUTURE_EPOCH) - .build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - })); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits( - preState.getSlot(), List.of(dataStructureUtil.executionLayerExit(validator))); - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForValidatorAlreadyExiting_DoesNothing() - throws BlockProcessingException { - final UInt64 currentEpoch = UInt64.valueOf(1_000); - final Validator validator = - dataStructureUtil - .validatorBuilder() - .withRandomEth1WithdrawalCredentials() - .activationEpoch(UInt64.ZERO) - .exitEpoch(UInt64.valueOf(1_001)) - .build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - mutableState.setSlot(spec.computeStartSlotAtEpoch(currentEpoch)); - })); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits( - preState.getSlot(), List.of(dataStructureUtil.executionLayerExit(validator))); - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForValidatorNotActiveLongEnough_DoesNothing() - throws BlockProcessingException { - final UInt64 currentEpoch = UInt64.valueOf(1_000); - final Validator validator = - dataStructureUtil - .validatorBuilder() - .withRandomEth1WithdrawalCredentials() - .activationEpoch(UInt64.valueOf(999)) - .exitEpoch(FAR_FUTURE_EPOCH) - .build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - mutableState.setSlot(spec.computeStartSlotAtEpoch(currentEpoch)); - })); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits( - preState.getSlot(), List.of(dataStructureUtil.executionLayerExit(validator))); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - assertThat(postState.hashTreeRoot()).isEqualTo(preState.hashTreeRoot()); - } - - @Test - public void processExecutionLayerExits_ExitForEligibleValidator() - throws BlockProcessingException { - final UInt64 currentEpoch = UInt64.valueOf(1_000); - final Validator validator = - dataStructureUtil - .validatorBuilder() - .withRandomEth1WithdrawalCredentials() - .activationEpoch(UInt64.ZERO) - .exitEpoch(FAR_FUTURE_EPOCH) - .build(); - - final BeaconStateElectra preState = - BeaconStateElectra.required( - createBeaconState() - .updated( - mutableState -> { - final SszMutableList validators = mutableState.getValidators(); - validators.append(validator); - mutableState.setValidators(validators); - mutableState.setSlot(spec.computeStartSlotAtEpoch(currentEpoch)); - })); - // The validator we created was the last one added to the list of validators - int validatorIndex = preState.getValidators().size() - 1; - - // Before processing the exit, the validator has FAR_FUTURE_EPOCH for exit_epoch and - // withdrawable_epoch - assertThat(preState.getValidators().get(validatorIndex).getExitEpoch()) - .isEqualTo(FAR_FUTURE_EPOCH); - assertThat(preState.getValidators().get(validatorIndex).getWithdrawableEpoch()) - .isEqualTo(FAR_FUTURE_EPOCH); - - final Optional executionPayloadWithExits = - createExecutionPayloadWithExits( - preState.getSlot(), List.of(dataStructureUtil.executionLayerExit(validator))); - - final BeaconStateElectra postState = - BeaconStateElectra.required( - preState.updated( - mutableState -> - getBlockProcessor(preState) - .processExecutionLayerExits( - mutableState, - executionPayloadWithExits, - validatorExitContextSupplier(preState)))); - - // After processing the exit, the validator has exit_epoch and withdrawable_epoch set - assertThat(postState.getValidators().get(validatorIndex).getExitEpoch()) - .isLessThan(FAR_FUTURE_EPOCH); - assertThat(postState.getValidators().get(validatorIndex).getWithdrawableEpoch()) - .isLessThan(FAR_FUTURE_EPOCH); - } - - private Supplier validatorExitContextSupplier(final BeaconState state) { - return spec.getGenesisSpec().beaconStateMutators().createValidatorExitContextSupplier(state); - } - - private Optional createExecutionPayloadWithExits( - final UInt64 slot, final List exits) { - return Optional.of( - dataStructureUtil.randomExecutionPayload(slot, builder -> builder.exits(() -> exits))); - } - - private BlockProcessorElectra getBlockProcessor(final BeaconState state) { - return (BlockProcessorElectra) spec.getBlockProcessor(state.getSlot()); - } -} diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java deleted file mode 100644 index 5a3698f1719..00000000000 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/electra/helpers/MiscHelpersElectraTest.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.logic.versions.electra.helpers; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.logic.common.helpers.Predicates; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -public class MiscHelpersElectraTest { - - private final Spec spec = TestSpecFactory.createMinimalElectra(); - private final Predicates predicates = new Predicates(spec.getGenesisSpecConfig()); - private final SchemaDefinitionsElectra schemaDefinitionsElectra = - SchemaDefinitionsElectra.required(spec.getGenesisSchemaDefinitions()); - private final MiscHelpersElectra miscHelpersElectra = - new MiscHelpersElectra( - spec.getGenesisSpecConfig().toVersionElectra().orElseThrow(), - predicates, - schemaDefinitionsElectra); - private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); - - @Test - public void isFormerDepositMechanismDisabled_returnsTrueIfDisabled() { - final BeaconState preState = dataStructureUtil.randomBeaconState(); - - final BeaconState state = - BeaconStateElectra.required(preState) - .updated( - mutableState -> { - final UInt64 eth1DepositIndex = dataStructureUtil.randomUInt64(); - mutableState.setEth1DepositIndex(eth1DepositIndex); - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex(eth1DepositIndex); - }); - - assertThat(miscHelpersElectra.isFormerDepositMechanismDisabled(state)).isTrue(); - } - - @Test - public void isFormerDepositMechanismDisabled_returnsFalseIfNotDisabled() { - final BeaconState preState = dataStructureUtil.randomBeaconState(); - - final BeaconState state = - BeaconStateElectra.required(preState) - .updated( - mutableState -> { - mutableState.setEth1DepositIndex(UInt64.valueOf(64)); - MutableBeaconStateElectra.required(mutableState) - .setDepositReceiptsStartIndex( - SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX); - }); - - assertThat(miscHelpersElectra.isFormerDepositMechanismDisabled(state)).isFalse(); - } -} diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java index f7fa60b93ba..a7d9e84818f 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/TestSpecFactory.java @@ -21,7 +21,7 @@ import tech.pegasys.teku.spec.config.SpecConfigBellatrix; import tech.pegasys.teku.spec.config.SpecConfigCapella; import tech.pegasys.teku.spec.config.SpecConfigDeneb; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.config.SpecConfigLoader; import tech.pegasys.teku.spec.config.builder.SpecConfigBuilder; import tech.pegasys.teku.spec.networks.Eth2Network; @@ -45,7 +45,7 @@ public static Spec createMinimal(final SpecMilestone specMilestone) { case BELLATRIX -> createMinimalBellatrix(); case CAPELLA -> createMinimalCapella(); case DENEB -> createMinimalDeneb(); - case ELECTRA -> createMinimalElectra(); + case EIP7594 -> createMinimalEip7594(); }; } @@ -56,7 +56,7 @@ public static Spec createMainnet(final SpecMilestone specMilestone) { case BELLATRIX -> createMainnetBellatrix(); case CAPELLA -> createMainnetCapella(); case DENEB -> createMainnetDeneb(); - case ELECTRA -> createMainnetElectra(); + case EIP7594 -> createMainnetEip7594(); }; } @@ -103,14 +103,14 @@ public static Spec createMinimalDeneb(final Consumer configAd return create(specConfig, SpecMilestone.DENEB); } - public static Spec createMinimalElectra() { - final SpecConfigElectra specConfig = getElectraSpecConfig(Eth2Network.MINIMAL); - return create(specConfig, SpecMilestone.ELECTRA); + public static Spec createMinimalEip7594() { + final SpecConfigEip7594 specConfig = getEip7594SpecConfig(Eth2Network.MINIMAL); + return create(specConfig, SpecMilestone.EIP7594); } - public static Spec createMinimalElectra(final Consumer configAdapter) { - final SpecConfigElectra specConfig = getElectraSpecConfig(Eth2Network.MINIMAL, configAdapter); - return create(specConfig, SpecMilestone.ELECTRA); + public static Spec createMinimalEip7594(final Consumer configAdapter) { + final SpecConfigEip7594 specConfig = getEip7594SpecConfig(Eth2Network.MINIMAL, configAdapter); + return create(specConfig, SpecMilestone.EIP7594); } /** @@ -161,15 +161,15 @@ public static Spec createMinimalWithDenebForkEpoch(final UInt64 denebForkEpoch) } /** - * Create a spec that forks to Electra at the provided epoch + * Create a spec that forks to EIP7594 at the provided epoch * - * @param electraForkEpoch The Electra fork epoch - * @return A spec with Electra enabled, forking to Electra at the given epoch + * @param eip7594ForkEpoch The EIP7594 fork epoch + * @return A spec with EIP7594 enabled, forking to EIP7594 at the given epoch */ - public static Spec createMinimalWithElectraForkEpoch(final UInt64 electraForkEpoch) { - final SpecConfigElectra config = - getElectraSpecConfig(Eth2Network.MINIMAL, UInt64.ZERO, UInt64.ZERO, electraForkEpoch); - return create(config, SpecMilestone.ELECTRA); + public static Spec createMinimalWithEip7594ForkEpoch(final UInt64 eip7594ForkEpoch) { + final SpecConfigEip7594 config = + getEip7594SpecConfig(Eth2Network.MINIMAL, UInt64.ZERO, UInt64.ZERO, eip7594ForkEpoch); + return create(config, SpecMilestone.EIP7594); } public static Spec createMinimalPhase0() { @@ -202,9 +202,9 @@ public static Spec createMainnetDeneb() { return create(specConfig, SpecMilestone.DENEB); } - public static Spec createMainnetElectra() { - final SpecConfigElectra specConfig = getElectraSpecConfig(Eth2Network.MAINNET); - return create(specConfig, SpecMilestone.ELECTRA); + public static Spec createMainnetEip7594() { + final SpecConfigEip7594 specConfig = getEip7594SpecConfig(Eth2Network.MAINNET); + return create(specConfig, SpecMilestone.EIP7594); } public static Spec createPhase0(final SpecConfig config) { @@ -246,13 +246,13 @@ public static Spec create( .bellatrixBuilder(b -> b.bellatrixForkEpoch(UInt64.ZERO)) .capellaBuilder(c -> c.capellaForkEpoch(UInt64.ZERO)) .denebBuilder(d -> d.denebForkEpoch(UInt64.ZERO)); - case ELECTRA -> builder -> + case EIP7594 -> builder -> builder .altairBuilder(a -> a.altairForkEpoch(UInt64.ZERO)) .bellatrixBuilder(b -> b.bellatrixForkEpoch(UInt64.ZERO)) .capellaBuilder(c -> c.capellaForkEpoch(UInt64.ZERO)) .denebBuilder(d -> d.denebForkEpoch(UInt64.ZERO)) - .electraBuilder(e -> e.electraForkEpoch(UInt64.ZERO)); + .eip7594Builder(e -> e.eip7594ForkEpoch(UInt64.ZERO)); }; return create( SpecConfigLoader.loadConfig(network.configName(), defaultModifier.andThen(configModifier)), @@ -363,16 +363,16 @@ private static SpecConfigDeneb getDenebSpecConfig( })); } - private static SpecConfigElectra getElectraSpecConfig(final Eth2Network network) { - return getElectraSpecConfig(network, UInt64.ZERO, UInt64.ZERO, UInt64.ZERO); + private static SpecConfigEip7594 getEip7594SpecConfig(final Eth2Network network) { + return getEip7594SpecConfig(network, UInt64.ZERO, UInt64.ZERO, UInt64.ZERO); } - private static SpecConfigElectra getElectraSpecConfig( + private static SpecConfigEip7594 getEip7594SpecConfig( final Eth2Network network, final UInt64 capellaForkEpoch, final UInt64 denebForkEpoch, - final UInt64 electraForkEpoch) { - return getElectraSpecConfig( + final UInt64 eip7594ForkEpoch) { + return getEip7594SpecConfig( network, builder -> builder @@ -380,12 +380,12 @@ private static SpecConfigElectra getElectraSpecConfig( .bellatrixBuilder(b -> b.bellatrixForkEpoch(UInt64.ZERO)) .capellaBuilder(c -> c.capellaForkEpoch(capellaForkEpoch)) .denebBuilder(d -> d.denebForkEpoch(denebForkEpoch)) - .electraBuilder(e -> e.electraForkEpoch(electraForkEpoch))); + .eip7594Builder(e -> e.eip7594ForkEpoch(eip7594ForkEpoch))); } - private static SpecConfigElectra getElectraSpecConfig( + private static SpecConfigEip7594 getEip7594SpecConfig( final Eth2Network network, final Consumer configAdapter) { - return SpecConfigElectra.required( + return SpecConfigEip7594.required( SpecConfigLoader.loadConfig( network.configName(), builder -> { @@ -394,16 +394,16 @@ private static SpecConfigElectra getElectraSpecConfig( .bellatrixBuilder(b -> b.bellatrixForkEpoch(UInt64.ZERO)) .capellaBuilder(c -> c.capellaForkEpoch(UInt64.ZERO)) .denebBuilder(d -> d.denebForkEpoch(UInt64.ZERO)) - .electraBuilder(e -> e.electraForkEpoch(UInt64.ZERO)); + .eip7594Builder(e -> e.eip7594ForkEpoch(UInt64.ZERO)); configAdapter.accept(builder); })); } - public static Spec createMinimalWithCapellaDenebAndElectraForkEpoch( - final UInt64 capellaForkEpoch, final UInt64 denebForkEpoch, final UInt64 electraForkEpoch) { + public static Spec createMinimalWithCapellaDenebAndEip7594ForkEpoch( + final UInt64 capellaForkEpoch, final UInt64 denebForkEpoch, final UInt64 eip7594ForkEpoch) { final SpecConfigBellatrix config = - getElectraSpecConfig( - Eth2Network.MINIMAL, capellaForkEpoch, denebForkEpoch, electraForkEpoch); - return create(config, SpecMilestone.ELECTRA); + getEip7594SpecConfig( + Eth2Network.MINIMAL, capellaForkEpoch, denebForkEpoch, eip7594ForkEpoch); + return create(config, SpecMilestone.EIP7594); } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/BlockProposalTestUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/BlockProposalTestUtil.java index 616e0e9127e..4b914df0f00 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/BlockProposalTestUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/BlockProposalTestUtil.java @@ -268,9 +268,7 @@ private ExecutionPayload createExecutionPayload( .transactions(transactions.orElse(Collections.emptyList())) .withdrawals(List::of) .blobGasUsed(() -> UInt64.ZERO) - .excessBlobGas(() -> UInt64.ZERO) - .depositReceipts(List::of) - .exits(List::of)); + .excessBlobGas(() -> UInt64.ZERO)); } private Boolean isMergeTransitionComplete(final BeaconState state) { diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/DepositReceiptSupplier.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/DepositReceiptSupplier.java deleted file mode 100644 index 104360ee9c1..00000000000 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/DepositReceiptSupplier.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.propertytest.suppliers.execution.versions.electra; - -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.propertytest.suppliers.DataStructureUtilSupplier; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -public class DepositReceiptSupplier extends DataStructureUtilSupplier { - - public DepositReceiptSupplier() { - super(DataStructureUtil::randomDepositReceipt, SpecMilestone.ELECTRA); - } -} diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/ExecutionLayerExitSupplier.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/ExecutionLayerExitSupplier.java deleted file mode 100644 index 0d49beda7e4..00000000000 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/propertytest/suppliers/execution/versions/electra/ExecutionLayerExitSupplier.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Consensys Software Inc., 2024 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on - * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the - * specific language governing permissions and limitations under the License. - */ - -package tech.pegasys.teku.spec.propertytest.suppliers.execution.versions.electra; - -import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; -import tech.pegasys.teku.spec.propertytest.suppliers.DataStructureUtilSupplier; -import tech.pegasys.teku.spec.util.DataStructureUtil; - -public class ExecutionLayerExitSupplier extends DataStructureUtilSupplier { - - public ExecutionLayerExitSupplier() { - super(DataStructureUtil::randomExecutionLayerExit, SpecMilestone.ELECTRA); - } -} diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderEip7594.java similarity index 56% rename from ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java rename to ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderEip7594.java index 381910f75d3..d7aaecbf09e 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderElectra.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/BeaconStateBuilderEip7594.java @@ -15,7 +15,6 @@ import static com.google.common.base.Preconditions.checkNotNull; -import java.util.List; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; import tech.pegasys.teku.infrastructure.ssz.primitive.SszByte; @@ -23,19 +22,15 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadHeader; import tech.pegasys.teku.spec.datastructures.state.SyncCommittee; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.BeaconStateSchemaElectra; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.electra.MutableBeaconStateElectra; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingBalanceDeposit; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingConsolidation; -import tech.pegasys.teku.spec.datastructures.state.versions.electra.PendingPartialWithdrawal; - -public class BeaconStateBuilderElectra +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.MutableBeaconStateEip7594; + +public class BeaconStateBuilderEip7594 extends AbstractBeaconStateBuilder< - BeaconStateElectra, MutableBeaconStateElectra, BeaconStateBuilderElectra> { + BeaconStateEip7594, MutableBeaconStateEip7594, BeaconStateBuilderEip7594> { private UInt64 nextWithdrawalIndex; private UInt64 nextWithdrawalValidatorIndex; @@ -46,20 +41,7 @@ public class BeaconStateBuilderElectra private SyncCommittee nextSyncCommittee; private ExecutionPayloadHeader latestExecutionPayloadHeader; - private UInt64 depositReceiptsStartIndex; - private UInt64 depositBalanceToConsume; - private UInt64 exitBalanceToConsume; - private UInt64 earliestExitEpoch; - - private UInt64 consolidationBalanceToConsume; - - private UInt64 earliestConsolidationEpoch; - - private SszList pendingBalanceDeposits; - private SszList pendingPartialWithdrawals; - private SszList pendingConsolidations; - - protected BeaconStateBuilderElectra( + protected BeaconStateBuilderEip7594( final SpecVersion spec, final DataStructureUtil dataStructureUtil, final int defaultValidatorCount, @@ -68,12 +50,12 @@ protected BeaconStateBuilderElectra( } @Override - protected BeaconStateElectra getEmptyState() { - return BeaconStateSchemaElectra.create(spec.getConfig()).createEmpty(); + protected BeaconStateEip7594 getEmptyState() { + return BeaconStateSchemaEip7594.create(spec.getConfig()).createEmpty(); } @Override - protected void setUniqueFields(final MutableBeaconStateElectra state) { + protected void setUniqueFields(final MutableBeaconStateEip7594 state) { state.setPreviousEpochParticipation(previousEpochParticipation); state.setCurrentEpochParticipation(currentEpochParticipation); state.setInactivityScores(inactivityScores); @@ -82,64 +64,42 @@ protected void setUniqueFields(final MutableBeaconStateElectra state) { state.setLatestExecutionPayloadHeader(latestExecutionPayloadHeader); state.setNextWithdrawalIndex(nextWithdrawalIndex); state.setNextWithdrawalValidatorIndex(nextWithdrawalValidatorIndex); - state.setDepositReceiptsStartIndex(depositReceiptsStartIndex); - state.setDepositBalanceToConsume(depositBalanceToConsume); - state.setExitBalanceToConsume(exitBalanceToConsume); - state.setEarliestExitEpoch(earliestExitEpoch); - state.setConsolidationBalanceToConsume(consolidationBalanceToConsume); - state.setEarliestConsolidationEpoch(earliestConsolidationEpoch); - state.setPendingBalanceDeposits(pendingBalanceDeposits); - state.setPendingPartialWithdrawals(pendingPartialWithdrawals); - state.setPendingConsolidations(pendingConsolidations); } - public static BeaconStateBuilderElectra create( + public static BeaconStateBuilderEip7594 create( final DataStructureUtil dataStructureUtil, final Spec spec, final int defaultValidatorCount, final int defaultItemsInSSZLists) { - return new BeaconStateBuilderElectra( - spec.forMilestone(SpecMilestone.ELECTRA), + return new BeaconStateBuilderEip7594( + spec.forMilestone(SpecMilestone.EIP7594), dataStructureUtil, defaultValidatorCount, defaultItemsInSSZLists); } - public BeaconStateBuilderElectra nextWithdrawalIndex(final UInt64 nextWithdrawalIndex) { + public BeaconStateBuilderEip7594 nextWithdrawalIndex(final UInt64 nextWithdrawalIndex) { checkNotNull(nextWithdrawalIndex); this.nextWithdrawalIndex = nextWithdrawalIndex; return this; } - public BeaconStateBuilderElectra nextWithdrawalValidatorIndex( + public BeaconStateBuilderEip7594 nextWithdrawalValidatorIndex( final UInt64 nextWithdrawalValidatorIndex) { checkNotNull(nextWithdrawalValidatorIndex); this.nextWithdrawalValidatorIndex = nextWithdrawalValidatorIndex; return this; } - public BeaconStateBuilderElectra depositReceiptsStartIndex( - final UInt64 depositReceiptsStartIndex) { - checkNotNull(depositReceiptsStartIndex); - this.depositReceiptsStartIndex = depositReceiptsStartIndex; - return this; - } - - public BeaconStateBuilderElectra depositBalanceToConsume(final UInt64 depositBalanceToConsume) { - checkNotNull(depositBalanceToConsume); - this.depositBalanceToConsume = depositBalanceToConsume; - return this; - } - - private BeaconStateSchemaElectra getBeaconStateSchema() { - return (BeaconStateSchemaElectra) spec.getSchemaDefinitions().getBeaconStateSchema(); + private BeaconStateSchemaEip7594 getBeaconStateSchema() { + return (BeaconStateSchemaEip7594) spec.getSchemaDefinitions().getBeaconStateSchema(); } @Override protected void initDefaults() { super.initDefaults(); - final BeaconStateSchemaElectra schema = getBeaconStateSchema(); + final BeaconStateSchemaEip7594 schema = getBeaconStateSchema(); previousEpochParticipation = dataStructureUtil.randomSszList( @@ -158,25 +118,12 @@ protected void initDefaults() { nextSyncCommittee = dataStructureUtil.randomSyncCommittee(); latestExecutionPayloadHeader = dataStructureUtil.randomExecutionPayloadHeader( - dataStructureUtil.getSpec().forMilestone(SpecMilestone.ELECTRA)); + dataStructureUtil.getSpec().forMilestone(SpecMilestone.EIP7594)); this.nextWithdrawalIndex = UInt64.ZERO; this.nextWithdrawalValidatorIndex = defaultValidatorCount > 0 ? dataStructureUtil.randomUInt64(defaultValidatorCount) : UInt64.ZERO; - - this.depositReceiptsStartIndex = SpecConfigElectra.UNSET_DEPOSIT_RECEIPTS_START_INDEX; - this.depositBalanceToConsume = UInt64.ZERO; - this.exitBalanceToConsume = UInt64.ZERO; - this.earliestExitEpoch = UInt64.ZERO; - this.consolidationBalanceToConsume = UInt64.ZERO; - this.earliestConsolidationEpoch = UInt64.ZERO; - this.pendingBalanceDeposits = - schema.getPendingBalanceDepositsSchema().createFromElements(List.of()); - this.pendingPartialWithdrawals = - schema.getPendingPartialWithdrawalsSchema().createFromElements(List.of()); - this.pendingConsolidations = - schema.getPendingConsolidationsSchema().createFromElements(List.of()); } } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 222473aca6d..9f844912a20 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -90,12 +90,12 @@ import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSchema; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecarSchema; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.Cell; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.CellSchema; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumn; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSchema; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.Cell; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.CellSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumn; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; @@ -131,8 +131,6 @@ import tech.pegasys.teku.spec.datastructures.execution.Transaction; import tech.pegasys.teku.spec.datastructures.execution.TransactionSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.capella.Withdrawal; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.DepositReceipt; -import tech.pegasys.teku.spec.datastructures.execution.versions.electra.ExecutionLayerExit; import tech.pegasys.teku.spec.datastructures.forkchoice.VoteTracker; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientBootstrap; import tech.pegasys.teku.spec.datastructures.lightclient.LightClientBootstrapSchema; @@ -193,7 +191,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; public final class DataStructureUtil { @@ -201,8 +199,6 @@ public final class DataStructureUtil { private static final int MAX_EP_RANDOM_TRANSACTIONS_SIZE = 32; private static final int MAX_EP_RANDOM_WITHDRAWALS = 4; - private static final int MAX_EP_RANDOM_DEPOSIT_RECEIPTS = 4; - private static final int MAX_EP_RANDOM_EXITS = 16; private final Spec spec; @@ -592,9 +588,7 @@ public ExecutionPayloadHeader randomExecutionPayloadHeader( .transactionsRoot(randomBytes32()) .withdrawalsRoot(() -> withdrawalsRoot) .blobGasUsed(this::randomUInt64) - .excessBlobGas(this::randomUInt64) - .depositReceiptsRoot(this::randomBytes32) - .exitsRoot(this::randomBytes32)); + .excessBlobGas(this::randomUInt64)); } public ExecutionPayloadHeader randomExecutionPayloadHeader(final SpecVersion specVersion) { @@ -704,9 +698,7 @@ public ExecutionPayload randomExecutionPayload( .transactions(randomExecutionPayloadTransactions()) .withdrawals(this::randomExecutionPayloadWithdrawals) .blobGasUsed(this::randomUInt64) - .excessBlobGas(this::randomUInt64) - .depositReceipts(this::randomExecutionPayloadDepositReceipts) - .exits(this::randomExecutionPayloadExits); + .excessBlobGas(this::randomUInt64); builderModifier.accept(executionPayloadBuilder); }); } @@ -731,18 +723,6 @@ public List randomExecutionPayloadWithdrawals() { .collect(toList()); } - public List randomExecutionPayloadDepositReceipts() { - return IntStream.rangeClosed(0, randomInt(MAX_EP_RANDOM_DEPOSIT_RECEIPTS)) - .mapToObj(__ -> randomDepositReceipt()) - .collect(toList()); - } - - public List randomExecutionPayloadExits() { - return IntStream.rangeClosed(0, randomInt(MAX_EP_RANDOM_EXITS)) - .mapToObj(__ -> randomExecutionLayerExit()) - .collect(toList()); - } - public ExecutionPayloadAndBlobsBundle randomExecutionPayloadAndBlobsBundle() { final SchemaDefinitionsDeneb schemaDefinitionsDeneb = getDenebSchemaDefinitions(randomSlot()); final ExecutionPayload executionPayload = randomExecutionPayload(); @@ -1830,7 +1810,7 @@ public BeaconState randomBeaconState(final int validatorCount, final int numItem case BELLATRIX -> stateBuilderBellatrix(validatorCount, numItemsInSszLists); case CAPELLA -> stateBuilderCapella(validatorCount, numItemsInSszLists); case DENEB -> stateBuilderDeneb(validatorCount, numItemsInSszLists); - case ELECTRA -> stateBuilderElectra(validatorCount, numItemsInSszLists); + case EIP7594 -> stateBuilderEip7594(validatorCount, numItemsInSszLists); }; } @@ -1875,9 +1855,9 @@ public BeaconStateBuilderDeneb stateBuilderDeneb( this, spec, defaultValidatorCount, defaultItemsInSSZLists); } - public BeaconStateBuilderElectra stateBuilderElectra( + public BeaconStateBuilderEip7594 stateBuilderEip7594( final int defaultValidatorCount, final int defaultItemsInSSZLists) { - return BeaconStateBuilderElectra.create( + return BeaconStateBuilderEip7594.create( this, spec, defaultValidatorCount, defaultItemsInSSZLists); } @@ -2041,34 +2021,6 @@ public Withdrawal randomWithdrawal() { .create(randomUInt64(), randomValidatorIndex(), randomBytes20(), randomUInt64()); } - public DepositReceipt randomDepositReceiptWithValidSignature(final UInt64 index) { - final BLSKeyPair keyPair = randomKeyPair(); - final DepositMessage depositMessage = - new DepositMessage(keyPair.getPublicKey(), randomBytes32(), randomUInt64()); - final Bytes32 domain = computeDepositDomain(); - final Bytes signingRoot = getSigningRoot(depositMessage, domain); - final BLSSignature signature = BLS.sign(keyPair.getSecretKey(), signingRoot); - return getElectraSchemaDefinitions(randomSlot()) - .getDepositReceiptSchema() - .create( - depositMessage.getPubkey(), - depositMessage.getWithdrawalCredentials(), - depositMessage.getAmount(), - signature, - index); - } - - public DepositReceipt randomDepositReceipt() { - return getElectraSchemaDefinitions(randomSlot()) - .getDepositReceiptSchema() - .create( - randomPublicKey(), - randomEth1WithdrawalCredentials(), - randomUInt64(), - randomSignature(), - randomUInt64()); - } - public HistoricalSummary randomHistoricalSummary() { return getCapellaSchemaDefinitions(randomSlot()) .getHistoricalSummarySchema() @@ -2464,7 +2416,7 @@ public DataColumnSidecar build() { final SignedBeaconBlockHeader signedBlockHeader = signedBeaconBlockHeader.orElseGet(DataStructureUtil.this::randomSignedBeaconBlockHeader); final DataColumnSidecarSchema dataColumnSidecarSchema = - getElectraSchemaDefinitions(signedBlockHeader.getMessage().getSlot()) + getEip7594SchemaDefinitions(signedBlockHeader.getMessage().getSlot()) .getDataColumnSidecarSchema(); final int numberOfProofs = kzgProofs @@ -2497,14 +2449,14 @@ public DataColumnSidecar build() { public DataColumn randomDataColumn(final UInt64 slot) { final DataColumnSchema dataColumnSchema = - getElectraSchemaDefinitions(slot).getDataColumnSchema(); + getEip7594SchemaDefinitions(slot).getDataColumnSchema(); List list = IntStream.range(0, randomNumberOfBlobsPerBlock()).mapToObj(__ -> randomCell(slot)).toList(); return dataColumnSchema.create(list); } public Cell randomCell(final UInt64 slot) { - final CellSchema cellSchema = getElectraSchemaDefinitions(slot).getCellSchema(); + final CellSchema cellSchema = getEip7594SchemaDefinitions(slot).getCellSchema(); return cellSchema.create(randomBytes(cellSchema.getLength())); } @@ -2537,26 +2489,6 @@ public SszList emptyBlobKzgCommitments() { return getBlobKzgCommitmentsSchema().of(); } - public ExecutionLayerExit randomExecutionLayerExit() { - return getElectraSchemaDefinitions(randomSlot()) - .getExecutionLayerExitSchema() - .create(randomEth1Address(), randomPublicKey()); - } - - public ExecutionLayerExit executionLayerExit( - final Bytes20 sourceAddress, BLSPublicKey validatorPubKey) { - return getElectraSchemaDefinitions(randomSlot()) - .getExecutionLayerExitSchema() - .create(sourceAddress, validatorPubKey); - } - - public ExecutionLayerExit executionLayerExit(final Validator validator) { - final Bytes20 executionAddress = new Bytes20(validator.getWithdrawalCredentials().slice(12)); - return getElectraSchemaDefinitions(randomSlot()) - .getExecutionLayerExitSchema() - .create(executionAddress, validator.getPublicKey()); - } - public UInt64 randomBlobSidecarIndex() { return randomUInt64(spec.getMaxBlobsPerBlock().orElseThrow()); } @@ -2590,8 +2522,8 @@ private SchemaDefinitionsDeneb getDenebSchemaDefinitions(final UInt64 slot) { return SchemaDefinitionsDeneb.required(spec.atSlot(slot).getSchemaDefinitions()); } - private SchemaDefinitionsElectra getElectraSchemaDefinitions(final UInt64 slot) { - return SchemaDefinitionsElectra.required(spec.atSlot(slot).getSchemaDefinitions()); + private SchemaDefinitionsEip7594 getEip7594SchemaDefinitions(final UInt64 slot) { + return SchemaDefinitionsEip7594.required(spec.atSlot(slot).getSchemaDefinitions()); } int getEpochsPerEth1VotingPeriod() { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java index 8d59ac3ca15..e6cc978faf3 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java @@ -21,7 +21,7 @@ import tech.pegasys.teku.ethereum.events.SlotEventsChannel; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public class CustodySync implements SlotEventsChannel { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java index 11b6debbfca..1cf0253b6cf 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java @@ -16,7 +16,7 @@ import java.util.Optional; import java.util.stream.Stream; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public interface DataColumnSidecarCustody { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 6a310480fd6..9cb1183ffee 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -26,10 +26,10 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; import tech.pegasys.teku.storage.client.CombinedChainDataClient; public class DataColumnSidecarCustodyImpl implements DataColumnSidecarCustody, SlotEventsChannel { @@ -75,7 +75,7 @@ public boolean isIncomplete() { private final UInt256 nodeId; private final int totalCustodySubnetCount; - private final UInt64 electraStartEpoch; + private final UInt64 eip7594StartEpoch; private UInt64 currentSlot = null; @@ -97,7 +97,7 @@ public DataColumnSidecarCustodyImpl( .join(); this.nodeId = nodeId; this.totalCustodySubnetCount = totalCustodySubnetCount; - this.electraStartEpoch = spec.getForkSchedule().getFork(SpecMilestone.ELECTRA).getEpoch(); + this.eip7594StartEpoch = spec.getForkSchedule().getFork(SpecMilestone.EIP7594).getEpoch(); } private UInt64 getEarliestCustodySlot(UInt64 currentSlot) { @@ -108,10 +108,10 @@ private UInt64 getEarliestCustodySlot(UInt64 currentSlot) { private UInt64 getEarliestCustodyEpoch(UInt64 currentEpoch) { int custodyPeriod = spec.getSpecConfig(currentEpoch) - .toVersionElectra() + .toVersionEip7594() .orElseThrow() .getMinEpochsForDataColumnSidecarsRequests(); - return currentEpoch.minusMinZero(custodyPeriod).max(electraStartEpoch); + return currentEpoch.minusMinZero(custodyPeriod).max(eip7594StartEpoch); } private Set getCustodyColumnsForSlot(UInt64 slot) { @@ -119,7 +119,7 @@ private Set getCustodyColumnsForSlot(UInt64 slot) { } private Set getCustodyColumnsForEpoch(UInt64 epoch) { - return MiscHelpersElectra.required(spec.atEpoch(epoch).miscHelpers()) + return MiscHelpersEip7594.required(spec.atEpoch(epoch).miscHelpers()) .computeCustodyColumnIndexes(nodeId, epoch, totalCustodySubnetCount); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java index 1b521ea047e..62964b7ed81 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDB.java @@ -16,7 +16,7 @@ import java.util.Optional; import java.util.stream.Stream; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public interface DataColumnSidecarDB { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index 4d2be1212d9..ee132e61b3d 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -16,7 +16,7 @@ import java.util.Optional; import java.util.stream.Stream; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; import tech.pegasys.teku.storage.api.SidecarUpdateChannel; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java index aaea14f9f88..5d243db4715 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java @@ -16,7 +16,7 @@ import java.util.Optional; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.statetransition.validation.DataColumnSidecarGossipValidator; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java index c06d5d05204..7f7b01e32a6 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java @@ -14,7 +14,7 @@ package tech.pegasys.teku.statetransition.datacolumns; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; /** The class which searches for a specific {@link DataColumnSidecar} across nodes in the network */ public interface DataColumnSidecarRetriever { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java index 707562394b6..26599461bf2 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java @@ -15,7 +15,7 @@ import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public interface DataColumnReqResp { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index fde2f3ecca8..1e40710c61c 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -27,9 +27,9 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarRetriever; import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; @@ -181,8 +181,8 @@ private Set getNodeCustodyIndexes(UInt64 slot) { UInt64 epoch = spec.computeEpochAtSlot(slot); SpecVersion specVersion = spec.atSlot(slot); int minCustodyRequirement = - SpecConfigElectra.required(specVersion.getConfig()).getCustodyRequirement(); - return MiscHelpersElectra.required(specVersion.miscHelpers()) + SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); + return MiscHelpersEip7594.required(specVersion.miscHelpers()) .computeCustodyColumnIndexes( nodeId, epoch, minCustodyRequirement + extraCustodySubnetCount); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java index 087870b9ebd..48ac7afc084 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/ValidatingDataColumnReqResp.java @@ -15,7 +15,7 @@ import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java index 2b491482e00..eb06d1c971a 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarGossipValidator.java @@ -14,7 +14,7 @@ package tech.pegasys.teku.statetransition.validation; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public interface DataColumnSidecarGossipValidator { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java index 301c7364385..9b89daf1171 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/validation/DataColumnSidecarValidator.java @@ -14,7 +14,7 @@ package tech.pegasys.teku.statetransition.validation; import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; /** Check the DataColumnSidecar strict validity received either via Pubsub or Req/Resp */ public interface DataColumnSidecarValidator { diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlobSidecarGossipValidatorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlobSidecarGossipValidatorTest.java index 38f9ddc81aa..f2d909bed46 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlobSidecarGossipValidatorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlobSidecarGossipValidatorTest.java @@ -44,7 +44,7 @@ import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; import tech.pegasys.teku.spec.util.DataStructureUtil; -@TestSpecContext(milestone = {SpecMilestone.DENEB, SpecMilestone.ELECTRA}) +@TestSpecContext(milestone = {SpecMilestone.DENEB, SpecMilestone.EIP7594}) public class BlobSidecarGossipValidatorTest { private final Map invalidBlocks = new HashMap<>(); private final GossipValidationHelper gossipValidationHelper = mock(GossipValidationHelper.class); diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockGossipValidatorTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockGossipValidatorTest.java index 60dd6268d53..1d1c62f71ca 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockGossipValidatorTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/validation/BlockGossipValidatorTest.java @@ -53,7 +53,7 @@ SpecMilestone.ALTAIR, SpecMilestone.BELLATRIX, SpecMilestone.DENEB, - SpecMilestone.ELECTRA + SpecMilestone.EIP7594 }) public class BlockGossipValidatorTest { private Spec spec; diff --git a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java index f4a615b0ab2..c2ee0cec9bf 100644 --- a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java +++ b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/AbstractRpcMethodIntegrationTest.java @@ -35,7 +35,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.bellatrix.BeaconBlockBodyBellatrix; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.capella.BeaconBlockBodyCapella; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.deneb.BeaconBlockBodyDeneb; -import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.electra.BeaconBlockBodyElectra; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyEip7594; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.phase0.BeaconBlockBodyPhase0; import tech.pegasys.teku.storage.storageSystem.InMemoryStorageSystemBuilder; import tech.pegasys.teku.storage.storageSystem.StorageSystem; @@ -81,10 +81,10 @@ private void setUpNextSpec(final SpecMilestone nextSpecMilestone) { nextSpec = Optional.of(TestSpecFactory.createMinimalWithDenebForkEpoch(nextSpecEpoch)); } case DENEB -> { - checkState(nextSpecMilestone.equals(SpecMilestone.ELECTRA), "next spec should be electra"); - nextSpec = Optional.of(TestSpecFactory.createMinimalWithElectraForkEpoch(nextSpecEpoch)); + checkState(nextSpecMilestone.equals(SpecMilestone.EIP7594), "next spec should be eip7594"); + nextSpec = Optional.of(TestSpecFactory.createMinimalWithEip7594ForkEpoch(nextSpecEpoch)); } - case ELECTRA -> throw new RuntimeException("Base spec is already latest supported milestone"); + case EIP7594 -> throw new RuntimeException("Base spec is already latest supported milestone"); } nextSpecSlot = nextSpec.orElseThrow().computeStartSlotAtEpoch(nextSpecEpoch); } @@ -261,7 +261,7 @@ protected static Class milestoneToBeaconBlockBodyClass(final SpecMilestone mi case BELLATRIX -> BeaconBlockBodyBellatrix.class; case CAPELLA -> BeaconBlockBodyCapella.class; case DENEB -> BeaconBlockBodyDeneb.class; - case ELECTRA -> BeaconBlockBodyElectra.class; + case EIP7594 -> BeaconBlockBodyEip7594.class; }; } } diff --git a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java index e21b79749d6..eaa24cccc27 100644 --- a/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java +++ b/networking/eth2/src/integration-test/java/tech/pegasys/teku/networking/eth2/GetMetadataIntegrationTest.java @@ -147,7 +147,7 @@ public void requestMetadata_withDisparateVersionsEnabled( private static Class milestoneToMetadataClass(final SpecMilestone milestone) { return switch (milestone) { case PHASE0 -> MetadataMessagePhase0.class; - case ALTAIR, BELLATRIX, CAPELLA, DENEB, ELECTRA -> MetadataMessageAltair.class; + case ALTAIR, BELLATRIX, CAPELLA, DENEB, EIP7594 -> MetadataMessageAltair.class; }; } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 0590be01388..926a01c7e71 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -38,7 +38,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsBellatrix; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsCapella; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsDeneb; -import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; +import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsEip7594; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; @@ -72,7 +72,7 @@ import tech.pegasys.teku.spec.config.Constants; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -301,7 +301,7 @@ private GossipForkSubscriptions createSubscriptions( gossipedSignedContributionAndProofProcessor, gossipedSyncCommitteeMessageProcessor, gossipedSignedBlsToExecutionChangeProcessor); - case ELECTRA -> new GossipForkSubscriptionsElectra( + case EIP7594 -> new GossipForkSubscriptionsEip7594( forkAndSpecMilestone.getFork(), spec, asyncRunner, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java index 6627093373f..3f4b293fc44 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipChannel.java @@ -15,7 +15,7 @@ import java.util.List; import tech.pegasys.teku.infrastructure.events.VoidReturningChannelInterface; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public interface DataColumnSidecarGossipChannel extends VoidReturningChannelInterface { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index 91400021d2c..9cc2332e1d2 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -16,7 +16,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetSubscriptions; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public class DataColumnSidecarGossipManager implements GossipManager { private static final Logger LOG = LogManager.getLogger(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java index d8f88c9c4c4..555bb3b012f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java @@ -33,7 +33,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java index cea2c99e5c3..e8b5e84516f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkSubscriptions.java @@ -17,7 +17,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -73,14 +73,14 @@ default void unsubscribeFromSyncCommitteeSubnet(int subnetId) { default void publishSignedBlsToExecutionChangeMessage(SignedBlsToExecutionChange message) {} default void publishDataColumnSidecar(DataColumnSidecar blobSidecar) { - // since Electra + // since EIP7594 } default void subscribeToDataColumnSidecarSubnet(int subnetId) { - // since Electra + // since EIP7594 } default void unsubscribeFromDataColumnSidecarSubnet(int subnetId) { - // since Electra + // since EIP7594 } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7594.java similarity index 96% rename from networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java rename to networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7594.java index 98b5937decd..467fc737c9b 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsElectra.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/versions/GossipForkSubscriptionsEip7594.java @@ -23,7 +23,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -35,12 +35,12 @@ import tech.pegasys.teku.spec.datastructures.state.ForkInfo; import tech.pegasys.teku.storage.client.RecentChainData; -public class GossipForkSubscriptionsElectra extends GossipForkSubscriptionsDeneb { +public class GossipForkSubscriptionsEip7594 extends GossipForkSubscriptionsDeneb { private final OperationProcessor dataColumnSidecarOperationProcessor; private DataColumnSidecarGossipManager dataColumnSidecarGossipManager; - public GossipForkSubscriptionsElectra( + public GossipForkSubscriptionsEip7594( final Fork fork, final Spec spec, final AsyncRunner asyncRunner, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index 412838dc30a..f36a7c144fa 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -23,7 +23,7 @@ import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { private final Eth2P2PNetwork eth2P2PNetwork; @@ -68,14 +68,14 @@ private void onEpoch(final UInt64 epoch) { SpecVersion specVersion = spec.atEpoch(epoch); specVersion .miscHelpers() - .toVersionElectra() + .toVersionEip7594() .ifPresent( - electraSpec -> { + eip7594Spec -> { int totalSubnetCount = - SpecConfigElectra.required(specVersion.getConfig()).getCustodyRequirement() + SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement() + extraVoluntarySubnetCount; List subnets = - electraSpec.computeDataColumnSidecarBackboneSubnets( + eip7594Spec.computeDataColumnSidecarBackboneSubnets( nodeId, epoch, totalSubnetCount); subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); }); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java index 5fb5db3f5f4..1bb0825d2ff 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetSubscriptions.java @@ -28,11 +28,11 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecarSchema; import tech.pegasys.teku.spec.datastructures.state.ForkInfo; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; import tech.pegasys.teku.storage.client.RecentChainData; public class DataColumnSidecarSubnetSubscriptions extends CommitteeSubnetSubscriptions { @@ -59,12 +59,12 @@ public DataColumnSidecarSubnetSubscriptions( this.recentChainData = recentChainData; this.processor = processor; this.forkInfo = forkInfo; - SpecVersion specVersion = spec.forMilestone(SpecMilestone.ELECTRA); + SpecVersion specVersion = spec.forMilestone(SpecMilestone.EIP7594); this.dataColumnSidecarSchema = - SchemaDefinitionsElectra.required(specVersion.getSchemaDefinitions()) + SchemaDefinitionsEip7594.required(specVersion.getSchemaDefinitions()) .getDataColumnSidecarSchema(); this.subnetCount = - SpecConfigElectra.required(specVersion.getConfig()).getDataColumnSidecarSubnetCount(); + SpecConfigEip7594.required(specVersion.getConfig()).getDataColumnSidecarSubnetCount(); } public SafeFuture gossip(final DataColumnSidecar sidecar) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java index f0e938afced..ecc1503af7e 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java @@ -24,8 +24,8 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigElectra; -import tech.pegasys.teku.spec.logic.versions.electra.helpers.MiscHelpersElectra; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; @FunctionalInterface public interface NodeIdToDataColumnSidecarSubnetsCalculator { @@ -36,7 +36,7 @@ public interface NodeIdToDataColumnSidecarSubnetsCalculator { /** Creates a calculator instance for the specific slot */ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( - SpecConfigElectra config, MiscHelpersElectra miscHelpers, UInt64 currentSlot) { + SpecConfigEip7594 config, MiscHelpersEip7594 miscHelpers, UInt64 currentSlot) { UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(currentSlot); SszBitvectorSchema bitvectorSchema = SszBitvectorSchema.create(config.getDataColumnSidecarSubnetCount()); @@ -62,11 +62,11 @@ static NodeIdToDataColumnSidecarSubnetsCalculator create( slot -> { SpecVersion specVersion = spec.atSlot(slot); final NodeIdToDataColumnSidecarSubnetsCalculator calculatorAtSlot; - if (specVersion.getMilestone().isGreaterThanOrEqualTo(SpecMilestone.ELECTRA)) { + if (specVersion.getMilestone().isGreaterThanOrEqualTo(SpecMilestone.EIP7594)) { calculatorAtSlot = createAtSlot( - SpecConfigElectra.required(specVersion.getConfig()), - MiscHelpersElectra.required(specVersion.miscHelpers()), + SpecConfigEip7594.required(specVersion.getConfig()), + MiscHelpersEip7594.required(specVersion.miscHelpers()), slot); } else { calculatorAtSlot = NOOP; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index a9864457933..ce377bbc9ac 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -35,7 +35,7 @@ import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.NetworkingSpecConfigElectra; +import tech.pegasys.teku.spec.config.NetworkingSpecConfigEip7594; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsSupplier; public class PeerSubnetSubscriptions { @@ -77,8 +77,8 @@ public static PeerSubnetSubscriptions create( Integer dataColumnSidecarSubnetCount = currentVersion .getConfig() - .toVersionElectra() - .map(NetworkingSpecConfigElectra::getDataColumnSidecarSubnetCount) + .toVersionEip7594() + .map(NetworkingSpecConfigEip7594::getDataColumnSidecarSubnetCount) .orElse(0); final PeerSubnetSubscriptions subscriptions = @@ -175,7 +175,9 @@ static Builder builder( static PeerSubnetSubscriptions createEmpty( final SchemaDefinitionsSupplier currentSchemaDefinitions, final SszBitvectorSchema dataColumnSidecarSubnetBitmaskSchema) { - return builder(currentSchemaDefinitions, dataColumnSidecarSubnetBitmaskSchema).build(); + return builder(currentSchemaDefinitions, dataColumnSidecarSubnetBitmaskSchema) + .nodeIdToDataColumnSidecarSubnetsCalculator(NodeIdToDataColumnSidecarSubnetsCalculator.NOOP) + .build(); } public int getSubscriberCountForAttestationSubnet(final int subnetId) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java index edb8e42e99a..1e10a3034ac 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java @@ -84,10 +84,10 @@ public static Set getAllTopics( topics.add(getBlobSidecarSubnetTopic(forkDigest, i, gossipEncoding)); } } - spec.getNetworkingConfigElectra() + spec.getNetworkingConfigEip7594() .ifPresent( - electraNetworkConfig -> { - for (int i = 0; i < electraNetworkConfig.getDataColumnSidecarSubnetCount(); i++) { + eip7594NetworkConfig -> { + for (int i = 0; i < eip7594NetworkConfig.getDataColumnSidecarSubnetCount(); i++) { topics.add(getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)); } }); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index e710ec6bd6e..95a760a6238 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -53,7 +53,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRootRequestMessage; @@ -72,7 +72,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private static final Logger LOG = LogManager.getLogger(); @@ -139,8 +139,8 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { this.dataColumnSidecarsByRootRequestMessageSchema = Suppliers.memoize( () -> - SchemaDefinitionsElectra.required( - spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + SchemaDefinitionsEip7594.required( + spec.forMilestone(SpecMilestone.EIP7594).getSchemaDefinitions()) .getDataColumnSidecarsByRootRequestMessageSchema()); this.maxBlobsPerBlock = Suppliers.memoize(() -> getSpecConfigDeneb().getMaxBlobsPerBlock()); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java index 867dc81cee3..b01c0207483 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java @@ -31,7 +31,7 @@ import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java index 116c3f1ec33..5077740d06d 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java @@ -45,7 +45,7 @@ import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BeaconBlocksByRangeRequestMessage.BeaconBlocksByRangeRequestMessageSchema; @@ -63,7 +63,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.StatusMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.client.RecentChainData; @@ -371,7 +371,7 @@ private static Eth2RpcMethod createGoodBye( final PeerLookup peerLookup, final RpcEncoding rpcEncoding, final RecentChainData recentChainData) { - if (!spec.isMilestoneSupported(SpecMilestone.ELECTRA)) { + if (!spec.isMilestoneSupported(SpecMilestone.EIP7594)) { return Optional.empty(); } @@ -383,8 +383,8 @@ private static Eth2RpcMethod createGoodBye( new DataColumnSidecarsByRootMessageHandler(spec, metricsSystem, combinedChainDataClient); final DataColumnSidecarsByRootRequestMessageSchema dataColumnSidecarsByRootRequestMessageSchema = - SchemaDefinitionsElectra.required( - spec.forMilestone(SpecMilestone.ELECTRA).getSchemaDefinitions()) + SchemaDefinitionsEip7594.required( + spec.forMilestone(SpecMilestone.EIP7594).getSchemaDefinitions()) .getDataColumnSidecarsByRootRequestMessageSchema(); return Optional.of( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java index e135788742d..61bcb8e7634 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootListenerValidatingProxy.java @@ -19,7 +19,7 @@ import tech.pegasys.teku.networking.p2p.peer.Peer; import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public class DataColumnSidecarsByRootListenerValidatingProxy diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java index d092f81fec6..202656e777d 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -34,7 +34,7 @@ import tech.pegasys.teku.networking.eth2.rpc.core.RpcException; import tech.pegasys.teku.networking.p2p.rpc.StreamClosedException; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java index d16e69b8ffc..dffcc983981 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java @@ -23,7 +23,7 @@ import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.p2p.peer.Peer; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public class DataColumnSidecarsByRootValidator { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java index a3f6e31f5a3..236091e2f0c 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/encodings/context/ForkDigestPayloadContext.java @@ -17,10 +17,10 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszSchema; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.schemas.SchemaDefinitions; -import tech.pegasys.teku.spec.schemas.SchemaDefinitionsElectra; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; public interface ForkDigestPayloadContext { @@ -62,7 +62,7 @@ public UInt64 getSlotFromPayload(final DataColumnSidecar responsePayload) { @Override public SszSchema getSchemaFromSchemaDefinitions( final SchemaDefinitions schemaDefinitions) { - return SchemaDefinitionsElectra.required(schemaDefinitions).getDataColumnSidecarSchema(); + return SchemaDefinitionsEip7594.required(schemaDefinitions).getDataColumnSidecarSchema(); } }; diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java index 1e213b598a3..5b5fd0da28a 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java @@ -158,6 +158,8 @@ void shouldScoreCandidatePeersOnSubnetsWithFewPeersMoreHighly() { .addSubscriber(1, node1) .addSubscriber(1, node2) .addSubscriber(1, node3)) + .nodeIdToDataColumnSidecarSubnetsCalculator( + NodeIdToDataColumnSidecarSubnetsCalculator.NOOP) .build()); assertCandidatePeerScores( diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index 53a319bd307..f42ba3b7de5 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -56,7 +56,7 @@ import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsBellatrix; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsCapella; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsDeneb; -import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsElectra; +import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsEip7594; import tech.pegasys.teku.networking.eth2.gossip.forks.versions.GossipForkSubscriptionsPhase0; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationSubnetTopicProvider; import tech.pegasys.teku.networking.eth2.gossip.subnets.DataColumnSidecarSubnetTopicProvider; @@ -90,7 +90,7 @@ import tech.pegasys.teku.spec.datastructures.attestation.ProcessedAttestationListener; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.operations.Attestation; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; @@ -449,7 +449,7 @@ private GossipForkSubscriptions createSubscriptions( signedContributionAndProofProcessor, syncCommitteeMessageProcessor, signedBlsToExecutionChangeProcessor); - case ELECTRA -> new GossipForkSubscriptionsElectra( + case EIP7594 -> new GossipForkSubscriptionsEip7594( forkAndSpecMilestone.getFork(), spec, asyncRunner, diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java index a47b6983fa7..f20b763f858 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java @@ -49,7 +49,7 @@ import tech.pegasys.teku.networking.p2p.rpc.RpcStreamController; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.StateAndBlockSummary; diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 217650b7694..a68007d9322 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -588,7 +588,7 @@ protected void initBlobSidecarManager() { } protected void initDataColumnSidecarManager() { - if (spec.isMilestoneSupported(SpecMilestone.ELECTRA)) { + if (spec.isMilestoneSupported(SpecMilestone.EIP7594)) { DataColumnSidecarValidator dataColumnSidecarValidator = DataColumnSidecarValidator.create(); DataColumnSidecarGossipValidator gossipValidator = DataColumnSidecarGossipValidator.create(dataColumnSidecarValidator); @@ -854,7 +854,7 @@ public void initDepositProvider() { protected void initEth1DataCache() { LOG.debug("BeaconChainController.initEth1DataCache"); - eth1DataCache = new Eth1DataCache(spec, metricsSystem, new Eth1VotingPeriod(spec)); + eth1DataCache = new Eth1DataCache(metricsSystem, new Eth1VotingPeriod(spec)); } protected void initAttestationTopicSubscriber() { diff --git a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/PowchainService.java b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/PowchainService.java index 7124710b8fb..243c6279c58 100644 --- a/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/PowchainService.java +++ b/services/powchain/src/main/java/tech/pegasys/teku/services/powchain/PowchainService.java @@ -15,7 +15,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static tech.pegasys.teku.beacon.pow.api.Eth1DataCachePeriodCalculator.calculateEth1DataCacheDurationPriorToCurrentTime; -import static tech.pegasys.teku.infrastructure.logging.StatusLogger.STATUS_LOG; import com.google.common.annotations.VisibleForTesting; import java.util.Collections; @@ -23,7 +22,6 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; import java.util.stream.IntStream; import java.util.stream.Stream; import okhttp3.OkHttpClient; @@ -58,7 +56,6 @@ import tech.pegasys.teku.service.serviceutils.ServiceConfig; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.storage.api.CombinedStorageChannel; import tech.pegasys.teku.storage.api.Eth1DepositStorageChannel; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; @@ -73,7 +70,6 @@ public class PowchainService extends Service implements FinalizedCheckpointChann private final ServiceConfig serviceConfig; private final PowchainConfiguration powConfig; private final Optional maybeExecutionWeb3jClientProvider; - private final Supplier> finalizedStateSupplier; private List web3js; private Eth1Providers eth1Providers; @@ -83,21 +79,15 @@ public class PowchainService extends Service implements FinalizedCheckpointChann public PowchainService( final ServiceConfig serviceConfig, final PowchainConfiguration powConfig, - final Optional maybeExecutionWeb3jClientProvider, - final Supplier> finalizedStateSupplier) { + final Optional maybeExecutionWeb3jClientProvider) { checkArgument(powConfig.isEnabled() || maybeExecutionWeb3jClientProvider.isPresent()); this.serviceConfig = serviceConfig; this.powConfig = powConfig; this.maybeExecutionWeb3jClientProvider = maybeExecutionWeb3jClientProvider; - this.finalizedStateSupplier = finalizedStateSupplier; } @Override protected SafeFuture doStart() { - if (isFormerDepositMechanismDisabled()) { - // no need to initialize and start services if Eth1 polling has already been disabled - return SafeFuture.COMPLETE; - } return SafeFuture.fromRunnable(this::initialize) .thenPeek(__ -> hasInitialized.set(true)) .thenCompose( @@ -128,25 +118,6 @@ public void onNewFinalizedCheckpoint( if (!isRunning()) { return; } - if (isFormerDepositMechanismDisabled()) { - // stop Eth1 polling - stop() - .finish( - () -> { - if (hasInitialized.get()) { - // only log if polling has been enabled and now stopped - STATUS_LOG.eth1PollingHasBeenDisabled(); - } - }, - LOG::error); - } - } - - private boolean isFormerDepositMechanismDisabled() { - return finalizedStateSupplier - .get() - .map(powConfig.getSpec()::isFormerDepositMechanismDisabled) - .orElse(false); } @VisibleForTesting diff --git a/services/powchain/src/test/java/tech/pegasys/teku/services/powchain/PowchainServiceTest.java b/services/powchain/src/test/java/tech/pegasys/teku/services/powchain/PowchainServiceTest.java index 190b108d453..4c593f963fc 100644 --- a/services/powchain/src/test/java/tech/pegasys/teku/services/powchain/PowchainServiceTest.java +++ b/services/powchain/src/test/java/tech/pegasys/teku/services/powchain/PowchainServiceTest.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Optional; -import java.util.function.Supplier; import org.hyperledger.besu.plugin.services.MetricsSystem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,8 +39,6 @@ import tech.pegasys.teku.service.serviceutils.ServiceConfig; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; -import tech.pegasys.teku.spec.datastructures.state.Checkpoint; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.storage.api.Eth1DepositStorageChannel; public class PowchainServiceTest { @@ -53,7 +50,6 @@ public class PowchainServiceTest { mock(ExecutionWeb3jClientProvider.class); private final Web3JClient web3JClient = mock(Web3JClient.class); private final Spec spec = TestSpecFactory.createMinimalPhase0(); - private Supplier> latestFinalizedState; @BeforeEach public void setup() { @@ -73,7 +69,6 @@ public void setup() { when(eventChannels.getPublisher(eq(Eth1DepositStorageChannel.class), any(AsyncRunner.class))) .thenReturn(mock(Eth1DepositStorageChannel.class)); when(serviceConfig.getEventChannels()).thenReturn(eventChannels); - latestFinalizedState = Optional::empty; } @Test @@ -218,52 +213,10 @@ public void shouldHaveNoDepositSnapshotPathWhenNoneIsAvailable() { .isEmpty(); } - @Test - public void shouldNotInitializeIfEth1PollingHasBeenDisabled() { - final Spec spec = mock(Spec.class); - when(powConfig.getSpec()).thenReturn(spec); - final BeaconState finalizedState = mock(BeaconState.class); - when(spec.isFormerDepositMechanismDisabled(finalizedState)).thenReturn(true); - - latestFinalizedState = () -> Optional.of(finalizedState); - - final PowchainService powchainService = - new PowchainService( - serviceConfig, powConfig, Optional.of(engineWeb3jClientProvider), latestFinalizedState); - - powchainService.start().join(); - - assertThat(powchainService.isRunning()).isTrue(); - assertThat(powchainService.getEth1DepositManager()).isNull(); - } - - @Test - public void shouldStopServiceIfEth1PollingHasBeenDisabledOnFinalizedCheckpoint() { - final Spec spec = mock(Spec.class); - when(powConfig.getSpec()).thenReturn(spec); - final BeaconState finalizedState = mock(BeaconState.class); - when(spec.isFormerDepositMechanismDisabled(finalizedState)).thenReturn(true); - - latestFinalizedState = () -> Optional.of(finalizedState); - - final PowchainService powchainService = - new PowchainService( - serviceConfig, powConfig, Optional.of(engineWeb3jClientProvider), latestFinalizedState); - - powchainService.start().join(); - - assertThat(powchainService.isRunning()).isTrue(); - - powchainService.onNewFinalizedCheckpoint(mock(Checkpoint.class), false); - - assertThat(powchainService.isRunning()).isFalse(); - } - private PowchainService createAndInitializePowchainService( final Optional maybeExecutionWeb3jClientProvider) { final PowchainService powchainService = - new PowchainService( - serviceConfig, powConfig, maybeExecutionWeb3jClientProvider, latestFinalizedState); + new PowchainService(serviceConfig, powConfig, maybeExecutionWeb3jClientProvider); powchainService.initialize(); return powchainService; } diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java index a4fd9c48628..2106e0cbbb5 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/SidecarUpdateChannel.java @@ -15,7 +15,7 @@ import tech.pegasys.teku.infrastructure.events.VoidReturningChannelInterface; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public interface SidecarUpdateChannel extends VoidReturningChannelInterface { diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java index 757f61a4763..31366cd0d9a 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java @@ -23,7 +23,7 @@ import tech.pegasys.teku.infrastructure.events.ChannelInterface; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; diff --git a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java index 170f6764f73..80cc7983e4c 100644 --- a/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java +++ b/storage/src/integration-test/java/tech/pegasys/teku/storage/server/kvstore/DatabaseTest.java @@ -64,7 +64,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.TestSpecFactory; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; @@ -2176,7 +2176,7 @@ public void pruneFinalizedBlocks_shouldRemoveFinalizedBlocks(final DatabaseConte @TestTemplate public void addSidecar_isOperative(final DatabaseContext context) throws IOException { - setupWithSpec(TestSpecFactory.createMinimalElectra()); + setupWithSpec(TestSpecFactory.createMinimalEip7594()); initialize(context); final DataColumnSidecar dataColumnSidecar = dataStructureUtil.randomDataColumnSidecar(); final ColumnSlotAndIdentifier columnSlotAndIdentifier = @@ -2189,7 +2189,7 @@ public void addSidecar_isOperative(final DatabaseContext context) throws IOExcep @TestTemplate public void setFirstIncompleteSlot_isOperative(final DatabaseContext context) throws IOException { - setupWithSpec(TestSpecFactory.createMinimalElectra()); + setupWithSpec(TestSpecFactory.createMinimalEip7594()); initialize(context); assertThat(database.getFirstIncompleteSlot().isEmpty()).isTrue(); @@ -2202,7 +2202,7 @@ public void setFirstIncompleteSlot_isOperative(final DatabaseContext context) th @SuppressWarnings("JavaCase") public void streamDataColumnIdentifiers_isOperative(final DatabaseContext context) throws IOException { - setupWithSpec(TestSpecFactory.createMinimalElectra()); + setupWithSpec(TestSpecFactory.createMinimalEip7594()); initialize(context); final SignedBeaconBlockHeader blockHeader1 = dataStructureUtil.randomSignedBeaconBlockHeader(); @@ -2248,7 +2248,7 @@ public void streamDataColumnIdentifiers_isOperative(final DatabaseContext contex @TestTemplate @SuppressWarnings("JavaCase") public void pruneAllSidecars_isOperative(final DatabaseContext context) throws IOException { - setupWithSpec(TestSpecFactory.createMinimalElectra()); + setupWithSpec(TestSpecFactory.createMinimalEip7594()); initialize(context); final SignedBeaconBlockHeader blockHeader1 = diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java index 6b1577694cc..64de7c3424c 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java @@ -31,7 +31,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.MinimalBeaconBlockSummary; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java index e62295a11c8..048a715c248 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java @@ -28,7 +28,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.config.SpecConfig; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java index a834ba69147..8df2ca54c6d 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java @@ -24,7 +24,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java index e9d018e5ae7..0e791fa864e 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java @@ -27,7 +27,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index 60bc456c2f5..ecb5075b6d3 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -49,7 +49,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockInvariants; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockSummary; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java index afba07ed547..05cbc65d461 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java @@ -33,7 +33,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java index 40d02c256ab..ebd3cbe8384 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java @@ -27,7 +27,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java index 30f480f5826..e35e75eb65e 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java @@ -29,7 +29,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockAndCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java index f045d658f40..83deb98584b 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java @@ -30,7 +30,7 @@ import org.apache.tuweni.bytes.Bytes32; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java index c80629cf829..569bdcb0f18 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java @@ -30,7 +30,7 @@ import tech.pegasys.teku.ethereum.pow.api.MinGenesisTimeBlockEvent; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BlockCheckpoints; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java index ed5dcc16e61..c8e87ee061a 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/store/StoreTransactionUpdatesFactory.java @@ -259,6 +259,6 @@ private StoreTransactionUpdates createStoreTransactionUpdates( optimisticTransitionBlockRoot, // FIXME: suboptimal criteria, doesn't fade out when blobs are over spec.isMilestoneSupported(SpecMilestone.DENEB), - spec.isMilestoneSupported(SpecMilestone.ELECTRA)); + spec.isMilestoneSupported(SpecMilestone.EIP7594)); } } diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java index 2eeb808d976..3a62a2f7896 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubSidecarUpdateChannel.java @@ -14,7 +14,7 @@ package tech.pegasys.teku.storage.api; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public class StubSidecarUpdateChannel implements SidecarUpdateChannel { @Override diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java index 8919c9ddd98..02d07f5562a 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java @@ -24,7 +24,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; -import tech.pegasys.teku.spec.datastructures.blobs.versions.electra.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SlotAndBlockRoot; diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java index 3c0f5c36362..fc0145f89f9 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/Eth2NetworkOptions.java @@ -146,12 +146,12 @@ public class Eth2NetworkOptions { private UInt64 denebForkEpoch; @Option( - names = {"--Xnetwork-electra-fork-epoch"}, + names = {"--Xnetwork-das-fork-epoch"}, hidden = true, paramLabel = "", - description = "Override the electra fork activation epoch.", + description = "Override the EIP7594 fork activation epoch.", arity = "1") - private UInt64 electraForkEpoch; + private UInt64 eip7594ForkEpoch; @Option( names = {"--Xnetwork-total-terminal-difficulty-override"}, @@ -309,8 +309,8 @@ private void configureEth2Network(Eth2NetworkConfiguration.Builder builder) { if (denebForkEpoch != null) { builder.denebForkEpoch(denebForkEpoch); } - if (electraForkEpoch != null) { - builder.electraForkEpoch(electraForkEpoch); + if (eip7594ForkEpoch != null) { + builder.eip7594ForkEpoch(eip7594ForkEpoch); } if (totalTerminalDifficultyOverride != null) { builder.totalTerminalDifficultyOverride(totalTerminalDifficultyOverride); diff --git a/teku/src/main/java/tech/pegasys/teku/services/BeaconNodeServiceController.java b/teku/src/main/java/tech/pegasys/teku/services/BeaconNodeServiceController.java index ae948646e9d..5c8cb4d16fc 100644 --- a/teku/src/main/java/tech/pegasys/teku/services/BeaconNodeServiceController.java +++ b/teku/src/main/java/tech/pegasys/teku/services/BeaconNodeServiceController.java @@ -16,7 +16,6 @@ import static tech.pegasys.teku.infrastructure.logging.EventLogger.EVENT_LOG; import java.util.Optional; -import java.util.function.Supplier; import tech.pegasys.teku.config.TekuConfiguration; import tech.pegasys.teku.ethereum.executionclient.web3j.ExecutionWeb3jClientProvider; import tech.pegasys.teku.networking.nat.NatService; @@ -25,9 +24,7 @@ import tech.pegasys.teku.services.chainstorage.StorageService; import tech.pegasys.teku.services.executionlayer.ExecutionLayerService; import tech.pegasys.teku.services.powchain.PowchainService; -import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconState; import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; -import tech.pegasys.teku.storage.client.RecentChainData; import tech.pegasys.teku.validator.client.ValidatorClientService; import tech.pegasys.teku.validator.client.slashingriskactions.DoppelgangerDetectionShutDown; import tech.pegasys.teku.validator.client.slashingriskactions.SlashedValidatorShutDown; @@ -63,19 +60,7 @@ public BeaconNodeServiceController( tekuConfig.natConfiguration(), tekuConfig.network().getListenPort(), tekuConfig.discovery().isDiscoveryEnabled())); - // making it a Supplier ensures that BeaconChainService has been started and RecentChainData has - // been initialized when `get()` is called - final Supplier> finalizedStateSupplier = - () -> { - final RecentChainData recentChainData = - beaconChainService.getBeaconChainController().getRecentChainData(); - if (recentChainData.isPreGenesis()) { - return Optional.empty(); - } - return Optional.of(recentChainData.getStore().getLatestFinalized().getState()); - }; - powchainService( - tekuConfig, serviceConfig, maybeExecutionWeb3jClientProvider, finalizedStateSupplier) + powchainService(tekuConfig, serviceConfig, maybeExecutionWeb3jClientProvider) .ifPresent(services::add); final Optional maybeValidatorSlashedAction = @@ -94,8 +79,7 @@ public BeaconNodeServiceController( private Optional powchainService( final TekuConfiguration tekuConfig, final ServiceConfig serviceConfig, - final Optional maybeExecutionWeb3jClientProvider, - final Supplier> finalizedStateSupplier) { + final Optional maybeExecutionWeb3jClientProvider) { if (tekuConfig.beaconChain().interopConfig().isInteropEnabled() || (!tekuConfig.powchain().isEnabled() && maybeExecutionWeb3jClientProvider.isEmpty())) { return Optional.empty(); @@ -108,10 +92,7 @@ private Optional powchainService( } final PowchainService powchainService = new PowchainService( - serviceConfig, - tekuConfig.powchain(), - maybeExecutionWeb3jClientProvider, - finalizedStateSupplier); + serviceConfig, tekuConfig.powchain(), maybeExecutionWeb3jClientProvider); serviceConfig.getEventChannels().subscribe(FinalizedCheckpointChannel.class, powchainService); return Optional.of(powchainService); } diff --git a/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockELECTRA.json b/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockEIP7594.json similarity index 60% rename from validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockELECTRA.json rename to validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockEIP7594.json index c6aa471fb2d..70a921be52b 100644 --- a/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockELECTRA.json +++ b/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlindedBlockEIP7594.json @@ -1 +1 @@ -{"version":"electra","execution_payload_blinded":true,"execution_payload_value":"42104374537666016842731412608176468386512470599052556672967227278486679620790","consensus_block_value":"50756583220288449835724789919752990744036228048165053817930899246206127260481","data":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload_header":{"parent_hash":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343291711b9de6b9dbebc4c9b49","fee_recipient":"0xbf886c3ec849316e3b187793c3a4398b6097768d","state_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","receipts_root":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9","logs_bloom":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d078748f9069c96e9d6a2801cf607000a52447e46e1bef4e056ee30d4bd3517aaf7bf65ba04dd28c3a4a14b8dc72a300f051722a6814fa3931d90a82d23285d4c1127b6c67bbc4f8682ddbf9b31eb3114c26dccc5330109d6f17799339c2d7ed7e4e3a7de5d515106aaec7be6d78be3e21806d6d30c39b77c75dcf354b63033fb200b3b9dc023d948278f0956c0ee99323da0162f2a84b6a95749d2fa1d4e089af416d412ccd992683f7e41f7b496ca04f9f463806e3643d1c07f39d2a65f84e97b7dfaafac740d1e03f30923a4270fcf651ad2ca3737859a524e86e02229a55abd1a7","prev_randao":"0x0c0d553e4878ae811024144112c88bbf79a372d5dfdf39730cede08696ad52d4","block_number":"4489858063226749928","gas_limit":"4481595642848361992","gas_used":"4479943159631677864","timestamp":"4484900609281730248","extra_data":"0x6bb2373e68f20adada72181a3474f2c098b26daf6fcb0516f0723270da91e789","base_fee_per_gas":"91973405088222260025272995045243630915786868313949746451634391325697029602367","block_hash":"0xde78143e27b846779904841e2aa96d8fbec4671bb57ffa72037ac721f8d633ca","transactions_root":"0xa415263e48d5a8a8ba3b4e9caf0e3028abbb6a65922580447af6fcc869b40d2a","withdrawals_root":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","blob_gas_used":"4476638188903342311","excess_blob_gas":"4521255257228650249","deposit_receipts_root":"0xc7dab83ea972daeec7b1385f04b22e210f708323c38b84160159653a163f259e","exits_root":"0x8e77ca3ec98f3c20e7e802dd8917f1b9fc66866da0310ae878d59ae1871cfffd"},"bls_to_execution_changes":[{"message":{"validator_index":"958637","from_bls_pubkey":"0x89ca6fbbaafb3c27a42f70699f42e3f79d3b7681b103e5b393042efa90512aa9e590fccb0b654a5c1590bbe675aa584e","to_execution_address":"0x4dc28f3e0884f5d07b860b8fce6fbebc3b857c21"},"signature":"0xa207b627176533d6fc3670d6c4a48afff107ef6bf9d7520b0f77162bf7df5505aa3a17c08ee53067daad827b75ef333015a61a81b421ec8888ff31dc50994121420862d11926782bf6983aab5f6f39c17264a77943dd6618d85444c56d469c19"},{"message":{"validator_index":"2034892","from_bls_pubkey":"0xb384518c8bde1118e66c725854a5919c4a5c0453e8eb822fed707aa5c636e6e2a2aa56c95966817ec67ebadf6c77b44a","to_execution_address":"0x7304843e481bb45a660cdae57581e756478b7a45"},"signature":"0xaf85c50db6dad8c04b9a22611e3174fdc57a3cfe255164e0361897353984556c3e7b91b040ec3b59cc29d774aaacf4b204e4e1960b9dd5bf9b72f8c3e09bdd64753a65084567c10b229784fe46be3de016c8add60d84c970943d221882294028"},{"message":{"validator_index":"2742020","from_bls_pubkey":"0xb2bffd7d93c4b1e46b3f4511f97de3ac70e2f9b2e3428b26ad7864d9f71bbca34904c81dbcc1dc2ffd1c02bfa016b096","to_execution_address":"0x7b5e6c4391e30ac86613889d8c20bde70e044e3a"},"signature":"0xa2a6993708f79d7b3db30c3968aaf4b706400145cf70fded02b06d54922e54a4c1db14d63860cc632d0b2a8c15efc5280bc57ceed454838f2a0848e03e610e130309d8012aa5eddc1fa84b509fc8178a685baaa0413ec58709e738aae430491d"},{"message":{"validator_index":"818276","from_bls_pubkey":"0x94e2d4cf94fb757578c496885af2075c26e2483eeffa6e894ac791f7c1945b0fbf9a6f7860736db93e03d511c4b08516","to_execution_address":"0xa2a06043d17ac951519956f43432e6811a0a4c5e"},"signature":"0xa8b4b8e92e67565ec430f2fdda94ed0f6f06d8cb302770191d614b795d194e4728c11e72162f25e04d0f7dda1dcd54da0d8a7c39e71e945873168ffa294da70dd1acbc1902a2fb1598267df5d277a0f95592967ea222ab0706571001c315eb2b"},{"message":{"validator_index":"433527","from_bls_pubkey":"0xa5041469fc5f6a944fda64e7ab422c1479ab9d0de12a2f3ac7292dfe368408cbc6d2b0ff519b521427da731e7378806e","to_execution_address":"0xfdc8e14312fb98663ed87639047022e291c761d2"},"signature":"0xb66c9d2c80f5a12930f0899b9ff3d1a6a37e0f9edb279ced767eca8ef0380227681b15bd3850a00a383491ed1d8e869310f10edea2b912278e1e2ec1cfaaba8c0981af2e40fd233a9fd2f67ec56540c66e062212ee2781593a4714914e15cb52"},{"message":{"validator_index":"1665765","from_bls_pubkey":"0x9105e2e35c7861d3fee37cb3bf07e8fdf3e0911d251cb11b956d4edbbd62a951c7ac9854677ce19a7748a503c307028c","to_execution_address":"0x240bd643529257f0285e4590ab814b7c9dcd5ff6"},"signature":"0xb3b0b28bedcc6e28d433c2a577204a9f7ecfc2fd4e3067ddcb65caebf7fd32d0389dd1db836600b0ee19a2ac8b6d0a660788a42abfde02bd5bddfdbe8cdde83a890ce69ee178ea314cfd9c06e5507ffe5cc4a685004f955219fcbbbec6fdd144"},{"message":{"validator_index":"1281016","from_bls_pubkey":"0xaae4f1779eb7e006a9d0195e39af1f14a05b017a4a351ee1f3c22929929fb510cae4ba8e01b6d2444a66e388e655d92c","to_execution_address":"0xe3559b43918610a1bdfb4d42efd9187fdceb55aa"},"signature":"0xa3acdd589f44c5b4201ae54cd119add73b60bcaca91f9e5d028669dd9b52f3ce15c20bb0ec39ff9ddfe96d5c1ce979c10376d36f4840a04cd90ed9d4348fa4a53f0f00e35bfab055a102ce3b6306255ffba3ef9ce7e1548048139d574478ebbf"},{"message":{"validator_index":"2201289","from_bls_pubkey":"0x842bb38ef27bafce4e8aa9abd3e31286da4d36eb87ff6a2fc4de272e4878230a7ac7a723bf3f76101ab2c2a642550eff","to_execution_address":"0x6cbad342d091b8c64ee004060b06d3bbb052340e"},"signature":"0xabd643eedb5dfcc8f2db27bcfd59f6359517cec81ab4d5ff08bd5fd246ba120883c047e0cffc1d215104169a335628180df5779f128772f899546fd260328d4a4368a044c3e2037f4284624728dc94e05467b1559aad3077cf9557bf62fc56e2"},{"message":{"validator_index":"1816540","from_bls_pubkey":"0x990cf4f3bf6ede0aaef3010026465f98f381860535ce007b87879afbf2c955c13d07d7c2d91e22fddd8ef5531f8bd22c","to_execution_address":"0x2b0599420f867177e37d0db84f5ea0beef702ac2"},"signature":"0xac8ebc3beb6cfc97c27f286e0d2e582707cbcb972d0898a41831b2d1393a684ce54ce54dc9128dc3988930ae4d92b4ed0a51b2bf639d8fd8e62e40ceac222362d9bb67f9d1b8419f3123dac1bb2e4e0cccb5c7c0985c83bd0501ed610935aa96"},{"message":{"validator_index":"2892795","from_bls_pubkey":"0xa0695f8f6f65e3d8401e144eb382eaed73f9ec56be6de71dadb917af79a08ff7b74967dd4f4766ed77f7bc2fc01cfa38","to_execution_address":"0x51478d424f1d3001cd03dc0ef66fc958fb7628e6"},"signature":"0xa18c2c70d886e11a592393a7bef6fb3a515100e1436763854eb96fca9c031a959e4c105be367a10ea87c3d1a8bce821303470a1d6053cd89139bbd86fd7bbdd3e377b331884bedb0f9b10eafcb3272561fc5d71b96b219d7fe3aacd6e1558c97"},{"message":{"validator_index":"2664029","from_bls_pubkey":"0x97e268878248299c9e4d2c86957935d6cddb83900dbb6d4e52a935bcda58978f6fd33e0dc891cea14da0feafd5173762","to_execution_address":"0xad6f0e43909dff15ba42fc53c6ad05b972343e5a"},"signature":"0xa2010187045aa6d63130c7ff23464438af57c3e42eaa90823205936a94c47713b68bd93d3b6837947e277ece630a6d200d428979548f340f6f71ca33e8731e059a8c20f75d71d36caebbbf6fde28f37a919353dedb7b7c7e4dbcda553e5bbee5"},{"message":{"validator_index":"740284","from_bls_pubkey":"0x8aec1b1f595063af33939f3c3322ad38d2e1de1b11fbc8a9d04235dc7fc9792e1c88e51452d337855d254a71f42816e8","to_execution_address":"0xd3b10243d034be9fa5c8caaa6ebf2e537e3a3c7e"},"signature":"0xa0ba14bb9ce5877d9f9d607da9b2fd2d629a1de42d6d3beb5a8f4c1661aa1d6863e01de14c548be8a9df222efc6373be1290581da81c76d71bfada1d07481d7b7de94290efd640aadca41d6b4d4f81091f4c459b454bd6e333eaa35c60faacf5"},{"message":{"validator_index":"355536","from_bls_pubkey":"0xa912f4ad989d87e777e45af7c265b430daf0b39345987506d4158cdee406847f294fc7745154eb52abf0934a5e1866ee","to_execution_address":"0xf51e0c420e9d60ece0c4bbc926328df885b91272"},"signature":"0xa7f77c7fc98b1c3a364dcac68b5cff112f7745e6dd41918ba56a6fa6945507e0ce245334e22d4581f49bda913baa2a6b1176b44d52168151b3aff9a625dcdebad1899747c42c4a43cf31f49124fc0d4543e4485592c243c5300b79214398b770"},{"message":{"validator_index":"1275809","from_bls_pubkey":"0xa77e90361be2a534a386cb689d6d763a98bea5f23f325b553a762648668e4adb4991fb5f41ad7ece1578f082a5c01b5e","to_execution_address":"0x1c6100424e341f76cb4a8a20cd43b69291bf1096"},"signature":"0xad188010cb0db88e067c2699030353a1c215ae9adf083916ee2069a805e0f2cd00c76db9250a859106dbbff4430b4dd114d6293c4b3c2e9cfd31f07949f04e53f63423a08b56d7247772d07959d5d92b17bd8c7c0b294b71d3db903d56509177"},{"message":{"validator_index":"891060","from_bls_pubkey":"0xb4582d56f8ad9dcc77eb5413558e63a6b562e42534c579a85384e7d7d6ff8974ff933d05a444c1d2784945f4cd1c952e","to_execution_address":"0xdbabc5418e28d8265fe892d2129c8395d0dd064a"},"signature":"0xa7f07c5a20159b029b2dac119315a0d439c541e63b0d1f6d377fd2867e5559d6b6302eb609d5796fab97cbca121ddf400c840b9ffa60dbcd89c6d441f84aff2cca1f68fd9e258a969b0d511ad1d90c0c783dde3c093ee8cd56cf6f70a61fd77a"},{"message":{"validator_index":"2123298","from_bls_pubkey":"0xa5849044acc283563bd9b40fe9b01a8c079093829fc3837cddf20a8f9c13e59629251481406f415c8e2df65285ddb41f","to_execution_address":"0x9ecb7542cf4bad14a20f79bc45931b8d1483242e"},"signature":"0x81df97c3071aac41af79494001a1c4404b5121776a71d6cbe3b8eef000e803f59edd2fff33331d2ea037faa919ddd6a115e09bead88d7c8f23368628f306e3a244f2ce0a54e4472d29e4b79eced6da3e5ab40177e96fa0d94d97f5e07d2e6e95"}],"blob_kzg_commitments":["0x23b34c422f5dc8f657e44bec0e51ab2840981d2ca63caaa51da14231033a661656d833a140f1279e0a1e40020f4c8be2","0xea4f5e424f7a2a28771b166a93b66dc12d8f207683e22f77941d78d8741740768f79e18451ce86d434d576fdbaf45f2f","0xfd705842efc5096d6c5e7d95673f828e34921f0839ab5831c29ebba04e78f7002799a7e34b2f67c27bedb9a981bcc315"]}}} \ No newline at end of file +{"version":"eip7594","execution_payload_blinded":true,"execution_payload_value":"42104374537666016842731412608176468386512470599052556672967227278486679620790","consensus_block_value":"50756583220288449835724789919752990744036228048165053817930899246206127260481","data":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload_header":{"parent_hash":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343291711b9de6b9dbebc4c9b49","fee_recipient":"0xbf886c3ec849316e3b187793c3a4398b6097768d","state_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","receipts_root":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9","logs_bloom":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d078748f9069c96e9d6a2801cf607000a52447e46e1bef4e056ee30d4bd3517aaf7bf65ba04dd28c3a4a14b8dc72a300f051722a6814fa3931d90a82d23285d4c1127b6c67bbc4f8682ddbf9b31eb3114c26dccc5330109d6f17799339c2d7ed7e4e3a7de5d515106aaec7be6d78be3e21806d6d30c39b77c75dcf354b63033fb200b3b9dc023d948278f0956c0ee99323da0162f2a84b6a95749d2fa1d4e089af416d412ccd992683f7e41f7b496ca04f9f463806e3643d1c07f39d2a65f84e97b7dfaafac740d1e03f30923a4270fcf651ad2ca3737859a524e86e02229a55abd1a7","prev_randao":"0x0c0d553e4878ae811024144112c88bbf79a372d5dfdf39730cede08696ad52d4","block_number":"4489858063226749928","gas_limit":"4481595642848361992","gas_used":"4479943159631677864","timestamp":"4484900609281730248","extra_data":"0x6bb2373e68f20adada72181a3474f2c098b26daf6fcb0516f0723270da91e789","base_fee_per_gas":"91973405088222260025272995045243630915786868313949746451634391325697029602367","block_hash":"0xde78143e27b846779904841e2aa96d8fbec4671bb57ffa72037ac721f8d633ca","transactions_root":"0xa415263e48d5a8a8ba3b4e9caf0e3028abbb6a65922580447af6fcc869b40d2a","withdrawals_root":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","blob_gas_used":"4476638188903342311","excess_blob_gas":"4521255257228650249"},"bls_to_execution_changes":[{"message":{"validator_index":"573888","from_bls_pubkey":"0xb8343e90edaecc9df1223293465ec067b3c9804f43e25817d27f1f4785bc5f554462032370781d9c65ab27bcc3d21415","to_execution_address":"0xdafbb23e48beb933bcf49f8ad83a43ee157382b5"},"signature":"0xa519e1354ad927358404a58bdc19113e5fd97d5cc19943888e22105ee943ca216a14898283fc3712500ba767de00022905e4198939b44a5f5a43fa0c87252969c56a26345135572101b257f87245a5e42fb2407a0ee67a6c2d039bf908b9aa8b"},{"message":{"validator_index":"189139","from_bls_pubkey":"0xa9ddce0cab5b51d3d2c710396b85e3fd7a87f1738fb5cfd5a7b25dbb483c167a80e785cb4ca7250c14a60cc282b1d9b8","to_execution_address":"0x9946783e88b272e45092a83c1c9310f154917869"},"signature":"0x8edfb3b9ed80067d0626019a1be330bac43c7ecd813f7ce781d0e6e34fb583803e9d2b047ad3294d6d3a54d020c68231085f7d9085d0afefb047def063a4698277e66d4a560f4b5bbd16586976f0bcf90177c00abd4a1b4cbd0ac393e5b904b5"},{"message":{"validator_index":"2357271","from_bls_pubkey":"0xa287d120292890ab1aa49bae1e3cd88bb160b5640f18c64f1aabae5990616e53099fe61698c3b812e2bc2ae6b6965960","to_execution_address":"0x09988f43d11dcf2aa7811c9997eb4119e8f153ce"},"signature":"0x8ca190827c66ff26c1fa594eae169b7efbd84c9456304f2194df7b0c204b0a29ac53034c9b20e4977b8e8b46d6b246da03a9337d3bf5e6f7ac941407a2a3437d7e2c0dcacda29b7623141833e02b4b12350ccaf8b27dbf96b3c520078f49efe2"},{"message":{"validator_index":"1972522","from_bls_pubkey":"0x8db8ee645b614f990839e4d98fdbf921263bb62cd917fb4eff9084dff23d7cc453f6cc645ad8b869aa9d31a6b9560630","to_execution_address":"0xc8e25443111288db3b1f254bdb430f1c27104a82"},"signature":"0xb0c3172e9bab8d04faa5d27f9818c36ad61a71b114f5bd9dbe77306be3edef2bcb56c215511ba76145006daec95f24be0f1f0dd24377cf7b440b5cdc7d0b520d6b64c539eaacaf14875d49c293af5974751bb0ce2daafde3bd01e097a466e75f"},{"message":{"validator_index":"48778","from_bls_pubkey":"0x8ba697cdd6f8c34a1fb96a4c88f03360d19515ccc4e1ea24aa5e80075d821059806a0047e6bbf5d908d312d1902aff5d","to_execution_address":"0xee24494351a9466526a5f3a1825538b6331648a6"},"signature":"0x87fadfd11bc5612e06c59d576c91599bc21095531fcc27a177967de7b521c377ee7a2b10d0fadf38779089929cfe136518757803c369b4ce94873e28d7d9cdf54c31a53ed86b07f76ea6104ee65d76de02267a4b736c949785ef233cbb73ad4a"},{"message":{"validator_index":"2820011","from_bls_pubkey":"0xa32a5f28ae7d36f888820160335232fc42ef994b4f93acf6a8659762b2ec52ca79354cc07c73a229b529bfcebc705eff","to_execution_address":"0x4a4dca439229167a13e413e752937416aad35d1a"},"signature":"0xa2089742415bdf32fa2dde853661a095ac24d273413687ae04fabb99ae2982700bcdb885d239e32543ffb95763a43e690cb1bf3a33df40d24e12c46d150e9c59dd63f960dec39712dabf74c08a55ba1bcb6db664ff9d5b2261da353e4374466c"},{"message":{"validator_index":"896267","from_bls_pubkey":"0xb679b4b686530827b2a201eb2b18454e9a5758d7257737b29bb215b9f354c2ff57e912b19d4a051556187aa24c97371b","to_execution_address":"0x708fbe43d1c0d403fd69e23dfaa49db0b6d95b3e"},"signature":"0x8da9cee45a3046b209da332512a6b4e4d7c89768f55998eb79ee236b4fb1fbcea87e0bba7b05d19ea7b8c5ea6dc0081e17a7ad0ec41566a0c6d9e127b87691e1d5b823fd178069e3f30091dcdbb44c36408656941755177c45bc976bf270289d"},{"message":{"validator_index":"511518","from_bls_pubkey":"0x83b8c61b63de768821cbd82ee3c67c81bb848163d6af0186ffe1ca3936d283bb4cab886f3fbc7f6336fec3da8d542c76","to_execution_address":"0x92fcc742102977503966d35cb217fc55bd583232"},"signature":"0x8c90298abaed4b5124cff46e41c9a4ed2b2baa0d2089add6b64c70dc7547f1a83bed76aba1fac6d36605beea72734b490b7b98994c7c65fdb436286b0df898731f6ad536e5a603da85ec8cc4488b94dc8c61e11363d1cc18733382dca51c7008"},{"message":{"validator_index":"1431791","from_bls_pubkey":"0xa532ee397fdd9e388888d90f712e13b085ad5043402debe1caf3dabbb514ed0d06f7c897e4e2795fd018cd672bfa8948","to_execution_address":"0xb83ebc4250c035da23eca1b3592925f0c95e3056"},"signature":"0x8fb8cb9373db269dd2a05fe0a07484db022a95b06c03807426a352499fcb65c55f8c388fd4cddbdd9936d5fe5ac5898e0d8b58ae09a73bdc7e584fe9940d3aa967607a0c4a1ad1ce5ccc0ad83f63a273e140ae0510f709cd0c214b645d68e3f4"},{"message":{"validator_index":"1047042","from_bls_pubkey":"0xb7d85608c3cf919ee72c0481283b468c2825850f6f6028c000cb19bff464556973909667d0353582d673e1049795f20c","to_execution_address":"0x778981428fb4ee8ab889aa659e81f2f2087d260a"},"signature":"0xa1079cff71763f60894927a0ac68cfff88642e5ec4e11d1f63ce7d7b15f2567842c80c0238a0f6e4d38ac2a9d09787c50c87daba460e05a0336332f1d37b65fed7526c5eb51a84d3a0169d09ddaf271d13710d22469e8dffde8859d50a2dd0a1"},{"message":{"validator_index":"2279280","from_bls_pubkey":"0xa46cb4c6f51759dd36e897cf8f5f8a774dbb5968defec8bcd85b9ec0f3d873a6569fabde6c6cf3fa5dc77e910bc39938","to_execution_address":"0x3aa93143d0d7c378fbb0904fd1788aea4c2244ee"},"signature":"0x988ea703ce8fcbd5bc7811c49e1eede7061ce461966a9a52f03afdecb157f065a1993fd71ea29c6769121610fc9e3e190eff938fb8c2f77dcf5f511208ad23cf427c05dd207b6c6004ba2a1ee3b6a84949e39db4ef1ee254635d3527010f7794"},{"message":{"validator_index":"1894531","from_bls_pubkey":"0xa18343c3306dae4ff3c78428069a4ae7876f0ad620219648b99b4bfaeea1d7898df50d508533e756f5903efbdf585076","to_execution_address":"0xf9f3f64210cc7c298f4e990115d157ed8b403aa2"},"signature":"0xa120e4f3144799db31e7487d25cbe6d8724f0076f23fdd7ff1f00b24b304a93a97862a3ebecb5e1b91018a0496a3c4020004b5d49571f4b9a3faf0f9d8f1f067d7005b5600db18872732313acf1350e1bec278384f3e0fe28d43f00203ae10e7"},{"message":{"validator_index":"2970787","from_bls_pubkey":"0xb23734206f673528ad12bad1b7815a9db722d7a5afffdfac97e17d453fcd2616a804619bd9f8db50b9547a357b1f5813","to_execution_address":"0x2036eb4250633bb379d46758bce28087974638c6"},"signature":"0x8de01f498b48fd1df0c20529228b7e8616c7bfc35457d392404110e394db4c884dad325363be1f2a83ac383486cdea460e78e89a728ac9464f71dfbc685ac8be3fb9ecb21d67a6c105354c58bfb78f2adf7ee65f5a4d7fbe5989e522b52daccf"},{"message":{"validator_index":"2430055","from_bls_pubkey":"0xb490d2df5759bb5115690df9aa805cddc1787b17fc3984ec400d03ccd5c6da6dbc54a724816ccf0c86b4b23e4daf0b17","to_execution_address":"0x42a3f4418ecbddffb5d058777555df2c9ec50eba"},"signature":"0x909ac7032213a33af76294ec19617f3fd9859bb22201e0502ae7187debe740c5cb0367ef03e944eab7fdc5ab23d303f916904a1ca5f7aadbcfbab89bdd82931dd7ff3e0efdd1135698f54774989ddd6d8ee07bebff863718c927072564a547bb"},{"message":{"validator_index":"506311","from_bls_pubkey":"0xa2810855686190fded08fbafafc427d3540a58c2b391c0d05a71be7a4d1aff2b4ea501c8e4c1ebb79cb49f1991ada976","to_execution_address":"0x68e5e841ce629c89a05627ce1c6708c7aacb0cde"},"signature":"0xa108770fd60463dfc982d8725440e47c54730329420bcf05a969e4937d06e468385b53c4a5f6c69e55a775f358fa0948171dedf3bb0ccc1679280251b7abe4cc644e10b46bcdaddd590951541bda68373c8a8dcbfb86d3cb97822a5dfc21f481"},{"message":{"validator_index":"121562","from_bls_pubkey":"0x8deafeba9f0184ffa1f3d1422b9d97d6975fc4d5a21df265b48b6e831d6aee5a6236b3d5fb9e03cab1e0795f3dd45206","to_execution_address":"0xc40d6a420fe36b9e8d954713eca4442721892252"},"signature":"0xb489851f8a8fd535ee14505b9ae32ab27cd8d5e637236f491f71bfc987316491ef3f1b7670378875580eb247993d82511128502ea093d108730e070bb8c5919b39e78893139b3f1a499e885b15d385073e227d6a4e85ba0413ab9e2481d0b8da"}],"blob_kzg_commitments":["0xb1ec6f426f978c599752e0e7181c305a1b8623c06088b5480b9aad7fe5f419d6c81a8f6862abe50a5e8cadf8649d347c","0x109252428f11e9b162a1e4c03bc8965b3a951e9aef7381ebf01fff6828d9ae8bbeb86d42469047b0c205fd55488427fc","0x23b34c422f5dc8f657e44bec0e51ab2840981d2ca63caaa51da14231033a661656d833a140f1279e0a1e40020f4c8be2","0xea4f5e424f7a2a28771b166a93b66dc12d8f207683e22f77941d78d8741740768f79e18451ce86d434d576fdbaf45f2f","0xfd705842efc5096d6c5e7d95673f828e34921f0839ab5831c29ebba04e78f7002799a7e34b2f67c27bedb9a981bcc315","0x5d163b420f4066c536ad816e89ebe88f53a11ae2c99624d4a7240d8a925c8cb61d3786bd2f14c967df66090765a3b695"]}}} \ No newline at end of file diff --git a/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsEIP7594.json b/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsEIP7594.json new file mode 100644 index 00000000000..1cda2c0e689 --- /dev/null +++ b/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsEIP7594.json @@ -0,0 +1 @@ +{"version":"eip7594","execution_payload_blinded":false,"execution_payload_value":"18191970007912226669631394668547651071148255645822697200640823429642410377933","consensus_block_value":"61453013339935582189619461221462653003808078281923085412032520595023747176323","data":{"block":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload":{"parent_hash":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","fee_recipient":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343","state_root":"0xbf886c3ec849316e3b187793c3a4398b6097768d06bd968a54e8d2652d2a75a9","receipts_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","logs_bloom":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9f770a36a743c8a3abab61dc439ddc0604dd5015b1ed3835787d9565dee0f3e64b25de4c097defe3001f483a4b6feac22b992cada114bfc709d483b4d94f07bb0a1c4fb9e93ca3c31f4b9683753ba33ffd971777e301367f1edfe6809da491535c711a7877b4c97fd1a756136c412b4f3c4471ba439607333623558a63030f2cb6bc2ba885822672de14ea697d44fbcde134b6909208466be0b4c981658ba30f999c991aca746c3331766af1ee10cbe69624066708ae086999a0a3853eb777b3f9f0455cfd98a98c7719710515b97c596d2b662d353a90206e470c523d4374853","prev_randao":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d07874","block_number":"4491510546443434056","gas_limit":"4489858063226749928","gas_used":"4481595642848361992","timestamp":"4479943159631677864","extra_data":"0x58913d3ec8a62b95e52fb1ee60ebddf392af6e1db902dd5bc3f1eea7003130ff","base_fee_per_gas":"48712354854557871613352262057776104244427151172201944877932608112921551169417","block_hash":"0xcb571a3e876c6732a4c11cf3562059c2b8c16889ffb6d1b8d5f883591e767c3f","transactions":["0xb736203ee72088","0xc7dab83ea972da","0xa198c43e69db1b","0x135fa13e28a157","0xed1cad3ee80999","0x60e3893ea8cfd4","0x39a1953e683816","0xac67723e28fe51"],"withdrawals":[{"index":"4864971916622804241","validator_index":"2164897","address":"0x09988f43d11dcf2aa7811c9997eb4119e8f153ce","amount":"4866624404134455665"}],"blob_gas_used":"4858361979461100433","excess_blob_gas":"4856709496244416305"},"bls_to_execution_changes":[{"message":{"validator_index":"625901","from_bls_pubkey":"0x8b772ee4cbcc67f534f33102671346cc3d0ddecc1a81f0350f68dd3210681c9e4bf907b49211cbd390bfadc7f285214a","to_execution_address":"0xb5c15a4371c6a89646dcbd1f07bbfa4e210d4bf0"},"signature":"0x85cc5356a9646f0ffd512b7d2e7d3242c81303a415e61b490d28635896aef1f2db03ae8a1439908d03cc131515ef83f003dd7b36ce480c43f4495ffd339b2b9d1e5461309a02ce193202f27d216a4f0e13f7b47295f3e1a44c8f0e8ae8e1e5a8"},{"message":{"validator_index":"241152","from_bls_pubkey":"0x8183f3f5071394e20f83599fe297dfda37f77a040362da8f8fe926a451eb9cfd917e953c51b81619f8e4925fa6177b49","to_execution_address":"0x10eadb43b24678ab331bde64d7f836af97ca6064"},"signature":"0xa19b99131e621d31846245039e99d6540418acc08844a3996544cca2d9965f8a24cc49cf695bd78959a784e1b5646b480b88aec749a62ea934ed3001af50bb8babfb15bb9df1f3d57abd738f65de02b77398c82302f500218675cd96ee3b2357"},{"message":{"validator_index":"1473390","from_bls_pubkey":"0xa83cbdf40e5c4bdb4e9802d94d765c70150d9926521b0ec4d273e788b83a9f304694e75d2e381ce631b24121ffecc9d4","to_execution_address":"0x372cd043f2dd36351da1acbb7f0a6049a4d05e88"},"signature":"0x998cb975f863e95fd53ee74c5beb85c19b8b3858773432a371e6a3f229f67b653165adf3bbaa6015dec12a1d13cc9d5c080915c55c921fb056fd32e9da643d96dcbe83ccd456b3072dce2610d7b96e69488468c83d26b7251a466571a5424351"},{"message":{"validator_index":"1088641","from_bls_pubkey":"0xa7f8aff7b912b6363efb810f2b661643624ab914b034e78d72397ef84fa04862dee94c9b2f46b872fe852f197f558fce","to_execution_address":"0xf676954331d2efe5b23eb56dc3622d4ce2ee543c"},"signature":"0x94773ae9e3d605ba2612dbea934955e7af438154f7572136c97bc3f858144ab833aa7bf6caee2a2c4ad066d36b1d0c3501359ff577de486f81a5210258cf63ff45bb0cf91526eca949dfee984c6757a957f9c7ddfb8b599d3cfbec0b778e9396"},{"message":{"validator_index":"2008914","from_bls_pubkey":"0xa6c59055cc0bed5baf1a815f59d9d1cbd7aba1a4fb8d83de7310ad85640524318110a6a16f5bc141a81c34e103d9b7be","to_execution_address":"0x7fdbcd4270dd970b44236c31de8ee788b75533a0"},"signature":"0xa28a524424e49283f416545acfa1dc063866bfe9892c60370705cd8133fa949d724421b1ffff5a757d573391ab2cd9fb043022aa8354e4e93c91d126cb40f9ca7f7be8e8293be5a97b0eeb79df9c051f90e4d97c9157efd9b64125cddfc257f3"},{"message":{"validator_index":"1624165","from_bls_pubkey":"0x83190d18858cb148b28aa89911959562dca6653f220f8b4878a5d580958dbb3ca184d97880f7c2bf0fa970cd41b70dce","to_execution_address":"0x3e269342afd150bcd8c074e323e7b48bf5732954"},"signature":"0xb469d5c6626f1c42e7e914ecaf79388360d2ad196f2edc1f7b6088422b4f32f43f36b12898bcccc46c5ed15285ff0cd503f0ba5f6def9b4c1e523e941f1de95263bcdb014637c359464eb2cb974e06faa164827b21ee15dd68b2375b7e76a700"},{"message":{"validator_index":"2700421","from_bls_pubkey":"0xb7ff61729743df75a8b0b7e5b95617b9aa407e2e6a30cd8101c6a4c851b2cc366cb80e68a19a23e19625a596fdd1ec61","to_execution_address":"0x64688742ef680f46c246433acaf8dd25027a2778"},"signature":"0xb29e4c2b22ba8da0947be521fd1710125a95d9465632c3d2b5cffafe6e7070f4a6bd71385760b2b1670add9981225a18060be73e5f486535919fd3577b7ed850b3108dfa0fcc9215cd9d526295616e09619e07977ac7208edc5a2af93835a18a"},{"message":{"validator_index":"2471654","from_bls_pubkey":"0xab0a4039f2f00ce09018af228a696b7b87c7bfc111e7782bf7a3ffb423c681c04fe335152668abc7d20b6e9a9bc4933d","to_execution_address":"0xc090084330e9de5aaf85637f9a361a8678373dec"},"signature":"0x90f650befec00b055e261a38b4ea0bc65a0d71fd735b46f8387f565fb0d31494f90645c40dc07b0f3ee26d7807b82bd914d4d7c81b3ffeaf9a32730ab7cba7265dd09a0e0f94ccdf2ff3bc53d49fe99a488cf7238200ae12e6c59960e71d5877"},{"message":{"validator_index":"547910","from_bls_pubkey":"0x83e4d3825bf069cd0b19ca5072eda2f7d141de02c9e65f9c0733c18252c1552cda074eb613e1f435a880262de2a4672f","to_execution_address":"0xe6d2fc4270809de49a0b32d641484320853d3b10"},"signature":"0xb9b292bb598db604142750cb641cc511a9081656efb8271132d7e0de30554dfed4b16e418100d9085951c1502d6ab657179da8804cb08f1c69b1210ce94bdf6a0b66976233a34a0acfb4b947cdc192cdbb8576a3453e50143e7afecc8cbd264e"},{"message":{"validator_index":"163161","from_bls_pubkey":"0x86c03ea323e3551ef39c8c4e5355c4d3a2cceea3c8acb3d947b39e245d2ffcab53b4479c670d8b268828fd4fee89eae7","to_execution_address":"0x08400642aee83f31d60723f5fabaa1c58bbc1104"},"signature":"0xb58eaaba3ba51d7098d65fbec3829ace78576a2276fd9c97c293aabdb634a2c50f52611f48088da5d4a5b5fa2c5f4c0513d8dd91c8534b50a7b8ae0072583612610ada0c81a261641c66ac542428cedf20f1b954ad03505fc058b40ce0bf4182"},{"message":{"validator_index":"1083434","from_bls_pubkey":"0xb54bda7a570f90c2d38e836a3a256a6a2230a6384a29af7dacac3eff1a981d3f50918e2b546b3d78e72a545870b5ec9e","to_execution_address":"0x2f82fa41ee7ffebac08df14ba1ccca5f98c20f28"},"signature":"0xb851b39a32955a7f05acd7707c6859df4ee2b1472996d6a805a61e14415db550a92a7eaaff14a67e858a9d3633306efb12a62ed84f76387a84deefe726afcf2fb744f616f67d144411689343e6e0dea7a88b57449b2cdecb43cf0b5a80887550"},{"message":{"validator_index":"698685","from_bls_pubkey":"0xaa3588a5cb0b5d8eadd316046b661044c97559a4350464e338456c5b728880b4750b94af5fcaf478e3bbc86ac3e12d0b","to_execution_address":"0xeeccbf412e74b76b542bfafde5249862d6e005dc"},"signature":"0xb99cdab802f2f2683fabc52c8ea095386730c43694a9a5f7a42033e6dea53f4896092b207f56b1402c5c69937a3e2fb41958e001895bb43c2ee1e360da601e1ac56ffa8bd5371b1dcfe85518f297f94c02cd4981a5961201d2c2fb4d2a15c888"},{"message":{"validator_index":"1930923","from_bls_pubkey":"0xa55017fe14158ad9caf1d11f971b71b1941799466d063c6c77d7e41e20d5b74fd7fbf969243f3f507f8c04a5f76c3722","to_execution_address":"0xb1ec6f426f978c599752e0e7181c305a1b8623c0"},"signature":"0x917311e1a5f7a689ceee1af61f06519a3e4c6d68a4af6f4d24da0f57a2246c963c964d0e576607222856258c0e34b0b1014b68dfe481454ceaf521bc6f87c15e6a21f6db1c303b2042d5857ad4506f00dcfdfc5e65bbaf1b4ee9fe7ddf7b738e"},{"message":{"validator_index":"1546174","from_bls_pubkey":"0xaa865744dac51436c21adc2a1373eb6b8d407fda20bc67492d80a43812dd2aedee636192b1fa742570ffc2833ec58b29","to_execution_address":"0x70373542af8b450a2cf0e8995d74fd5c59a41974"},"signature":"0xb875609f4aa01bb03c08b4f13459fa7696b969fc5e8440c89f690478820b8b5b4ad75e7fbf03c4b0e919cdc80b07857604bd81f75128f2bbc61861d0b5a7744e21eb4ad008f05b46be2c2780900a7913abc2cd3591390f29e05e2d5b2dba570b"},{"message":{"validator_index":"2622430","from_bls_pubkey":"0x99c16f59ffb2e2138feb9b6f1804752cdbfe3796e20c52a3ae489f8348df4c1a9614cb6ce6860bed51544aaa1d22cc80","to_execution_address":"0x96792942ef2204941676b7f0048626f766aa1798"},"signature":"0xb9196e6383fe7a9eac1809c48fe10e45ddf57d6ee7946c22d48873b45064a39e66f861d7b36d82699f4b1858c3ef093f13fd758af1ff4deb2b7e1ffc7a7179306726cc556abddafee546ed2a6d7c4b17a1498494d994ff4188a2edf3c261a683"},{"message":{"validator_index":"2081698","from_bls_pubkey":"0x9786334738ef86988505249871273257e40b3e3c47995e751a40a52bc46f915fbaab7e2b1802ca3dcbf2db0567e8c9ae","to_execution_address":"0xb8e632412d8ba6e05272a80fbcf8849c6c29ee8b"},"signature":"0xafeb0dbcb7463673415ae2897857e5b13c4299ee60273bbc406c38f4e805cf7bc147ad40d7873740f3d261bd592574e618efe8f93cf439d13db8b86ff91918c57578b1080c6e51cf121d816eb3e5a2003ad57799d24f1ddbe495724d9e5a292d"}],"blob_kzg_commitments":["0xf14921410d6e44af323bde913793c2037f32eb41f938cabb3c5db5168485eeb88923ac822543db013af49d53be186cc8","0x046b1b41adb923f4277e45bd0b1cd7d08535ead3b001f37569def8de5fe6a543214372e11fa4bbef810ce1ff85e0cfae"]}},"kzg_proofs":["0x3ece09418d9cc1c206477b3f86b61438983ee789d35b6da4f361c337ee08cce3e8a1c4fd0fc75cb95755aa04da37fb61","0x51ef03412de8a007fc89e26a593f29059e41e61b8924965e21e30600c869836e7fc18a5c09283da79e6dedb0a2ff5e48"],"blobs":["0xcb072d41cdd6852547b50f3b90819969722ced1d8ca77847e05a2e86cfc37fa35ae41fc530811a26acc317fb2f89a4fbd13c6876261f3f7aedf916080789e969b18e152f3c9ce7511d6aed2a6c3c7eefc18473742e0343b6ea6965c1e9f240fa5aa4a90a7fd4ce8c81abd7536ffecf3f4d3c78c58d9599280f217e798a2aeda204de9a8cdfbc36868414133bd5f24401e6c476bfc18f14769977e3dab02cce1200d03dcd13c5d92a14b067862f180e96e1f1146e5c017af54a64e68c550cd8d8c43a57e925800dfce50bd9ec8132dfb2f4e0fc1a847e698ce74802072805e4616cebbff8f63a3c45ee4ca2f6c485ae7dbd620e95729e48b9b9ce35e20b5ea1b7342c7494bae28ac5ea53f0c36460c10d3bf324a8f3c9d0850dd3eae4961bd0f6ffc4f077783be568d8f10b0ec275cf3b534274edea591837b4633f4693822f4ed38bffa38b66ea187c2ce69ab30847004c4cbd5acc05319183cbc1ad806aca5a5b85554b23b97383db9238a7ffe5fb945302debc22a2b56dd4aef14410608be16495a4bbc5e2aff1c3a1ab5fe32ecb2ef8827d980b2fe135043818c8aa97423960bf0268c7630cf04138b55d8ff3fd6aafe2d6c1b736098af35349e4e8af8733e6f6c833d5536794281c2141a99d747b5184e45aed7b950187fe9f581a4421f62f804b1f6f7843a5918e75ebc92ae5b398016ecd86f8ce77299f0741c14edc019be00d8268abf8c155f0a596fb36b659a6a3bac4ef2b20cb467524a9145cfd962a5f541069911af19576c5fc40d7407f7f6bea20abb9a04dcf142d067e8cb5c2041535046ca08fa733efab0a0d4483428aaa375ccf570667b9f2c67e1d6745a4f28d8ae4417612705695be23ca538efe142ba3e0850d55117e0138f8457cb7e3e2f8698e0c7f17d8eaf54ed153c537decce8cc5a89c1e8ea9b5d7fb8fbd75ce766e8fe57c5eb502c1de441d47b5adbc54859fb2caf18b291100a270d7b44751036a3ee5db6f84adce47df0f385c234b97f4493c35ba4c0dce3bce0ceb35cad2aa904a54e0184da1174407591a9549cb94e2d82b2076174529dbb3fcac56e5c4540ed2c5118f45726c92cd7fa959a3098f3495d31c184f0eacbc7210e892eac601c4376f7436ee02422fcc4b5e9aab65e950c37c2aa9da9c77e1b99e0083814b483822b861d5d17768264d27dba533bc1ba3b6d8d7800290acc7569a30262d3d65fde783e17472227316d697e1015bf81ce9af142f4865f5d4c376a4169e050bebef066c0f4f4d8fa3ad3e16467eb76cea322cf1f7d9e21169e8e5fd6e4368f5953fbaa0e4be86399eb7d704332ea6b6aecc9ed0c84a6a608e1b41c234dfb1a8cf3b70e370922c3bf5703e1e153a683dfc0dd56aa0e9f25523b3a010833681f6e1ab9d124ec38f745d73c4b05a5703b101dea7b803527e95e556223b059bf8a02645aa6ac09c0c62f85eb366c7360a03e6332427fa7cb7466dc90b94f357c6c0714412c7446fd59c1bf6cdfb8fd2d4e05d5b9d46825a5957001c694587256f71f906c0af1e1ef26beaa7e8947fadc7ece1cdc7ec4054778b7fa2abecf6e14d15ae2d60a5ee7a6e78dad141dfb1137750cc375117cb1fd1de47fae89ffbd2d9a0239a5d92ebde79400f23b755e5e16ccd5bc9c9454265dcb334db1af19a43dd7b966e8493d9b249fcfe80f1815f47f748fd9e84709761f4a0ea7c23f7d9f45a61e60ed41a50dc3dfc7c3be487a558e6cfd544c33ee465318f6d06c7e3cdbbfe995c01db10670b2fcbcfa9da579fc2b796a477ccaa551dade049211002fbc81d368457031af7b523804c94ec420d39d6c9635e964a3e337c957bad69254596ef912526936ffb3a9e739adf27d25bdddc3c180478cea5eaf98e899a2c98dfbf85d7a6babeb38f5eb0882ea6fdda00dc5a47cf0a763b9b9b28c5185283ab7a27716cebc1725c9f050b44908c6e18a0d0787d63220a992fca07ae35605c004804579f3927e0205a83a61cc5272861779fb112b5603146ac5d4ae46e9eb56127bb5fdbcdfdf2849f5ab394556e4c3f10139f90f51aa118cc6027198a1e15d80afd437a1ba3bb361020ff6c167027a75caffe1d679cc223844e84598e18a5519eafc994bddf23c24ce4bfb011ebe7675ee739bad0c685bcb9a49480193876b6f2f37e62b25b7a72fae2eb5a9d5b80acff7a733a6a93ac7c6b440339da4df6498346e791c160c7fd7c91e67572bf8dd086877b3d7d4c9b4f394dbf7f7857fb92ee370da5255571b5b9b24edd383aaf67e34318b5777fb2866d10af8dbacc4f206dcc436112c81e6aa724027af84fb232a4512fa025c3efd2313e040e8111a9f18f248b7fa09256428f41009159af83ee5f68ffaeeb4426ac5981c5ea3b1a46cf2aaa7d67fff51dfff97c9878d7485f32e9a727ddc9342017ab20fa1660f003db1bf99239343dbdff42879e0a6846003cbf3bffd3d71da557b47c5f45eae0a44daa1f818bab038c9bc45a0f03e82373552bb75b732aa71da1c508499a998c283a8f1a0f59844f7444de3179b3de9cb61b41f140b5bff32b1565a9d6144da3137560625be4ba1b95ad6c8477317792751ec3ef8d790139573df08894922056bfeaa6b4fd2398dc8577a11ad285db3e059a9ae86d75def7232a78e568536af95dd9f5e75c0947233e837aeb06cf1606ff47ef82543817fb430cc386a5db1f00a613a5399405ede8003e5e1cd4c3c0b91b43e17094ff513871af0e18b74fbe68178d8705d81a404ef8e8bc3083bd882bd5a7dfdc943acdf25bb2a7c455135314cdb060ec6309f3a44d7f2bfb43ec259abefe70fd440ac7b3273b58f91442277a207a43eccea7a792f44a3158b070c66e41bfaa49ef147639df1d0b3d9076fe1002f8241c0f7ebe038e73b5811dd6b466a2ddb89b7bff1781ef1541645ac359c93a1f20f7c84143142c883f14fa4cbd76a7da3809d4b8e7d3dfccff856b5795ab38aa5d265bee55397113b9ad87525bc042810bc6a363a9bea791b31dd7818ce71101168427de6ce8e7d52e31c7e91dd1b48b7664f862edda684ceaa2e1e5333362823e4d7643552baa7d9b0142266e43d29231cdc2fceb53c4e08dfb9d6a8c669b2590c60a8af30a723a7b834a2b0c09621d76c1c8eee750812a164e329314734af7cddcad960df2e7b7708b5b51926f505255e81d31bd7d0d7ee5d346e077d489e1e03d16ec3795776027f27d87e0bdf69e6e79bbdf1762f89f104d9c9a1afc05bb762d6e5e43bc44e504f5e43262bb61da177a715a052891be8266f541f2b0b0ca6909cb43308b6b06a82a2cf7885ec9c5a6ce4c7595097d006a62434b2c2e6e8a658c56a98e79bab5f733a349add485e629cc29bbcb87ecf44f925d04b4a6f01523825b27e8133f46e5140620d393b38a7d0dbd636b57998fda95c729a1b3a3f08e3e5169ea4391a249fc8f3cf65414bc043be540fd90f1571d37df2389382424cc178c1a5c2aa9312b758e9fc6f5b24ae2787ddfb96de4603a86844aa9266e7e46a598c697293bfe242ad2f142b90097d204dc520c5f1d67aefd4035cfe32864f2fa01fd2ee704261c1d0a779af81a25dc75a1eec23ac07648251577441c040f89164fc9c94215e4c0882ab9bd8116f7d2839087ecf3fcb2bcc92c4a0c6deee66c7efa1ec666527adbdea4776c416bee4448ae536ceb6007d5211d0747fd37b0f3dbf36c48aeebaa4dda5a8d5561f16e490b38293878b19e827f8812862e4c6bc10575fcdca01d4b738166dcfb6474227745877b648d1a9e375632ab0e7f4589d92e42c3214a766cdf00ce267ce1d7558cea44cddbfeca4c2fbeefe378c3281d1ccb04de8fb691f47febf593dce95335a6c67328781dc48538a946b0751d1aafaf762e98534aaead4ad101c61a80efa8295fc4c428611fa7787da30b13cd8fdb30213d39b1a7ce5e61d22a93a9466cdf18108e6ebabd791316d52a93ed7fd9c0c102cfb9b2b195aebc13f486f8e1f2a07dc010aaf2f4ee1404867d0b5ad941214f71e4b8689e0a9d8e2131d4dd393034a478c5c3436f06abb3a7ce4f2d087be49c1e454f17ae746dd192e3d26683ac584d7a22d4549ad94adf95abe4f566757482e518087e9191f72d9d97f5d93fb87b29492199bd491d42a4ff237fcd784d0563135a0bce2ea1a76e34402a46095ed68fbc4bdb4a7ff99fa99643c3cf11f37a06f5119531995916bcff5990b1d1054c77d5a4c4f7676f56649665bf9c4a258b4c318054364ebd46a8a92af1fde425398fe94089459ec409065ecf93677ee439bbd42fab83db936dfd2f63b2bfa67b63e5712f61f4cb64a0f052b7a01b1b7f7351e2e652d41b329aebc9ae12f8cc1633bd4d644cbb000c59dd8e3f46120f3cfde5d139a08b3f75ffa2f495cd7f24de495b627447dc8d7fbf2a0045eb9f651843c25070be3f91e094f6626c1d5528c7d35b135e6683dc01301b1952ea767cec902d5cc4b341edc91a1720649c810510f4570971ec59bdf524576f8ed482c48e9ebfe78cd2e4f2d8742b6139f9959aa27100d94a573d2b436ecec7534310331dd8ee9e37b995e1c7f8869c5edd99cd4fe63993dea0fb5605820251d01ee2c49c8d50feca6b3420f29ba182624460c3f4d639a5b3d8f7e7e61f9c7f17c57ed6bd7c09e88e19295a8e77e216a2def1fa02de887257ff2488c93268b6a8529106ce0d1fbabc0f5153896e738d6140f121a7d034ecdd98894e3a39ebe3f989b600050f53af6a33923bb5bf3e6f54f9f626f6be5cfd952eac541ee54c389fac32fc038fda837f2fc476dad4e0522a93054472d7f5f513d20a4e03f328e2f3594d32f95cb42c373faaae8e2ec4557845a6e82027a46b4d668dc1ebd552aa5402624dd0e193b91b4d59ad6fd06ab5c7854fc9e02b441c798a27fb8a3f035faf9e2008cdc5d7a3198e30a3087fc0d3a76f28c2d614f002e0ab37f8febe55f38bbc3c87289c2198e89750f0ea48d97fef84872551c85a9d5172c360408b5ef113192e086b726e77df0d0640ac4e748d7f7b3456947d8532f410adfdd4b1d3de0c79232d92e3ce726d7c7e72f8b08757594d8608b63fb8c3a21a51ac3b15539f24977ac02cc6ccf30e9da197bd90c46c5bf7e626278b5d45f1ed36c420babe32a89d0bda01fc28ac736e8a2ea111c34f44ecdaeddde58201b4bd0bc6ad8d4d0ef2d77dce1d04db76d65adcb1f03c292c73a88ec09f0e95876c1afd8f34957ac67058d012215a2279f38cf038f13b40143036e86f509e8d95303feeef62fd7b20869c92a97cec162439cab5b409330f99e71b62ca59cc7f38829c21337d75a367799bcd80d249023f09af609205561478f3202501831906bc8eb3abff916111cb426978fb5a32fd8fbe905e9def16c89d902c0ab5d9cbdfb5ccb8b4f6f115e5e9bbfcb5afd42f12360f5b9f762562cb43b6829b6b7d077ceb3ea0d82ab689b89b202934480cd6b5b6c9526d6c77fcc8fa2307e32d1fe7004b75b1ffd2dea2f40472af2df0195140c48d8784cef86fe35b03113a101f93d34f8ee1ac7faf260b8ddad66c17a743a7345891ac4a15dba9792021ecaa10ac9e5aacd99cd420e863bef32c203357894020460bfca3edf02f829aae43970b593136f683f5f9c9015a5f86613c1f8bcade26201db93fd168c6f7f7b1dbc0507d8b1eb8417b1cb3157ead89cb19c4858586c433cfa72bddd9e688b9ad8e759783e213974fd5b1be7678e7f47a23bae5bf9af1b2c9d64663117234ce341915789c9bd1bbc9ada4480a02bebab98c51645f0ee582b7b85d9e7e4342c2de755818acc520ebd4a0de9c85eef7764c1683f6e941f92bf8c99719f0801b924f412a6463fa01c1f98b89830a71b7e8f64d6314d436c8ae9d284130fa147a935026ba57e84db8d0f18046d3b138540c32ac3d097c2b6038396d2ce173f96333fb0484469c52d5f991efd2f3d07172b217a68749cc90d6194c5300e3a61d0b40c335311a958328ea226131521384d6122f2039761dd3919cc27f15750a9aea6cc747c596a477a3358cf9a014020bb9cf78555915ecb35f9815e8a9eb1193be6ff26762c9be70a6d13004a658720855b96162267dff6ae36f00715211d69574a9b5f37716faedb18011bbfa272b1e65ecdb6b4a3655d88528ce8d2db1147358a55f77c0aa3d25a5f1626fa079935a979faf4594698ae59cba9b0a288733cdd735be4430b2c12e60d1c79de56d2b5b3ae3b9fe1be9414ec9fd0cf8b2f9faeab72e4394ffb4ef356c1daceb4dd330d779f357821fab251c0a171ca3747516dce65614aa72b472570e0b18ea71f68d1804ac93b727e0ea98797e2b17465ff1dcac63fa7171bf538736623c3490d2392eb1953243298669fa72fccb1b57f87c9f61f7b5eadea44c68e7d1e790ccdb4e0ea414dda41962702bcc273218fbb56cbffd276e33de23e3766e91233ca13fdee426f6f598616afca14e68a2a3dcf6963632fda85de04f4dd944117b23e0d417bcdc5a5df68621d6431ce6ee21df2c836f7d868366bb5b8ae25fcab9689d4a52cf92e796b56db45924b8101a6315d611c627e119f036b5ff41848e541ae7c517a48f8c2a83f7790b8ccd986a39f5b5eae9fdfac408a756598e4b705ef15a974b5cec1c56b1a4dde87d2582fc131eb633a7f24275e26d24325efbbd3340cb6bed4b6bc05046031c32ab0c83a7ad6ec4cd525830dfac20f567d18ed33fd4171fe2dbd397ac18d62a6df6dafdcdf1fe54e898836ba108e43095d2e27d2884962f160b587f2ffa3459c9043914ca6c34ca5cff4c498867d0d45237673e3431da383540422c8eda6320b612103ac091b7126c989963cf7c5ac400729c34a089848024e222f380c20516d592a8dac61a6e463f3bbdb43af632436d7f379aae8fa5a02d81f4ffe189f84c151345d1f6e877f386ad0c340a0946fa59776c82cc7685efe641aba0aaa33697dfb4ac9d8c102f0e51f6503230b5dc619dfc5d60cca696bec57cbae6d45f15f12742ba2913403db23d8eaaadbf205f2a296e9be2a376ad74e63fa30d22872ab5719c855b0ef968cc21c214d352696b174db5f57345f3b6d81fd05182245549c6b71c9e1237d7038c3d4a3751ad55f2e5e1a98cd2c08913545ff5f6d2d2cb8cdddd70996ba3a65ca07a15b6fc43274800b2278105d3682cfcc5f0b1b6fcfc271344a16a79eb7ef46fca3085b8c9b2c4343d5d80af7490b59cb6725ea48f81dae1e1eb290e2ccaebbdbe3c0955812a239925a05aef5268de747a359d150537408e6a3554847b5e2bf7209686fe0ae360317233ecc03844c9037506a9389c1fae2f0b7a83fa603f38481d73c3140c309e05fe639c3738cef002006adada063e308680e0b37a91aea761f594e74971484858cbd9ef691b803a844d98b968b7ce94a519410e0b9b3f56cc26ffec7ab7fd10226dc8b1adc1e786d204a024c1edac82bb199fd4f5316e769cb1b87a8c9c760b55e010e4ee78722fb2de8253275ebc19c85984f421fdcb5ecba9472c55d1b644daca0ae05ff3d399540ad98414c0e1bcaab54322b8981fad06e082198000f54e2edece3c5c6e8d982efbc14869de59df702c1091ab18d74bf0ba3464aee6c6a17906599ad68c48306d661e9ec20bd166d5e8eec6be8cf852e745368ec243c27506141b2d01a2b9be6a9d877638716d6721889254e220cb2f26e9a65fca2cfcdf40717816205b7e8856ab7b4458b7e325e58f2b2100273285fdf94eb3b8083e4c64c6f4fb0d339168c2a85a91c30b103ae5dd2c748716d32f6b05bfdd6da0ab645ab81d9eb7966371e3665f5accbe5273762d949c3e796c4a958c674dad2d6d222da37d0c5f9503ea7978e4855b2d4ff60e5c45347cf3cf3ab0d977bfa0d2c8cc63db958d82fd783303da7e9bca0a9f0717267b528ad5c7bcf1b1a13a97df17ce4762b89e1f39c424a400031d23c9fe2eb336170aafbe53878f14b8d7c6120fb1f7f09f1cd15851b84121f0e194871c4f8e54f3b31ddf7ad1bcbab12e6bf02a9c0056f11930178937590f581495d2c06e7f9bc014613ee80511efb1e38cf1aea76fef9dbd913d892563da9203e53231db6fc586168e353fb73264c01fd08a3633094703b79e93ae42729d095329ffb02b392232db1d33e8dd034ec5d9de11382fcf12d8bc033deb86b43981c5250d3b3249af7eb4469264768a6e8ab565e01f40094741c92dbd5246e1c6409967e7cb1084793579652096040a127b68103f02d7e1229b44d7d209e1d570640f446ab81df499cdf78a793b242fb4f1d29d575e3e398240b8b87a684734debbbddaebc2b895350250329053aaeec05d5ef631993f57a424da486a7966ca92e080d096ebe31888a8141abc99b177054a64720e6fbfe99889b1067407691f4d0c8a4b575ca4db3867db812119cf97175a9f76d9a9e188c4e898eb8842f0f60ff329e576fe4a64e5357b78f37a5d5318dd0f231a9447cb713a11e3c1cab5f47a68d87d9f62678ae33827a017a46e476575e7ed40577e9d3c8dfcd46753c822f55b992db1eac9ae44b251eab45b1557a376ba8f1d6061a84ad3450c36314c444d1a7f364a817c22cd09a60a46d3b22a995610e32b98546f986046bdc73cc189991dc8ef2820ec9f399f034b0b3df6a9ff882f30531ccbad2e0a72f9a4ee102a5c6f3f73c25b90dcc746a25635dbb69ffd05fa73c47747aab3deb05fc4d320ccd7c18c04f4044dee0adfe85c30190f20582623cb2115df50799908659c584ceeddf8d14cc2762f5ba70d776cd564683feca458e8d162bcc9560cb251492fe8b30a428ad700bc7066844d4b6848966e4f8579998c80dc4a4ef16c4ade33fad57aa1ed30a41c7ad5b578c06467e969e5962ea411dfd81d30e57261889476b01ce87a60c06e6a0507f7d4836d80b2c4162643cf49aaf364978160657dde6134e5dc76d62ad21c272fb90705c1773dac4092b7cbe939207d6317db98775e93f4c1ec41efe7370a1d824722e29b6dab942c874fe7f237c297483a7db9f7c93a8983c133217b031d3caf70fec7e52d738f9f20d926ae60753ddd084df0d08ae2731ef751e7d69f1ee97045ca1f327c279cbbc19b0882f1f5810cecde731728644fb00e23a0f78ad4d0f6c1f0f51924380bec64a9f338fe130d0c3dc9edf848e8e3fe9e26845f22f6707ecc29b1860bb737c406d829ba9a73924e499c2e173e76a2974d563a7ebd87dd9fb5b57faf47066d498e434445d1ab7d9aeedac73a7619949fbbfdf53837119e1f890445e3cb56437befec98639984efb656bfdc556bbfc132ec9787b42e28a01632664de6b600a3cef3e7e0c456330609225ad7df227215f6a8fd2e5cae6531e294729c99555801c1b02ec561451261d98de41325f6a93e9a4e22c69d518d7a005daeca20bf68e36d21e5f3730f7960e5b078af2ff0b5ad2e1a2217780ec25f6ea75ae9f90ae658dc6623557cc8dd253f33d47bd907ac91d3dc5358eee771b1080c7f12b5c08d345dc0f54b1cd02accd713e7107d1167b3383c165c6342034e3ac79cf64ece7eb29cabadc1621f775a5b477473afe7d666059eadb479985bbe936f3609da8e38b284a2d76a961a0f8c64bb63b321e98fb53f6718dd02149fb9e6d7b86dd53dcabd3d92db062cfc8da5e6d97dcbf1935a3a26a1d913172850e04e797a75f4f4147e10a4f438b95674561fab54a26a7423b4479e8a0d1fa14df204cb57510adaa6bac63283046618b3e0693c36fa004e5fa15001c5e2877ff9a7a356de91512b319b2cf9a0b0448915548bbff22da82fca955f0989d46bca8a1a117fbaa42693bd114a002180ffee1bf660fbfa274dfe13e29d759384bd79a81a53fb2d6a36582fe1a1c7925015daff26388f44e826f63c419f66c9aec7b05219bdf8696f7d4b900aca187954b3a6c1b86d0ee5c0e1a4de190ebe890f5c24c941d2bfc5930c2032edb55ee48af8df895dba7bb509322057a60eb9da5c1384177c783098ef467010c566560b0c7e7bba4b158e36484c307ac3b8785adc43bdbd6bbb5c8961ff35deff5d457b37c38283c1b3c6eadc3984bd23b07330d02ab93da1e44ff743f15ff5732e262a28fe55ea86f4e66af2adad8585a52c69c95e4b36599c5d4f11667c5aaabf38cf41331f230c9d3465a06661de5fb6623032a1437dad85048631c9b065da219c521f0effca3890c0ccd969bc7bf5f6370d684d116000ef9361cfb7f2b417d1e648662900adfd1f9d4149105327c311e1ccef80e1dd5705a056311d31b67462734de797089010832491f9dd4a990fb4eefd8f05795b2b5363e5af1e261ee2be49b8f9784c1f159c5346fd9291af2ae46eaf7695f77c79d21b89ee201303bff50cfb1f54d894235320cbe5431e6cc1e38f15474dee45e673d147f5ec8b13ab80528731d213355cd694a99930cfc238410892ab8c214cc5807a898952753c6ef16074771bc6cc899e4da360f8d6a76fadf26b8eeac051e3b342456e84be7c06386e20ca32e0be2d3c60130b4be038b02d2d3ac64bf91eedc9c8a446e4fd93b764360343b0dfda6fc14748e6442d62800b627287bbcbdb98c3a457372bcd0cdaebba0bc15fbdcc157f8919b727887d2bb17dcaf29755d2ba033701cf2df734735fb1acfe07ede2d6913f52d287bd891e0e7793e788767baf2f7a8a222ec311e5a5daca0a0290fd97fe6c4c541e9df7f7fb4e3dd056d92ba5e6df489e7ea3e380ecdbd352c0d2616284483a46f80c2fd8c71be1682d914c6462c8e715d802b406895a0a0b0825bc3b7d54231d687f4805fd1fadb3469a3020fccb23c527dc24922c284532d410381c2af79668f0a4e6d352d0180759f054ea23fb788c841165d1910ab29f4c3e22fa6f6e1ba26e27bb6adfd41af3335dccfec41ff4677efe76fd06db4dd2f017e3400931af31d9197e94be32c626fb772c75ed2a4d1486cd270ee0b31bd5870d4a935f6f98d67edefcef83ed036c075b6ad6dc1e219db34e34abbfdd08bc06048c3ebeec409ed9a4c0b805aa3a8cec0acec134d949f02ff8a33a769d8b20aa0aa1a8e8dbb140d56360712213d2bd778027f4ccd5bb9442c1028c449b90bad70a48ccc18aed7150e0eeac467f9422cec8ec39b7042555b0ced3a47bcf4ce75d0f99219128cedcdcffca50dc6ce54b505e4b18208e3c1b6efc3340be4a572020f5ec12c4a1f47ea7f06e949fed60c69cb45660de67140f809d3170d59588e47b4d4268005652e9c65c94639f64be9d72ca3ac48c6331877241248f1280c2c0cb7d8dad43037d90185b415803ffbada1216acbc4ce35a4be2064757b6789486d03c4eaaca5377fafc9be4927307242fb175150169a585c0b4b86e8174f65a75a513131cd7e2fb32e01e90d198b64c330b36f0a5854002bd17ca77e53f15a89154b525ec47c54d47ba514fda8b80f5e1dda40aea7e7fb78370fd1725f77d945b603749d607a26c88777f9a1068f9ce2b51568d9a3387539989d8b0f94f2e942aabd2bd134f1d6d1b6e94355cdaac1d87b3f544df272fce702986986eabcce483001e654168e6c48615a26a8785728480b801941bd6500dbb7a561768069feb8db02d28fa3d51421541ee748e7075654919d1c9c30f04585aed7221d18455cc2135a636ab8d855bf505b35b6df5e1c42509a69c4ff7d304112abccca9866d0469307e4e4faf811418ab24e2588c7d17a1ca3cf6cdc93ed08c2dbb4f28a4fa1c8eccbd0a250bd8f882abb3fb6ed9dd4a7e04d1c78026cd50ed006251f9c1c316001cef93b63b6f79ace4cc9707fe71a0f1a281098ada29281021d3a212146006d8991a1892573fa85049ab48b939bd0ef5a95e6c79ff2e2e754a54f118ff0176f913d14f3478590e3d31aa02fdf7b46bcc6fd8a239d01036874cfa017ffcc3d38f73271f2c195063f33478f3d017db3b4994cd5e5f03c17ff45560980b089936ef0f2a0a74e89c7129021c7a0f8be8a94a0be6789e5c523255d32104f85d2832c8355b154edb496d24b91c15aa91568babb0008214d6e659a861ca66982e11e640b10120c61222c7a398d41c75b21d570629e8dfdfec41d2340e04164529f488eb88b6baeb2e464642216d28d3bf768c41dc0bfa0503224c73e7e4439c4535a180f0c0a93aa7c79c343013a55d95904a20b3abccda024cec2e51ecf7949e619f5662517db594deceecd92ae94a9812cce5ceac140d04c00b7d0d4740778ae28b069831b7d922022e063e8cecd8475c6a4d7677839c5ea7ace4e2f72088bd44b6598ff828b6cbbd6a2a033b21124f3b47655000f8ce829b37ee29c5493df413007c6071d16c182df328fc492f2abe71ac94407cd5ea68dcb42c29d3b8278218c739b54d34bb22fee3c31e4dfea1d7844b02574ed12f06d7d2f5ac61df97af0bfec13f0102f2194457cfb7ec0d1e19978fe0dd1df18bc830587c691bb6db11e59b3fe8b9344356f92fb559f502c443b350528794899828565845b042a6b0a3f18a47e28a121da515a531cb8230af5fb973532275aedbfd38c6d1e1e1fde18c85cf18b0adca33e93b21c20bfd5a28a75f502fcd362da75353a9c4b1d6a729064184972022fea55553133a717b519fa13e108ede4a565a9a4a895d090416dcdd3abfa52028cdb57994a0de84ccfa3227e279c7eaadddee42a5979cf3a517b4d5a4ffa9e0477253d648b66919ceacb408e7d9cbd2efe70b3cd8b0720c3b11c31c2e8f19e42972147f364b3425136b77bdae592cb531322278b3940cc3934b4c1eb70e9edbac8fe78007a5a10edbb1c59cd0ee75e4254ac5666a1239369abc2e6ed4a73fc0768df1817a07b0324db18464e2b3c4028c603d883ba6d67fa1c82afc62eb38e86d25b31f8062198a65a2514fa07d4ce4608ad4a561d3eef6304e8cd9b89083acc54fd130f38133fbcd74576eb5e2d79512343ceda357e05b01111168b759fed6d01d3d1f5df2cddad9ee78313acf0481c76ab89e5d07f380d0e3c050babad65162a37c20994f74c00360b392204a5559a1980247e56be4b31d0c438db0dabb7ec74390010068fd77e260df8feaf944b2c562a4c4c43e5b48b6a03f184b767c94ac8e6ece95744bf15caca03d18ebfef3e04003314b33e1e48fd8e966ba17bd8c386d5a859813fb87c2bef039880b79439f2770c430e75e720d626326d502ef47ad5f19bd6f9686cb140eb83545bc9a5bca932928b2c23a301c86ff112249f7fc9e682f171183af7410a7c75c02ce31f302e2682927e069779e4c6a7512ba9b23583c719cbdbae696761a8ab97ca3e13a28eb41cab10c73efe00a64adaa0b718b94a5545140c7847f07ecf5f70f3f529f564d6a6a37831c8fdb17974fa70821157689bed26e9b80bfb84b8ab23824b185f9ccbe9a1006f96c1a553e20c6b745103b2864ca8d60f9f80959212c675a32f3719f6b20fa140bf208997f779004f5ed86e4ae23db865e6ae3f7cf62fd0d9d0141c9b52979b818f02e09b3a48b379159e3dbef0d1ef0334134e781f168de6ef3d7a491f30c93e03b17b3bd33c964d25a0d596ffe72fbdd0943fad3ab233839765058cf13aed7f4dfb787f128c809792d49bfc59019489793ff56b57fbc4d465babc60a6ae9f785628f0dce919d77d345246a88e11b1d2dedd5ec16b6e08f30b45a1a69e54a03637b9c0fc6d4a0df1351708bbf10290d025643046cc667984ce9647553f15a50bbffb22a758623fd0971ffffc194bc4c4652aea76b806578c09c880da7254f5e0f16ce9378a7788f5accbde09618f10952bf7bd1dceaf98f6a8c6b46b245e645b82eb9bf384f1f56f9c96103967594b43f9d91a3f920bc963aef4c771c6e27c365fbad6b0eb993a4be21cc5bb90db7504c2d5e988eea30935af16a677ce83ef852553978c0b3d8367cf72af8083c53645705bc57bac0bc1de5692cc2c3470969ca70861cb51da0e0e06cb64eaaa282bbe5f94b564fd96b516e1fc6d1ef610a0306c79639cd0646c21fe6c9a4ab46cbfd6df7c3bc5f5a39688a0f908c394c5b2f42fd20c2fbd13800284fbbb61165482f9b5ceb4f60c73365a79688c5594b01b465de0b16ef342b285360b94d7851024299abb62ad9e8ff6ee67e462c6ff935ac09af435c370e3e99d080901bf7d679319bb779d7e6cc82808f691ffb9e2bd9c7e3c95dc68d67b34b5c370f0bbbde6ea4c3794feb419b86696c93dc625ce04f993ce00af4cebb83d0581531a50c9282038a5298c8caedd214e15cb689d59d47ecb8f801fde198ef8a3332e37286729a73f8bd22e681745ee5723f213cc9b496a11f18ad4f1efe51b631b10aa0022aa7b70e875fcb0a4bf4943d24e05e6b25f0fff1ab6c5a49ee254135211d9ddcba64236abe121d789ace1b812bf64183e2c4ee3667a142a7cae50364319aee1f2d975f33935755b5e5199476a117b1ee52ba249e3c44a9185dcffac2ef5f9fc7151cb4ceb1bfbdf9fc2d20d7203a02cd09e1e72d359e9ddd558b579a6beb318e2124b2d8dd7e8fc2d5cbcee5a9079522df5e9642e07703c2fe9024014924e1824d0c404e11aedfecbc9a479628f664fab4b0a4c1da749bc793d75c631959f43daab97861ab4c9c2a29f97a760a22e9501f8948680f245b93e909160904d0e3ceb0dfe3a61078899bd82c4193d7451de6725937869238b0a2ad7b6d11247099e03884f895459d27f8ee1729178478f4e1a812d739f6729209283c4eb6f0af9d411caea7d82e93bfa673c84f0a1d4848dddf6f0301c23ba73ead2a8e6b4632b75c7a6b972f0dc819702fec62fdf7874ac737d3e4a8f02b5134caf3830bff870a41e2881ed97f80e71fae39b5e6bb496f126f337b6dbdd649cf81ff33dc52f67b7cfb36f7be32b4c014ff7afed13f98bd4430abce4966aa6a4ce4992bccd65a82eda3f9b0d35eb874ee614cd3cb8c8c9909c45433e8bbeda7fd5ff8b5333755cac0397615e625a7eb8070603a2d0231db40405b0de39a2b739b8e3e314268ce34f50addc333347ccaddd28abe6e4479cf8d6a1633051b3dfdb4416f19b295395228c04dde7e3f3d62dfaa8088513bea18a6f9b2aa53b576c60ec77f9cf9663cee5c8201c7aa4b1f242d04787c78eb9aa94afd3d12a927dd32f4579c439160ec69fd40c99951a4bd28f38b5da481ac6328c7ddb7621a432d3ab8a7b87dd2a71e6b9caee505ede3e89a2ef77803a91c6e643404fe589f3014d1419c1cd94a6dfa06084acc590fcc9aafe146666456143c938664393dab6283b06f3ed3df63097f81921a0047975c24fb7acadb7ce0b0922e9e0c00e6a589829f040185ff3c5c63d35845c1f8bbe410d011e6d12e9a5165345a451eea5f8c8dc1ac968bf7635e5b2cbd58148f33f1f265bb8dba74059dcec732a9d965de85b10507c4b1e77a2571687fea10648d63a81ce280209dfe4c99b33f01467402f8e77bcec2f7c2605684a482e352fa551972e480303a2b9182dc91ef6b01f4f077b6965cfd9311ea98b95155a9e9bdaf744854441f3259c5a6009f643bc91685f2ee36d7e4171abaef9346db73fca05001c24d7bf426d262449b1df5eb1ed495987c9717804d2a987913b36429fdde118475f9bbcb4884384262d3cd5264053cdea9471df65cbab7c11f70e389bab55e09de43f5ce80ab69baff247bacbef20c5c5d023f9de3d8b299d55a59eaece4b6dcfa02f2871ca132082284520744e93ffdf1805b03f6edefd93051bba45ebacc75c2f518c7f9ed1dbe05c50a3358f2d6e7e7e2a135f1438bcb7493497229b686c635ff55819d8cd2ac2e554dd93b3cdb9557fadd35b5f8bfea2bb3586d1b0d5ca3797d440e2791e7f2ec4cef2bc419fbb4cf883c76c1187ae8f76e2982dc556c8cf206a5c7470c6dfee242905d6d5d2010fbeb96841153d8b6d640124f5401c3efda813a8484d5b648bd891c057e0144f6fc54c55f22ef6aa48a72d476e541178787b1a852f4693bd995c0bd17a23f61c9a0560d181aaa3f2ff587b5f1b8f7db7ba453336597b29a95b506067b4c17811187e1440ec29fb9e4b1ba7907f09a9b0a86a1064eec5847fad6e5cb942efbc8da00a1baec8f4eabf13321c3ff352dd0b0d315a9b389113aa4467587fc9985824bceefc47796bbfb816221a99889571e5e256bfcb5a805d253e31b777fe903d1b37432ce0eb7f04c0df0da80d00a22c4e70d3d7c799e26103fdb06ce1651732ee63af286dea55e8630d4bd1ceccd52bcc30d242c93438f8e958cb06041fcac7cb2d186889fb6ca602e8e9db52290daad78f30c0eccd498e921c40fba7c0546d14fb2e3ef1d2f54c4e4829c7d53b13765a730805b16294cd4ad4e52d98456eafe15f0ab109523500d1cd1065d74a42a4389611b27bd40b6874ec20bc2909f8fd7ca03204551817ba6565abcf9a01460bc5b2e3211301768a0520b01badd34f52e303ccc601a45166ba1f9ccfa5c43168497d63bc246be0380637288400f726e449f6c85c5a02be25d557070743165d9a816ee41bc072f2271515b96bfd7a70b295fb4d4e5310c5758b290a59050bb8431e5ec997d91d1e7ce4171699055a3806e171b123014b04f30a383a133cce893e43edadb7c970f135b9c59bed7ecd753dfc23c1371f308c7051c4e7d38036cbc50bc162a9cb560af9edcb0c9452aa2a1ce9ab37b58b2e5a27b3131c052c6a8fe1008d46137f1a6f496d4d80b16ccd619a5ea58be7c726f0d359268e09df932ca5a5edc5836a6e4d513c815b0f3f98dea546b08222797b2bb2bcbd07e6fca61611ee120c4e730562bfefa070763e7d8f963f4332c7ebb1bd692eb3f4462b349ef6c33a2edb68bb8a542f279d9c659c15251d4e58588ba2e827529e163fd64d051081fb5b56774d45453b5e8197af6ff38a65ac35236c396055a0cb9601ad7c9c9b785ede21b5a21caa633e2bc09d873414d36754bdc4c67391e382f02622c4d92c6ecaefb19ba446d88e9cced4b657a0d2d4cae37a6cdfe0ddba9d6dc1eeb69b121abeb64183a7f4a1b5a8e4ee0113ca520d43940c63cdf1301a6e2fa38a9b0aa6aa5aed552f63182c66bc4c958c988f2bd2131da74c90c9f5caf4fdbcb7a0c9113ebeff27437b9f3d427948cbe96fd900afd8d571eb6dff5ce567576af6216aa56a2eabe3c69767aa7e376d7eacd5ad1ae3d70d7dab12ecc15c097ad411fc5295b8126818a50d6dc266c4e41eeed7e801e0240b2c1b424ad3724d625390f18d1b24bf28df3017f67bd670977ecd822baccd92dbd16c170da03e51f7d8451f2d4a9e0e37fc9b9cbc65bf6f194de91cd4a00477613bf5d58d232ca1dfffc1803a848bcd3ab47e8c584ca88475a7e9731129943b4604b049736e7c4fb84cdc84637e45f4b22256c46ce42a022f3cf438630dc0c1bcdfbab7f413c25aa88d31b2d2dc96b16b9f028958b973181575396d199b7d943d71d65e5a1564648a099f5be9ff4145bb27468f6f16c473c1f167dea4acf8eaa6eceb87dd1f1fb7265dc964cb5579aa8fcf4b94fa9ef32adc5e35b3ca370e7b6a7881264a93839d377a9716ef92681297f51d8682cfeafd7e5fdb070d75d31bb3374909047272083239b3032388aa2e0d552c3c28b126e451cf979ada0b0c38acf5929a510da5850c540dfdbbe8cf4bf716ccbf0a9e39eb3a1d2a8857cfb41055a6d4300efb27867e6d5816dd37dc3bd9b3b524fe134865bbdf5818d17f7bcf895429ef25a4d8f471fd8aba0b40ca102dce83f114398d67540d6c9a2eb542692ca0cf357d85eb1ca19860d081ec06a7272d3718bee3d6988c74b151f18ea05097a2a7a2250f68e09a0ed7eeef17f0ff93a6b47e79ae04507128fa71950660f8c3eab9a102e8ab3ea746b4a1a1e85c6ccf15431765dd8d02ad971b2c66c90126cf45ae8d3f3ee32a4699febd359e148730750f8924a2fb028ded6dca164f3514c533546039b794b242a17ec65c4953ff62ceaa68e5986b8f902d4eddb27a6265df3b1ef3bd831574942446604fc24b3382fd8600822f0f305b339f77b77324e69ecea2cfc217136589c227d7ee56a09afc317da90406ac652462447b678dc91f6b071990f5060ce9f18939b2c115ec008aaf4ab30d3e1da8fe57a8f5ac02d3baa16ae2cfc791d537497757583e4f3990be900edddd1cceb48f6a368c19057ab13fb0ffabd88015141420a0c415e38aab0434cc5d075a412d140ce1f8d71d27528ec696d3dd06c7d1ef7df84a70335299078bec7c07f38d85c8789d96a027fa117e7b71286e83bcac0be1846c4e24fafdd8bbbcba4a62da421136e3fd0239462f99a67fef4f2b176f068f32bc7f6160265b62ea885da6e689fcfd31b196ae0f24b84051912ccdcf676a782fd3e1415322ba470b934b9c83fdf6aa88d9e42493e8e1919deacb2330b141bc6e55d90218affd9418976fce16edcd98ec121d9abe050281bf2c39695ac77c66280637c3e8f1fd5ceccd35ff1ad358b9e848e71fb4775e1c334e7328c16e25f356ecfa986f0245d9c7e392659b23644c07a3e47d4b60f7a41c0b8b9dabea97a53987a7594f45a7dcce8031c7bc6ab7c35c81b1e58e893ac2c175504eb5833cb0d31217319b969ccd8861985a31bb50ddfb887a613bb8b4060a17f2e74f5e7b7e6bdc04ef5c44a4f162ffcaa1c56c2f677dc076f646ceaa99079f2fd83ba1e42f0da7bc0710d93df22cc9263cd62a487180bcd88c54bdc4ac6925028b12ecc6780820a2a025b6404b9beb7e04d4527ceb25dc088f4149346007fadbafbf1daea96e6382a5837e9cc1c5a9b8aec6a2aece91953ada9a9a01db5b05e26b016b84949790e1cfff50cc18a7456a7ec4374ff96cd2ae0a24a167a802b9c0e8edc44f639e6eb6bde9d286fca2783e9e7edcf8d8655c356653466ade4090fe416b8fc539e22046b17f0a757bfa7b1e16b3a7f9d3ab59bfa7d46f102d7a20ee6cb4a72ea887eb11587709fa7e56a1645cbbeae02675d914b703d2ebb8b9b9a159daba558bf307090379675563437b9904fe6f6c937c70ffe77592d88e2e97823540ec79f25189700ea38caa32b1957d1c7b096fcf7ce57bb470ba1e92a89f6d6a1ecc7f09115133d404ccb60c74be615e426f8a1bb7edc4056e795f011fb677affe9ddad266138c67214e40f4c4dd0f7efa6dd6ca71f948089e37f8112d9abb4242efe7b1c76b3b8572cb8792bd5ec67b5c65cfcd7510dd366378c5ac9058d32d9c7db7636e01bc97e6164780e0270817b51626848ebebec1d38c6f0aaa4bdf48936d7909dcbb266b5b78064b9123433168c34a320f1037b63916abc801ac3c8a05d10c555307b93027d7d2bc97588f2e6a9bcd784c9ef05393581715bfb04d139f7d68bd5a292fbe92b49ee39e920829c57bee141fac52cd145da4b53240f5546733d8dd3da5648959f6d7f4b89a841915fdf50264527348a3e4aa094d30a898331348c88bb0ecbc2d81fc70d8ffa7ee0ed231d47cbc72565b2a209a261736352f65182096b794e632a337da566f9be9f475aeabce74513f09768f7276ababb85baf3348f6f1022d804e52e86e9de37d212b08de42c19370b17ec3471cb5f05a0b1c61cc0607e3ada6238063c0bd1d608249eda1c40f44150a9d49649a8d031f65300d7366c252a2224cd3b6d18ba2651add2b98c772265efdcd386e9730f422e6d2021277d8802bea2461da89e351557f946433bf2d2f492985e65c5bdc00c1dea1ce55e2baaf35e6b8616cb4e908d058ffbc30a6518ec7624f73605642764eef99c628127d0eb4730bd4af81c48405e51c2b647869e2f92e45591099473577920cf04bb5c81994e13271ae06e4b300773a3414b63664da474d66f15b55006f92e63d013fe2762bf642f8eab9dab7f4f3213902e77d1d151c2fcc467539f2738e46e15b66bd7a8f6a071de8f8c5191a5398a90513e37cfe4f75302de796c0677b63a6e8a721cf39698ea3478346f359252cb9d146d6d8903820e63151e6ab9720c397e5c0dee7991483726cfc080e3b0a546f161390b3196028bbf37a063f2599c0612d34113ab70058a6596ede3b4338dd2caf0e1ed2c707d103decd5c40e4d5623050ca7667d4b541efffc3646945f9a900fa517f789ba0cacdb9d78576ec188301110ed3cf173001c4494d3f2de32ac065c4f8d05c524f2565a4cb9522907a43a5a3cf633a9a617e33fe3edfd5debda74473a58a23c9721395f5fee851e265d5e6949f6589e0ae325f012fdc37b93719522dfcca76f4157c4ec36a84697a1a90973b56a8a9b16e63e8909ddcbe7c32fc24d828b40a047c5d9efb2f068dac2d5776ee9033306e6f2cdad9670e8385dcb59da955d8207a9dff1d810e26c30005512d002afb578b715adaac522f658c77c778e833b65c02a3f64651ecc5f94c5deddfafc9a35197a915ab2485257339d7bc26387a45fb9a6df5547f55924b78c63b03d2535717393f5c47ca0d281708e76a43756093bf330c709fb9f732299823927af273a936f2d04c24215a77d355247e46b9246f263e480e9522220b48ca2b833216198c4205ec40618dc0a58c0a205947958726b3ca1ceec2814f8a78e8ea24b40ff853dfc799b7f034f76fa4670043eef44e5267b736b69134960cc6f540a76a8f07cd9ad3ce0f04d49c256731ffe5ec63c2b2b7237878a2d1364b82ee603a95b5015f02fdf912447caea18d94e91d98a12f1c90d67a64f524115d1e7753451ff1e541a9b47a4fa0dd11d3d5dfa0167d8768733883054a44062d47f3bf73ecd86376c2107a2543bf060ed5f3679bba0b4941ba2ce299eb3a8e341d5cd6f0e7c9d9bb96a454c4555982d1975db26271b0fb142c3a63e820775af5b1c3854eaa989e80423ddb93a30856ea18b351132ef7273d6a73d659c97ae3e1d787dde5d63f21d48bda2ec29757aecd2bc5b4e0cd91dd9eb8cdc9868ef9628e31d46a60088c6389ff8771f75618f352d27c878fc65b651c4a7981be762c6c3892d6373fe71ba8b8859daec9f94e963525252bf924b5351f9d4661844715233beeeeab5590dcae3a06cedd4697382f97ccd9398bdbce3e16e8aa550db8dbf29adbbc7a4a4b3c152f1db6a66e1f395a7b081cffc860c2fdf195354bc673aebe7ba22cea19918d1c7bdfdcceb505ba654a49d04dc49d8e06a5d2880bcdecd1cba9dde5d99362542e44af58c885bb430b50582622ab2c54deac2727e773fec60e11abbd20dbee77f376208aabbafa5b533c102e0c38e76a108228a86bc05fb318456c0e07e98992b83fed9e0224e0f249a4a51a763ec52a62292f2606efe5759dfc1617df07f3d92693face3108d668587f7f7677230b82eaa628f7d9ed96106e9015c4fe56eb590ea96c85f3a04e77f1a99fc90c60c8b29b82710244de8b52ce7eb783a84a1c7b1b7bcbe753964e86ba629b743902a6d17e49c2457b99a3eb3fea3108c32a4e10d74a376dd38dc73885cf23c2cf2c05e0790e2c5b7f270554381830199a91ee633d19ad77dc98c2393b781e7159936d9bfae524120e477231baf4f3d644f0e6ff8d2d5b2184c15a9455c84d39e1004c5c4968a46da9ed6924768b0611b809c329b39305b0a28487209891890c2cd2009e98335099cfbf1a27ea907892fd75274dc59a7a60ac51b7ce9085871647793e0a1f68b8a7669725e8f1d8b285832098cebb54177c570d3adcc9be33bd2dfcb721ab2ca42b060430cb5dded651f9c88e00521a5cea08917b3ab5369923b1741fdc28286efe38c846797140af93b981e134840591b0342a0eb316505af1b88f74d9820971c5095b102b9e3f301453337924ce767a115c178592194eb9606b0ea3fe210084082d67cf16684388de5c1a5827818e1d3f170f1fe44dd830d5c84771970f4287ebe34a35ac2955c0d76346f0f61db794d0a4b84088807b9c7ccda3be57678900dd4297048852a4ec796a19caf49a19f46dcd31bfafc027f1bc392112d65791c7cbdf958349d301ed9425cc7a2fe5253e648689fa85ecb18570b8d27859699cc2b50bbebeb3a262bd36dbe9ea9fb70e6813a245c813ce4feb32385bb41424efb2a1a3419bc4ab62561d8bd2ec58194b6e6809de2da5281e5640f376b928785311563581b9abffbf1d54d23923a3701779d56d41eb3b16a095a9cf717e087b94fa5eac9427cbd9dbe6decda6373f1ef1107a1f4dd5d8bb369dde77ad191827052c35fcc5e32bdf3d83941af56048011c636db75800a6aaaa9cd1c92030a1cbfb0f9844132f10060de38af2d539711920965c92abba4b3ba8a83401d3b376c44dda6cbdb0af8dbd9bc5b6524ae794ba472eefab0157facf40f3934f65f2e1e1dbb4b44f82635940d8f9a0052b8fd6692182a3470cc7f23066944e6b87f3b2a204fc6b21a5622f79db2e536aa41ec989013f15f8f005982672e2affc7e278e032e8b6482e67180825d54ded0b66059707dfddf01c652530ba15aabb6a565d12f41196d984a654f963d8b1c50b264745ef1e58fcb1b3bd54e9219fd56e83aaad12ca557e185526836fea5900ac234a416fd6452086e757dcbc9e3b0bf498837185ff2b02184935b70443985f260dc1c7d03f0588fb58bf35d2fbe51da5e71d0cb50153b9fe498e6635b3ccd3fddbef3c0aad91f5ed8553a66901644fbd19acaf1f60f7d96788cb49db039c8f3db3d7b0b5e360b183544c8b81b7c0c0adfb85992517ef9b2c6c3a06b550f8cfad3affc97cdd00b721b7a6e0de6e784c9da6d622ce052def18b02e50a813f56b291a64a636b2e3e5d4fb8182968954239a7785a4429a33fbf338ddd730ed1a6e4b385e557efa3b56f8a17b0d6fd6aae2f4133904434842f4e610b4d92fabfa6862d69acf8d479e5fef892aa4266f291fa3461739610f744246d3a4aece6fdbbe8c75b5411d9205a9bba62117446e11a0a224eb9754de56391e16f41159a026d579ddd1fe18176b4216526086da6319768423c767f812dd854fcebb3cd7fecf5919eba90899419196181df1ef471ca6b07dc228668c4bf15012ffc9e5b233131e50f310e489cce856b0cbcf826f9b707b8cebcde9733caafc7a2edb47ab9ddb52b0568b92669f4be00f4b011be70f4d5fd0ae7a49a70f55837b0cfd13e96674d6eee35ea5d8e08c728a0e4ac5870c06fc4154f1f8920b1fe1d66065666b50c236209370e95e13b12ff68e9353f748b916b8a71e07c6c90513108ad50e534c44e8ce933e48eff16e8e318c89ec95c6c948895e8c12981b64c588dc8a3e5d5be9d64f6d74d92c52ea5c572da84feed296e33790a8b8410538e701e4a8c137e77ba6bebb7310ed8e8cef7a875260f4fd379b30bd85c23ce2e93329be137d1b95316cb59a8278c1e502b1ae664730b8de88ae6343b3dadf547276816a052d735d436bf2364e90243f94ee6c633c28818130dcb72955b3fc27e9d00556ab6db435548bf0083c1fe35f75256d948f4fc456e322c579ee827f551a0ae543ba612595f180ffdbf72e3a9dfa47b25d0123fca5f728d09929bb754abada3c1363fc29387d92af88f51c094df2289a275aef54500db18c1af3e3427bc8fad7f8b09ab2de0524626088accb2dff507bcef8bbab513f7814c08dd27bde964722326d3bb0af95ceb8d14cca602b9dbead4596762ff914a858d9c6b61e6c908c87a333e623070ce9d8bfed7e04779d337bf5ed8266abfa7eaf9cb07fd782ff02b0173adcc4a123a4d4d43a53489b75d4c4533a16723d5deb6191325b3cb46ca306b59789d258b204759ba1dfe92f8c254a360f257ba398a870b1a447f3cc2784f1f0391e233a6774e050ca7397223126b0a5f6d33a349f9e902130d68ad1631e9b8f605a00b1fa977fd77f454fc05c9f61e00f085b7af7bdcc268e7d303c8d87083b709dfe7016b6ac1ebda9446e9585277a972a8b04d8f8f24bd685ea7c5eea104286afe249c13b5246921b02d36f96f235f83d4b0b4b2ea9837359406d7936371bca1f4c5fc4aefcebc8784cf297a33229e2717bb2dde4e9e232b5411188476357dec4ef1043c84bb847d780e9a097be9062ffb3852e58e4cdff3d5be8513556fd90afc7108a71ebb86433114ca1b5de0d740fa7434431ccb19547f859a7fbc70499c5c33fe77dbf05671c2ca4eeaf5e4372ea31f1813f5da61d5c2e3fa272f41c13ba6c940c299524e19615f033b3bc55053ebccba37f64a04a33bda31805f1bf3c761ebde43f92ed104f2c70d61b1c9391150752cf3d2803ab41b6f7dccdbec6181b0f07bd2daa3e5c27f39f3a98827b2aa44f6377119f6a89dca2babfb82d943e80e59c05380241a83c2b0bca89e8dab0de88d5b065ab92da4449b043043b634847f475a23bcf9bbf6686c2b111b9ba45325615125bdecfeea62d14bc96290b8ff62008e08fabd5db2be6efd14e466d30231f8274d124419d7b6e0c7011d248e41c66e561d4ee1adc9a1ff4175f2f61f5accbeea71a88bfe010c636170ab65d16d9a9f154920289d9f7629beb9a1c8ee31d785daa10e1ec455f5f7ce01eef9edf81d46d5307d29d524ee3c6140754cbb3b800036e9316f712c03bcd13ac3bd6136b93917bf8fd17efeff4bc9166f6882aad24692b62b83ae8c61d881d5ca3d4f0db4f135a548de584f6fadd7cf01f3fc8d7755c3417a74c2dda6f4aa0bbd3de0bc750d5578c3641e1d8e7c55c7da3baa6a408d54b74af0e1ac8cbd3820b93378da72cfe14b8b4c3d8cb819a8853e80aad87731912af3b8cd5d2564b3f9edc9b6f3889da3eb7ad0dc6b2ea695357576693304ea1c90b822c1e0e21fccdfc7604a7e4a815c17970134cc868a195ed1c718a41f431c438398b0c76ba5f9ad5e8892ffa8aaf961eb413099f6f148522f8ffa10c82eedd63217d854cdb6240d388912967ee9539624c96b73084a470968de7bbb107d6f37d8b1995c1291648691deb34eb33580c35d24744131c74a50163a1f972e6add593b0bac14a87ccb804eb5d8ef1d27b05257b26033e2e0b088391a37a36f0f3400b8e0982a4340437c56703e24cb7fa2c81926b93256d02264b56c70d14cef2b51ab7c8bbb28aa791ba826b02f099da314d809acf85d16ca506a0f2d436d6cc14662416f07d80cddcea522899771081e8fbf359544dbf69487a65aaee7c04e4d35b7255ef912b9a832296307692debba09465ad4ec70f68aeefb940fc5cf422ded52325fdf2c8c0172ba1b6e07dc931293707cf3c32a6131411b791076fa58fc38d40575eb57622241e131f8abd7f2eccf7f731d8c85c7ff3031bbafa6af855bbaf650fc57639b9840e5bf9a010e1f120c6969e4724c7be46f667f9599891f4a6730595a49fd9d90db7b94978646d4aa64856058cf3614d9d68fdc4d00de640e2a2378fbc6145132f3d1b2df9d7bef30aeeaaa6d1c0eec861ad06053b089f3ad65ae9da16569a40de917d1457adef1eff7f36fb36969a4fbbfe88ae9fea850f66062c7027daf0897da36db804d398215cf992a5abcb89e6d433c51c7cd7c6515da958eb702f1f0b31f9e6efe64a6eb51a35c2c8a1a708210a30f9b9192f3fcc144649a7964fc57575e285d851efe9a08efad180a3b9fbaf9bf6dc725e32c46f4746929b2fbc33a2ec5ef2fa8def0a21005f765351328f514472b24b450d5572eb8258d58f489195c3f899e07737d3805a696e042bd13a72814ee77aa9b67a7549f93ac20c1957a47b3861a658a573726bf7027db703a88ea40c7796e8a379216fb9e7ff976f966769e5044852cdd9dc8f4f52eff8790112452e357e8eca9d9e6ccda93dba99eea7aa05f73e25b6d0c57dadabe3ecaef02e7341030dd96241f842e0a8be7a46b98efc69e322f4b4d4ae2f910f13e7821dfd1fd5c9b195b106d1172ce9bd417d30384ac47c0a8d0ccc1410df6b890d1426e649ce31e6c51beafbefc120faf93894619bdd98e390106ffc30a1855d70e5b3a64f4e0b86ff12035019e9dc2e6706d7eee30e6c56b1d67376fe75ee13e0a1f13510f9c5826cc10335c6580a7adf8e64bdd904abe2b3a31bfd130e7c742106c8ce957416363ee4341b9380844a6eb2754a6d51b875a4f399062e05a47c492f87cbf11c332063636db50ad2932e2bfbac04d74f465309684c9710a20f470b8167edc7e0491042400e222e55b9e3030c3bd87e98f4c6f61cc2ca94a473679022a707665114245f58019ff7247cbbcf62afc34969e6965d990a01c10bec996829ef8b3e7e7784a0c50a91d87ad8efcccb633effe8499bf4525adea2aeb3c635bc5fa80df665cacf5a09d47e671d1ee0aa99aa5c049ef2bcf0b036d3d8674fe84f7237330478c6391a4678f6dc010aa7f286f49cc4d32cce209f3f8f22c260510c562bec1a297e32af9599924dddf3a5d1b728abb8a5c78a199f42f1ef360d760c46939127082058a6a2d74204c61d2518769a350fb1cd104c020e020ed002f1dca3072988546c2469a7e01b9a4d24428d19d3673825522a4dab4743f7e3b06c6ef73d2602c4fbde9274ceed291ffd789675b3a3c1d9d1543ab94f6c10618ce37d42fd49b05fe9ea3977c3b4037c961bb7d75f5e26cbf3494c2c49c0468f3f48aee29460e0b293fa58556567972a6b66f7cbe0b9dad393cd96ef0ecd9b6fbbc0ea167a095be969f62ab05c13033206809b8929ac3ffbf3d4b7ee56be05eb36258f7b468323183b64adb1b4b837654d3e5cc4b65cf80b692747815deadeb7493a09f20a74a662109be5e0adac645737f8bbbcd11fe11ba9ee66c1818f2a6ea33e9bc7e6a16bc14164e11abc1397844ccd57ffb74b2d21862f518255ff33c9376a5a4e55c686ef58f8f2bbbac14dda0f077673fefe14454dc62f19d5906a34442de77306844fdd06709ef5915c1e1f54871dd06d920caa53e2490818fe38da3e80ccd7e6af63ca6a70a4c8a4d36bec4f54cb942ff1fa874eeaa21f77afcdfad2c264db2fa9a19ec1c7129b617bfaf24ca4662e62fad0be1575bb0c928329a32998f3bf33c4ca40bd3aa2d28cfa9d03c8aaf3f6ed688913e901b98e5688f035427f95d2ec764d4de5d7cc79120a5a9cc792a0154a8a2b491c823ffde8ee8ee0c35559e8f958a8337f455bc8552cbafa53a9e429c618f2116907aec69c2823fa5cb680e0c0f748f595a41b1fc1d9e802cfbf2f049914957bbe6c677e1df24103f36aea9da1999b87f9cb2fad31675d3dbf4f79de5758f2da31f6299c4e8332a4de8834ff53f42c95499c57e49a420c6c03a0aa3fde96fd1a493d3543404bdcc17b4e1f49de3a7cc7a5cb29c8289c360fdd7568ba360d888c9232053770f8f1d39510efad38313a2034982db07fe24eb463ed2bfebf13516d08b978578bec50022330fe6e1103504fb1350994456fdf366043fd86e777be5ac840915434c687d96afcc86cf501e31dc8e9c13093cffb8baf4dd45cab81e4404b78cb48cbbfd472d75249aee5396bb174282e80a779ff4b46ba1451fa8dec5b37d416a29a73886d25164b151f9b6b292d7d19f5f45ddf45906fe2893ae64a04678ba0394a21c614449e7f54f830639255561e204448d44c4a0b20dd008133881edf4b66a2a3f8598fbced2033c7fd81c2718c55ae7e81ea1a4210e81339250dafd0ae4ee240643b55b134dcc29375db8fa81f4a583645bb2a715cdd867ef53fca5098787f2059bd6b56200cdd399c4ae68f29c8be50d7e4e5a0b010b285eeb469194ac3d0ee1240c068c4c6e1666dba66f83a29b7af51eddc55a4cd2dbeeb44abc4127403e4b69ba9e6d3cd907e278bdbe781bc40964e95ddbd940eb7ccc394ee7d6d6615412fa171fa2147eecf6e103d0d106dafbde09e051d2e69753d368ced8d20d977e4e4dafb965458f3b27c3fed214e814343d000cd564801ac392c1f86fec047c271c23e2ca6db085b3905c26679c4890784e719983b38b3e260c0c2e8771d7ce66810764bfacd0997e6cef6726f21dce6ad7c1d7bd6d37d0bbc0f188a6af36ee016154bb4a3c574c67bf38a2ffffbb3110e4fa2544979622a3e00c4f86d0dd64fc9cf1c5ede73ddf1c2cc92b61224982eab2b57ba5a4e188e8ac0a8361d4d259b8caf32fcd1125dc8719564c3365a16ca0ba93e1fd46fcdba15e4e561894ac1ba71a24fed7eb0f8b2e9fb4c1a2753501368c15f6048148f91a251aaf22f5689d89556e0a3e3f377da9e90a3bbb12b933d593796a6cdf8842120da4a952b9d9f440112c7e363cb8052809e9ddd106645c26faa9838eee341d05e3a411679451db2e7f465a160c5aba045c66c2bff31fb73828279c6c472c718c62dfeb6d710630267632f1b61f82f769071575ec7b3ce4faaf4aa2fdcd252cc944d6edf6119174eb99dc6b2c74b45b0630997728922a68439b3496ccfa91c0e0ff670216710d4f45c2b544cc47aa4ad1e4f5bc57fa4e2a6963330de749e8a189494fc83594a20b9d0332cb83f91e69dfed2d2eec4433f86b137db31b2981c8a7ae9713bab3a3142ffb36a47e0cf02647f1aa2930cf3b00ad569ec388ff2e33f9d1ed6872e50f7ecb4028471d8a122d15a0f9fd6092905737761a917a28c262d2235382dcb2f07f26207b7f3b3c2df450ddcd7099cb1576988655009afb84dcea2425d13645fbf0b8fbfcebc6795c301111ee398cc628b74848080c6b3c86a8398698f66211e3d030fb4180a57fc15ea01999860e5554dca37c2b421b8b39d4e1b7c049982d00c171e70305ec8f043bb18c367772bd578971b17e43a0b08ef22eb9fc53e6ba4689255693956451de4ca33aa5a0b129aac5fdc48c4526e02196a13b2a54870c1093dc86df2165291257d725488b67e11b7dc1a45691ce4790d90b6b50be0277e16bbe9303ecf58900e8136006349cd855019b089b06be8a4dd9bdec8bb71ff12f46cad7b90d486e1dcdf97fce8b5e5dbf85962c08957bfe8f37c2e7e59ca7f01a77a7ac90f07b7f249b88a0d2f52eeec0a246cb82c39ec50a972a286879f82c3cfb05e23dd5b79bd927b31c9ecc398372d2acda132ace2136236caf30916731f5ee8e90f8508eb9810d04795ada61d482f7b1cdfcf80d97d709cc437fdc73e91614652a53c7b461052c522d15e21a668cd42e5ce86e5eba54f8dbd0b784c8905a5135eba9f70939db2785409c1814671c9284132c62aac51ca706ab1c670fb1e631d709dc70ec546ee669e893a3b2a8663dc53195bd054b1e4f2c089498931706617d4db014e153b113979fbb225c3c01456c8d49fe3ba1cf2c4f3f24deb3ec493476e8f79c8974f49e6163f524917f09fea68bdbb6aee3c9ff4fa1f23425578b1f7cba3c13eb24e23ae10b86aed7a0c3202d67ce0a849f578fbafc78470555a43d965c8a5904c4badd04f031ba75e71c47a9496e342588a123a21e56fbbcb442cb8898aba1b48f8544b7474db2fb477874b1f90d020dcd54098ee295e7b257ebadd52d08f3f1f8f16c13fe93f99e0fd48a4f1f7256e6eab71376a38dd4cd281fe84c0629ab4fd8111aa9e5a06264b0a6d2196170a4fa112415cf1f7241c77096262d3ccbf8d43cea54d18028b71af8c777dbdb8e77f07bc268a7229ce24d72b235d493545e23cf15e612d0af33253c5b0bcdb6beecd94a3e6ad383091df8779888ff4a6e34ed0259df96d853081644f6005dd6223493cbd4fc2951088b06bee4b49c5f8527bc901511add45adb18fd2f3088ef31177135cf03d132227df6e186f429b0f1b507f2ff946040b4432d405956c27d696c3f41fae7995c4e75e4c124a6a6978fb39bd956431c34414d313561936e5d35a28f3121166e9e28a8bef553cc13e7a9c84ee2213d5fbfbafbbb1cceeae70ca63b5726eb82cb656c83474c158c845e2cef08eea965813550c0c78a42c319d60d4bded5ba1f51c23a23e8f540b0be0739f919ef72e3879ea3052d5c3e6ca862a3fdfd045a665bbfc2db5247fd276b146509e0157ca109be9ea8ecf0a993a97cd60eaf584bd5b0a4bbb2f53ded82d6211416be9c70cf2362b7784a7541cdecfd1950e85a966d3ef0c0e23c307f784104a36c62acc0607eec9dbab7432513d42f8429cdccc233e41a085ba553a762c17b552b5d124f8b6047fda78da9ab40bc1dcd112ca081af517a183d9f025dc0b07444c706c47d4ad5b8e7eb97980f94f59be0184d323370f2d1256240ec64b2afb5dc86b667e8dedf45c8286f0fbf344c05705d10a21385ea646ed2c6974d7f341dd805495e0949c0973471a342dba720f1ad985c5b5705239b854cebe50612acd0fdb4f6065bd24795c7cfb6d0f35a1064a491dd31eb89668bc4c4037a28772aff5f557a3744853d2241c0e78a251d2e3d64a612a5dcad8dfc31dff534ce63ab194081872244b1d2b683b3cb9c8ca5cae1a249ad27a892bbaf1378f92bedaae2a23c2391265f6827c1ac8db91c6908cd6939803f84cc5faa05031f2bc526c69cf8edfadf3ec302df027127044f6c439bce5e643ab90bbb0751cf95eee36a57afb8a11bf910f89ac45c1acd042664b792de64a23ce6b255e167b052214f2d9e79a3766e54d0b38f7fba00c0f5c8e1a810f735fdb0241c5d21464cff1cbb8d00f51db52c63729b0558101cb59c2b87bd0ac7cfcabc655e081bd3dafc8921958955b5e267e6ecc9620b9d5955c5005f83379c7d727eafc80f271c43db015196663a46b9829578f7d1b0e1b2bb8b9101eac6fca2f39b535730509665e381ded26a6ea9dcb514127fc2ad01a1f9764350c6aa8f7d6021f9caac0f39e78eade2c08a3808e48a3bb34d68ba7c6b9363d7404f6f95635caf936ac71984b90be12cb1613c3a0e63abfefb39f674fd013eab88a1a373aaa3f9a1c24c3ac24bbd13d620adeacec2f1b557f0720cf360328d88f73dd2148f82954de507411bcab98a5db4d081a4d3be93a06c9168dabb04cdc9f887091f305a1846c848fe460f4c5bb9b54f9e2e4da5ea18d8d97a7faecd98e00046f442e82186715e664ff3f573bd04f2575bce8554a4166bd10c1240d5a586a42682251de00fccf43a3b2f87622eb6fe3303de06d114939fc5a54d19d98d23b63920a6e53622112ba01e6d1bfe628741d74f52dcf270c66e44ff21aa8a69310d728183c0d3c3079afae8ede3620a7991583b13d2e3944b71a08945fb0c3baa8a29f7a0f2fe438cdaac68a77a5e609cea72606e28a41a866c7100e4587828c8849a596e0a1039b129a1a7f706f38f8c7901eb445389c044b5120fdbb75a86a20365803474f7ce9157c457b73dd8e5c1368818d769b81fda14230537b333b2711f0f4b4801b465af07884f7d03f8d22865ad484f07aeb2258b059be143f8bc8360803966c2dd7ff48a611625c0f2bea68aba9d6e7ebb2b475d61c6b4e091e52b388494b89b1c01b7c44981ea8298aa76e41e2dfd6c811bf65da19e63895be336894a264a87adbc539d3c5facee70a287eceabfdfed79e7b7417651eb5aa700aebde882e790e4533d7ebc352886360147405523f5e26e106e1e50277eb03805f316d4e0b034a56498344d0d41585882801c39091d12ffc8f6eae9971a80c9f07ebc6c81deefea07bf60fa4da33c79243ead93780a1b21dcd9f0bb6e40968585cd8c70a9798141a0454acfab57c9eedbd1a306cec39d9be33ba785ff75908da23fdb82290c8f4bf85a895c2317ea441573e0594cf24389ce915bca6a8ffa766efe14ad76881f3eb19d773806728ab8071a80f53c63c1dab7ce6151f2e9bec85129bedb52eca46401c1c37c171c14bdd7824a68a05c4ad20c0643a65d0d2e80c1d8575e05601b9ac29ad444b8ef9a7a89824fe46ea7708803bc86986e653e8ed9bf866823d41ad2d074e132247a7049c3177ec06871d269615e40b965054958c7764192e85f9691b8bbc6ebbd22813a60e122f6766888b3919f7fe196a7743504b8e4591fe26a72b564a7976b3f4d8ed2e486a49bb1cafb0bae1fab0d5b34f35f1acb9c99a6cca100a55c6503a8883c56ce5a8b729dd6a80a8236f74191bb52db116d2039121a606fd48e05c89a6b6ff3143693413cb97591bb9266dc7d0d8752411d0c889797856195132cf503880551ac3445993345f3c3ca4be18afa59baf7662c6de1751ffaff9bd6145c7a8612572ea94f857724048c5c6513e560ae889d6d9fb32905de3ccbae23f90f83865ba1a6ec044dc9a8629045ecea600c9d81d16ca9321790c9dd79baa19d22eefddcc16990d9c31db8167b65c01a577662abc6a1caa20affa2032713b0c4705edb4253a299e9294df401903c749a171e3bfd0fabcca077d5f2e6d03c20b97e4e6072db7df9729cae0756932d48240eaf11e52a91d32bdd43a45527d577c864249f007e2328561681d21e00a64cb9f61669c3d96562274e509632aa78ffc1cb26cab97edea39b00440fb38ea6231c227e6a6c4e8481dd8957b9f117bde47cf964881a942619ec5261e6450b66af52ca534ba724bc2b79a468151d2c9b9e4fede355d60970231544cbd3587c08392877e0d3b32cf1c68b3f3d64d4ade194998ca28c53b3ede98a8a6f334499a43fad7210abc005fab38752d0bf46ce8bc43bdf46142daeae56e12ca2c3b490112db34d68307188c7f04583d54439d714bc1a5c86deb2f4c14953e2e16e271f37a35274551960011f5f810e936e882923d49f459b4665c049f53f0398b872f5d9f166d24cbb9a3b71815c40e05f9f8b24fa361659227cbca882a809b63a3d6f1e8a0c8869e81f5b9866d97c4a4301c8b5bbd2fcb1d36208333cb2a152bc81993bf229ae7d886387860c61d8a26ac39bef826869bff1350a0dba284aa88b108918d1e32ac7a4d0652fb9404076dc0ef5f21c6fa99f30491122fcf98417f00d01ce262596ba70426436a82533328ca0814eb56694ba07a3c85ceeb7970ebb706946ac202e96f689d32bf3a609f88f4c8d176a4e483f367e798b8f16dcf838831576a1c5dbae72ebd162c9bace60457530d07d2cf6ecadca1d9ad673a0970567dae5064b47a9149f05aae33da8b37c97ce74f589be789ca8420c7d4b98669190f5895caffc10d25227719f6fcf5070bfe44a22f29d2244efbae008bfdced7745431f852fa0ff31a65c2554751953840d284b3078694b14aa2149cb177d232121c5885ace32c67daf62ce44d70faa8839f4405330db1dfb982cdf0c3b9f3e8a9578e7d4d466ca8e7488360700992e7e0c0af995b3ae9c39abce414b3c309e7c637d66634b040a8674792759c23074ade29d19a0a0cda71b892a443fc924a5eb248cf53a835df6151ed88523cfb058342fb67d00168ccad50e55185eba49aeacc4bc6bec8ed399ab57a855da41a9940df7d92dab3af5e472c8e71fab89a4793e019dcc88c56a39b0f889f17f1249cefb530f5063b727022c7a634cfbd463b5dfef96a9f7416fe4504ec3fe50a5d6f0aff329a9ba39c2edbc9a62393edd5f84c676a7d9e86229a4b5991be8853cb8caf897698929f15f5a48d29b6afc0a2b24e5d26401f84ca23d568d85e9a57f0c34c895683e831425ab2682b457fd65b2144a1355a52d6578b726d399f69efbd23f60575a64b45c5bb28a3ce32ff31c6e63d49b94c006d9c315aa89e10535a0a43909d999b049851ef73f47627f1d0222fca6a2cc1991160bfe95bef08a6243fc71472f880fb7d11854e61eb2200f0d2e2180b579d0f750473596f75029f31a0f1ba6fdba2fb8854f01f7eeab2327d66e6ddc3382dc10fef829ebad3beaf84335b8d2fd7ad46c6aff5a8f1b6494d71cae0197cf1c7a195b4cfc12cd247fbb3de2e1f27d9289044d89f775bbdc4b8841a3bf8a1107bcd4e64db66836155b686c2d60d4de6b92e67f6ede0e198834bfcb83b492d50bbeace22ea6a93ed5ac1999eaddd516802e44555df2fb349bac3ae423152acad9314cd7990c9d2ee783c17052b62c2f158deca94de0f63d30f0d5dba66dd6f32940630f1791aec151aa25013b9f7ac64c0a0b57ad250a1d39b562a1bfe6782a36f8ef1a812e28a93afd5f40e0317c8d828f31c19cffaeec41631e4d81c29d987fc76f4a8989dc0a514e46853c94f7c9fa30b24c3b557e7b03910c99bf72e33fe5b3d4384ce37efd0718b40df1c4f8d4e062d4e103ef12cbc5b14b62ffea9c700a52f63df6199a0fa18eef77be4bd79c955fe36c398d7394921c6c2bf70fe5550db27bfa776914bf06d84a478d5077224e996c6163afdbbf37d7ec9b9f6673688dbce8d14ade71037774b166065bb6fd01514632da1e02ec19cf44fad364a4fa01ba8e6945ac93c4fd2109a0bb3cfad4a9ac1fda638e0cedd6694ab8820552ad60fe9c2d80db865dbfc42c24e0e766e7cde66c55e0e03749b8975ee76d824bfa13f373ce49fd1020131ecf089134568764717b2506e9dcbaf5d17bbc7b5575ad41f9a9bd2bdb0dd16d5cc2c3add64a9a00265bcf2eb4e1afc7ae2c1297e3dc6361dee6b2fe515d291ccef8b14006be1850dd125c33b2d7cb49f578e61f73551323ddd7280fcb1454cd401293779cd590ea0748d9584cff642d5b0bdceb4f8682a56403de5105e473edd7700c2ca746a46f221ad74cbd29053c562db9d63eaccdfc1f3157a820211bd0639f25fcb46688f51e74eb313cfaeca9abb9e6e2a2b2e308f86f5b351dbcd5aa834ac9f25bb487c71d7f2fbccc578d38a7d1f4e9f5b2099c098174c1406e9c5f1c2d4ac0bced7e740f33bf5d46f90d2924f412dd765b54f1c993722f14d66217ee17da8204225f2d1a3b3d8364d5c7fed11099952f383273d3cce700ebd48bac52ab131f2f09b073dc844ee0f1e8cb115f558135b45be2d11406acea266f6fe56e7170288c530918e7da1bcc60355af172bde9173e6ff7695c945a79a9a4dc7e60c6d159c8909a8808dcd1ad62126b96a2e1935104c4d9fa79a3d2c5253393063363fb9d7dc4a26f437a217950ed2a66c81c65c1f39943ad70e7b63944f8c785a56f16a88bbcf9a18aedc13a765973046ae2e9b02872c66973f6ed63cab5a1fb0ab62d1cbbc3875d9cafe8b493e45bf6150ccd04cced457fa59c23dd384ebea7cff3b1476d59cad796a7d60b247ba814e3f664fd356c7799d9df463c912caf246d48f7695a2d551c20254a823e4854ce7b85248778556907c9360a1b366e7752d773b58ca0bc4b3b3bac0a4622e50e3b5f7e261bb3c0f9517dac662a17921111acec89eca493e8c724dc5f4cbf5cb8fd4149a92fd2ef5c22e64515579be4e8caafd572f18966f8a6d0ec963eceffeaf6b8c190389cec9878d50417fefa9b5dcf608155b73be85e2de357807649466ec797b0df372e6d0b32dc502d31fc9c457dab81792655868f8e3f43f20a5903c222ad8041de4908e2dfc063600b432c683d09390b9c5ff6149b852c46ec030199f1c6ee853b17aca3ed8a947a15a864034d94819b222fcf0ccf1826d91038b15a4428c81dff3b3ca4175c018bba46274758da459d3a5d3fa7b08aaf8bb1fb019f15db459d486a369131922566cc355ed8e477d6ea322450ac7e82d44296d98ab8dc87364208c93eea3ceb5e21181cd74e8cc65e3c111d0af42e7cc66772da3926f0bcf369efd77f82cfa541990a13afb706281ab50aa23fecbcf89667519d5e20b8cf1213f1b0db01cad7e0eb6489451305b8d2598cd7b617c8f9103313617211b1c1dc6752c906635e75ddfc0018f0d7db6ec18fd39deb8a82e2f1c0f81044173ed3db304b94c67766c223f3a57a356bfdfe1ea416eda76fef17eb8840a63f864bae385b36a9b8620a28bcb9ec1e2aac44f793483d5e3f8271fae608d3706063c152347b524f3919b7a8f98034fba46a0ff43d0e29e25097a9120540ff18feeac52ec74512913aea646c9d39a79f67eb28e92ef08de98f12a58ba236476c0c09d00c5114fd381ff42d1f3c1c4eb3006784ce22ca5a7f3c6d5c548d733c4796e289c150661f5af7aa341c4f24364f2489c739a7dab117a0b6818231846c2e344d83c34d5c0b46ce53da9ee090a4e971f4a4ae174771151b0e9fd0297a5d68ff53b416793e6912148d4574af645bc323bc06b0a0974b480d2b85511d6a9aaa0ab4cf350ef517108fc638e7fbe6ed08f0e908dcf23670e529d709b33deecfabb96265e32790ddd5abc238e0ec21251a78f8c39c6ef6c3bc956e16aa0aa3f82c6904d50c90c0a7c074180374a16574a9fe59e737588aa9ac8464066238a9994bb97410290d5b5c0bf4b5545a90c57da5c1f2234713095a96e9bd5509b8260f2f74412ebdd2dc39e045570007cc97a61e419a66e37b72cd5432939fd5eac3e8627b2eccf005e1c7052a12555f1ff8bb9af09fe20c33223378f794ec18f92d56705eb1415cbaac570977cfd445bc46a7884c41ec191b5ffac7fd8251422095a3883d75a27a66a314096bc83a7662a432c61134a33650d16acde5b1dde38b31c65263d8906992d1a47f780ed26b7a79dd6961226d3fa003cde86b8bd917f7b9f82d916ee0e06e6259b3d7031c4c9df1c042ef274080e4e722b3290f55dfe4ea677b517c088c152befaad7e0c60f687a84c0f553bd22a74f70ae5f4e0654b707efc0c37e662f6f1fdfc9a330f463454962358e2c1b552d7593866cee77c74e53e4b63ea150431fdb3e76f553cffd0ed33ba43829e0735674ad643da787292398f592d449f8ee5f1b316af3fc735a0454be8cd2386dc0bfcc1a1731c83450fba314223e8d1c8d6c42e69252b424259a4b9767f839586e43e2fb0f9eb2befa7ec57383f1b68c24f4d72c9fa655d1bd12fcb8c4d1809611397d4d7c695c13aae153929631c47a7e1002929fb28fed99a5edae3d945777b7d44a91f5e3ce8da8cd27596eb3e99dc46913125a74669779386902ae9d796e34a15a06906ea604db871e475b0f0b25d181fc583397b20dab5200af63e299ae70599f714741962835fe98ad5e9346799a9dd292cff59d76d6ff05a73990d993bd5372760c0ae332eb13fbfc183568d064664fccccd329f60bcd0e68ce3353de7d49107ff401e8e3ba0730218551748fa32753e33883a8140ee8789f486194bcaa8418354e8491d1e54125ee12a1207db4815f07a53b427512aee1de5bde1ba6ff630e8a9c29e105b730499cf24656c4de8418caf80272dc72a4ac607f8fdf05d7b5dc4fbae66cedad1b1255a32ab6c63f6bcabf66aa898608eb80dc88f6c6b401a14c61a8b6dd95a8fe7acf16db13bf8f93e2af240c2ace2bb5c96b1de52f4423b28f5ac8e3723ca78f7efcfb12f233e8f17355c9e8cc22923b367eaa2169185d2bb9e1851b7b2c038b9ec302960e3af6e6b2af71fd3c4f84e639ded821395352e3427fb015deed324361cf2a87d78642517ae20f2e181027258ac5aba9588eced7860608bf96c3753ace696f88ed5279b7c9a685e954facf83a501be12f3659917468ebd842f7a003ef36862ecaa0cdc9d414b6c3fb9b1a01847284f3b2ad340ce25f9ea944c868513612b880296da4674a844ac79616fe24af5db12a19156e40b2d0e43a62273324baea4860ff0ed730d684e466e3dd8db4e7520f4f7b1f24a5a47217c1ffaa07c31f27979d22ca5663b101bf0278f231b6d52bf3ea188f6cd4fc64b3745b253572d4a2efd906f7e2800d19466f670916e515cbce0c30598e217dd34666b3d11ad10595fc38315da41bd01ae97714b31052b26dab56a8d5e4c58246a71051e503602c211c2252c8d98ef9cc1057d60f8715c872e0ffa795d42f11748a1d6f17507b1af0d5058f6439e50be8306e342288bd719c7e881963e11d697728b72e32570734d4bc0866bb4f8a82a82bfb159056006bd5d7feea31c04a367dd86b335df05cc4d6f4ef4b5a00d3cc634021e61ddbe4f151d38ddd62d9ea4b06468cbbdebf39065576e27b2a91c771d9e6952e95d05db09750ce8583e6fbb7df50919649eea9acdb6d0f575f1d079db7b5b3bd53a353443ffeb8dc4fa0ce740465efba9e14772c3c2ae0cc48d74215507cf12dd2487d6b273c67aac0afb494b1f13711c1653ea07ef2ef19cca2f7bff45fb280c10f6700bad2fd614fb8b2758330472e440a6fb5fbaf45ca93cfee4f3e94e2941ca7ced43bdcf6bf5f804b4028800963a8a1e78158b2d8bcfa343b071c253e5b1e1ffe0161211f5bb5b8e8e496ea4caec0bee63749bac82a49d2acc59bc5f8163db1d0f84a9be87c8ffba63120b4a90dbefa8d84ef6e7a9f32737d9b37235b9b3b95f1a4e568431ef70b086a56f52747dde67587983e8f93af3b60a274ff6d4cfd2834e7d148314f9e439462d4249455bbd59604d247ff52c85f965853a08d13df7dcafdc6d6bed230650623cf6a4cb94bd5edb28e05022312b7a423fddd02d53e8ff1f79eecfc79e8ddf4d449525a859ba86ceea208c21e1bc6569ea6f68d6be1e397528a9a1c90eaed8d71e1714576fe78c3a79ff58ae8d2e26d6c11ba68ffce084d700cc086b0c4ed16d874ae43db0cfaab63ea056b8b6f3dde422eb276be1a90393dc440b24a412c4699e4dfb168aa35cbaa79fb7889331fdae0e4a38cc16df24d5db75b395d839b6c368969c057ed7f0e4a38ecf6090c15a71ad16b3a695d70177e0f9a8781b454e0f4d8b3f1aa212fdd2297f2349ca037b1ac8414cc22f32a30c12796d27d673a5e498abcf15206d123bb1a04fd89381ee8a4dffd9f6067c2aee5d8daf6ee60ddeb2fa4799c8b5fe4a1cb9ec5391f45ff2a4cf86848813271ad5f0b6656a1c7e488207cbe47f32bd8ac241e3191926a2bc030464fd04a1d035f5bd60a39abc3c215bb7918293ffaa8d454f5745eb183c1e1645572045d0d7b42dfdf45907fe8931dde753c94a93551df16c4ba266edf4646311c69f801344c10ca901783e91f8dc13d61cc9fefda307b626dec1017b19a7f48abb9eb3b2fb7ce4026d183413c865b9a8d3b4955ef5b05c8c469838ccfee237f6785af0494a071e09bab3909a0f72ebe653c4316c8f5bb3b5e02d4ca0549c353f96459947b73c4c075d85c52eb81bcafc151134f15c7e5b394ab53dea47e19c74b236ea702c2b3d072097534c47159aba672f824afcc42ab68e98864f75e1fc46b48ca95c6c746e5797240e6579adc9d4348653e91d934d4c644fefaaab807e8c9a6a54f5d818d60ba648c55cacbba7625fa7a9d432257a2278108f8b7e8f93bfdc6c9bfd8964e67a1227b20c1abfb261a82afd236ba88cdfcaaf6eb3a8935c7e5b7a8f86b3f810bf2a0432c0e2ba252f28cd968504e4d5311d3c81993e4af90ce19270785c2bd23979fcd9b9e798cf1357d782bce84d3f4a6c2da5e8b04d93cd31bdaa0e65377b0b894eb1ac751d51b3a50b0b1c9ca4285d7e3629ff9e89b5cdac762f59ddf6869dba686c41bfb5949ba59f29107c688c267f0be06f79666e397c4604bcab214219282dd79527abb2bdc10176964dce895facaf2681f4d6cde35556916e834af5ef991d39b95c857bec866da2be1b05ec4c121c61ae4ab18bc2cb32f5fb2a148c518b61623042a5915f7c90bf2e66e9a730585159244ffe05702c1565c31259eab73ed83c75ab6252358cc121a19f7f92d59c269d48502852aaf3586b7a3b3fbd5de2a83073e636ceebfb8cf163fccfc30963676f2eca33cfd4cafea7a66c729bc0c402680b16beb6e7f49627d790fd321b8e122123e96b8272195f9125bbfeb42599ff01915c9978f19c16f2f1bfb219626eccaf24dc2c8bb0256e95a45f852812ccd9904fd67baeb4be6b48bdf93c96b6ddd55664f7b525dbc433e628ddc550d3e7e8d30164ecb9420291bfd6c9f1d4f56b1a58caa62d08e69b582e8977b502f608167a584586958db56c88ef35b7c2809b27a370304160e6efea64f2f99ec1cf62badd7783c2e3ef2442df4e6cc9ebbb2d1f2825470a5b9606bdf963cd0464f5d31917792b94d4a383c5194cc217b3907ff694ec6c24ecd416ff9d2f5f3f81c38aeb76e95861bb496494efd602d37fedf594f37b251c7c22c9f6fe7ed71145d8b9a2764810231666c1148fef0c07d8417aa31fbdf8a1412648f6e2cc1faff3975c69708ae9b555e195fc8e2cc259a003071f5351452b4c2ae1f43a6839e97da80bfffc998aa5ee880504d3364549e22b44001908e3eab19d346be5f6e3840275e1eda9d1f1fc18891584084c88838bb7338acd6a954be690f65411ee93ebfeae6ec7d68290e52cf9f40376be2b319228ee1174324e56f9393f7a6a1ab9b53ec6be5bec72351fb951d5f67673ac4dcb026f5112ce3fae63736b627a1c55c74e736c1ccd58c79a47eb522c2364e266894f7197f1e0bf0c0e3c8d92f3e6e1f85532c61d5262cfc143896db54f1ecf5dcb1d5c661ebff96676376cf9ee408886bac00268e4b0d3349dcf10404d44bc025cfb3e543ff77027302816ab7d45a6bfe8a2858ac6d6810a91c42c2b860baaa6c480de3d64c55325807aff4d53e6a5b0e49e33a36e5ea9d97a555ed564bd0dfc5dc5d01ebf9b9d4380bcb3dbb76523c10d40d74b65440618aed227e8b837e0e01be5a92050a209e2091f169761dc6380a056951f7a78db971f6fb9a1286afcebe98438b36e33d589a1fa315a52b41b1a107471cac2605d2e16c5495f19ddcdd78f45d2bfc15d5280c1479b9d552e852d4671e2d4c553eccc304efee9e0294f241e53b049a9634a292c235fdb46dec60035e8769d661f1f97cfec6348ff7c588ad4d95b0d543b30636251842fda2aaa4aedb88932a4839dbf5462771697173e1a4d8a2af1bb0f2347f5b81b583bcfa10c968506fe00b6ca35c1d93da658d0be1f781acb588cc0c0f62de1e076795d20373a353e7575de3f6ee85ded6a9a9142259ec2e1ad855ec861717a6a3660b63e125d73cb3e249a16d0d55ea57067d768b02ec0b4af49b18c2dd9d5ea341193b2a4ab2f8780a27903910530b7e2e233e7919bd5e85b9cb43628b66980d66aff1493ee1b8d478b81400ac28b7ee27dccb7e2e1c54d820d14d8f1135016e10bd978319530503c41a947d04d0bca3023dc9708abdaba55afb54dc5e0c8d4838d5751c1cf27bf14365e5d1782b1d36baa2fd65b2162fb8bf62df28a0fb221ac5f7ea81534017976cb02eb2e3060c9e39aa77148ad2ccbde2047209fe51631ca8daba5175580113be568a91de461e54784ba8704d10fe3ff868285f6a89d2567940d64e58aab6548915a9ba89ddf16aef9f21236426415520f90448a0e15c12e5a281985664e006fbd13d84eb46c3ab7896cf92132877af98ed7e7351cf1a44e51538b66f1778e00d0c52504627ae2d1e45a6b3e6e59ad1447f32371acc7224dc03bd82ce6fa63101c1564b45fe3afddc0819142a4c11731d4326a8f65d09cb9335e8b7a85ef0f90969ac6d995623ab1efbc8b0d9e264f4cd6164b6fc16b7658249104ba862f01bae561dfaf221889448c77bffebaf780f44fd7a8c3b59a94acef7b2e1ceada3a7ce06c79cdde081b82183476994523ef593bdfb960a8836e005b1785471ed318d5d72e432f189a7f00e40acc8d85a17c631c4cdb369ef0ad50fb366c7101422454901149fd721c7f261a20ee8097ea4918677133e5fed05fba06fbd5dbc211f830a6f03f95be3cd37b6437a92e257a61f06955240edab4b44e465d39f18e7035aa329d9fa3148f6f28241e48be115274de0b328ed75652516e19a8d8783d52889eed5c8d55c70e5ae4e33bc2640f79a62416f7e140d9062f0ebefc579deeedbb91b26696ed7a9413dac9093aaedc766b46f2c70339744c0c885bcca3bfa03edf8f44683a454661903d7f94cb0899d872a9bc78fd9d72476dd89f6127504e8379a068014605dd9ac09939ffaa6a39aea4b74c1581901c3e2c6c1955532810bfcebbf247fd9e95c6a81650fe922da70f2e945b5d3bc561199adfd918741e163934742d43165094520a3a0d5c83797c949db54241f6bdbce82e51586fd8cd04fd74c5187083bb830e9ce43b77cfd8a2d90ff7e3698b14765cca29fcf047cd6c5946bc58400ee10aafd28d44bf7ccf51006cf22cb0a6416a4c847c3386c9cd9c62cce75097c17d9f4b39008e3af18f0c2db0b54fa4588997320d607af3b95b056d2ec7d54e241f6be059183b03f226470b4e253b0779f704ac9ee34249323cc2963ba511b14373b8cd35484dc6f1b84c45979d1551d99506fb7b813f70bea0a23c1f94b6018e9fc5564ee1a946afaad90e3aa57c2b47467e8702312ca463ab3683257b5bf37ca8e7243d677ed823cf9d9acdc214f55eea7b584a7ad4f4f6fcfdd08f68ee301034a2c3dc6c06e5c1f483a26ec784412ec38a9f562dbfc4c347d05179997ad31731b62301bec36c036c2de46b205055a6320d2bd69e9a4c8220fe73cbe91cee4e2d3118c0d7cf7d486189a1020224adde4843f5738c3e169e87756a429ee505466436da49f10bbf0524d11e2b7d137827c7fb95b70adb84cce46aac81ba92911dd1918651179beb42b57b2375d2ee16ec827ac9751eb647f36ea4763855827e10cd249a051c404e6937ec7ff4935aa06552d2f5d0e3abe95e3a8b46569cf8adce04e37d985def4b6219b8823d688b601dedcb9ec9647f454967925f4f7bd973a7ce65f558a522d705176381718093543f6a76ee5e81ec09b02a5a4871154218842b13ae5aa143fbd86858555b13ffb0bc46a9343f8b553b7bab314d47860259b2fc74baaebacf561cbdb9dc9b6f15593e1d13600877d9b1f3ed3ffc0e9ad4e77e38a40c2c4aae992ad45577e676746ae779eb8b18c599978947a9aa3c7050e69f279bfcc76d900827dab98b4d94e4b3e773016dee83f9edea147cec0f3a0c6f6ff5d8c67e001800fcd7d9009f7f3335def6abe5190ea9076aac0c1e3887080ef61c000acc76f16ba9d53dc5d05f9ca70be61375bc65640bb4c6addf88d28644c57c004db3a1024de387fa497d1ec4f454836bc4994f6476d7d75f93be40ac06a21bed77589a4e3b00a3fd0470fe98bb15c4e91ab88fa4989a1227e052c3de3cffe7d454285835df372d5fec41c774cec27dcbe790e94b132210353161119f4348fcd41391b34376316a7a000618b9b35c942f3cd1c5b460e755f8369b28be6cfb3122ca4ad7de51e2398ac105b3bd26b2945e50aa9fcf49ff3371624d48317a940df7ef2f62b457bc0466b48dd450957264f1fcb19bda70b0b5e06c73987328474d2ae0a5bbfe822c8fc5e1c9e639a210f2554053eab3c7599e03cfdcee58a189a4b15d955c5b5e694e2357b22427a5740e64bcced75e16a9ac9049616471066f6e7b09f30e278d9118eea607fe2e287fda0f37cbabad79219e9e5f25aca1ecd160d9ecd76512451a4a3c635f4e96695b72ca1c76ba64dff1acebd8ee5e60db4e8a2df329f0e6d08dba9ba740b9dcdba1c0ccf83349958c1b82bd3e89ac1cd00e25aa6fd4e7430b9a1aeb9050bd28a54aa35bfee0286197e8d535603a25afbb6108bc92905f6fe76e9562bf8e500d2170cc0f83a127d26db23885d3468d590229c326f53d3137ec6ccf12d9a1174b5ee1084f8142bcf24cf8bd5a6c86ecc6fd11a9f015cb2e43b1239c2a07348a9f9f84d66f182a8ddfc9c5834cc7084cb178a05cfcd1ad572865a00545755f96ecda89747309d8b41b4b6950bac4c9d34e3409a094eeeb1403490c7fdad62a95033a6dd7dd39f6e50b8d7ac0d804763a6c65dc763e93b3bd5c349b5cb1de15a2269ec92af634f9d01e2031bf28703fb9263580e2dccd83e6a60ecaead2c7b5a7e317fbcaa5a8506dd3e60a439cb3c1f58b407bc3900ce53ca555edbe1bcd61b3446e91e03ee527088098c2880ebbf69302168fa9c6a4b1f1b49b0f1bf7b2ef61719b9207af64b63a77429646833c3b6242bda29c934eeef3fe05207f363b8077c54f8dac053d202aeac2dd91d52d6351cf2d7d28d9a4327e48ce70c13fb05bf4106b7b09c1afe6b2b14cf1701c48deaa5ed14f35a8a8a1b843c28548fd60de382e8774ec079ce861fae85f53108eec364a0c1177013d7271f8c33240151b750a5b6c30b2d10af35ca9f375ea29c1e24439b2a67d61ac072e1b480ae4edb6a98bc99a591e01a3e19c329a131c08e97ec7738be0af7d219a16efd91b39ee8b3f79aec3affba1364f1b8362d309e8d04cf7e7d342bb362c6f7e9d9402d13bf65f3eaa4efaadcc34d6d593a380669f861a2dc7bda0420c41a20ac4ab4370efd9d6b1cad5ca1f2e33e1c83408de9396dd7ce6c2eb86d54c387db958e4fcb15b185f8e8ba70fb6d56f0b44a110bc42b5ed63265ff8757ad4cd604ce917e3028a1efff72b477633b644113a79905f8f1192bfa383eeec556b213dd6a9b4908d835775a067eb9db539b5506ecd2f38d476e9337871d04338bd379a70bc3a24f254b4b45c22fe193664e8d9a106e9e6ac86d7dd408712561c727830599ac12ee3fc2a713ddd9e443943a80f22928d94e34b8227e3c91bfde0263011fa8c71f2019f33bc7cdc1ee194d4e2d75a683f984294d5ac67fcca5ac7e40e5f9eb309f193654bb1d2bd858e7821285c4eb4f66d844b00309d2deef37f3da896d52859caeb75743203df0e0a2206315a141dfffcdb5ef69d7506cd9ad060f566c778c86a9f3cb2b4b65ca641e7015704c374782ecf65ee224fd09e291d58d119fabfcf89e51f901cf0a172368076177263dad6d35d6ae34e9b1e0f0d5d137c8c4d787b78b4579aaab17846afbaf095a0d3ae60ddaeaf144a9643bf9e471a1bb82c30ef3f7c44e1f7a24c2ebb01701503b816b1e0bd438419003cc54fc18336ca3726e00f961e7a02cad8e903632583a3de253aefc27d7bf3098a27a3d34c38cde80917fdc241ff49a9c969f0fbcdecd2b53abdbc4e4803a4e98b64f8b947f3e0f4eff30f54a90cfc54f55497f89f036ab882d315553b6c01c536eb25cda219036cdfe4a2d56ac33051ab86818ebc9d99f11a67a69f17c870e7c2a1e35776c467d93af110581e49374b468d3c9d3c354bfac13910632a6f7a73c260059edfef61e6ae33b4af2c46f2ff992bbdde386911129b5d51f07c7d6e02507285a0eefadb413e583446ed6944e10fd891d1a17aa83514b97abc794f19e47e1a5f68132a60ce7821255756e28c4c453ee5c656d006559b8ab638dd0a88f0fa0b39e2c626b158eae4f4b1c3e73364c6bb50beae74fd0a71df9e345e14dd1dd2efdf94e27bf258530a2b5b012f47de10e27ef990956878911de4c47699344cb6e190bc791961804c92f6a6587a265a62fa9d714e04f0245e7943e81b0799f9a1f313950dd59b158c8c0ca7d707400ff67da298a8b757076e81cd64ff9207d0aea9a6dca5e57b61229fd6056c1bdde63d9b5910d5496017ab947c972cb1843f197cb6d9ad677daea885f1a8b381fe5253b54b41d978370baf651b306e9f4096e0596b6d75dab79c8c89fbb49605497454e26efb4ba67822237c7b13f0dbcb482f1f02fddca266cc570fb3ccd7f80161a26da98a665abd20c1b5fbddfda85b5cbb25f73b02880121120d65a5c8af1b9ffd5424e951bcbdd975be5ec0938e1e259bb10021bb38e51ea38aab4a3a0452234ecb6df9c71c01f10f9df1b0b4d7a0ca9e81d98f4bc28b2565267cab91d356677cb76bf2736b1eafd729022b1e1341441b46db81091a5588f392864aa27e0e6921341e67f8c08c0e678ca5f6fe94b4bf372569b79298dfc79a13e32b926adca4fb10329424d403621bc7ceb2cbf39ba33a342a3cce79f06db803115a9380ff5c074d63ddad866c36842eb51b298927a007031448335cec4dc650459cb0adfebfd0a3afa4b559615245e88a4729f7cb7ffc104547b6c5df2bc3fc0838446225c016fb58e57ef13ef5fb708353c69e06aa9e51af124c9d170c299b27d8f01410ae1df5b73cfa9ac4b7d9172a3b9a28328a17af92a32ab049ada7d8edf21dbd48e82063de4a3ac144b0d17f08d450fd6a4c5285c7cbcd2d4def67f0b7490a59f3312bcc0995757865bc66219d2736cf83a2a925d9bc4d2a6328b335d05b806a8a823deae553f6f6701264c8a1a5900f0a837853f067c41e0fab778c9de61975ab60142c9fd73b15367c0b14a2e82b755a148dc88d0056657ffe49ef21cdc883f3190c35c56b9cd378dd8ef9e106967bc53571b0142cf0c0858b30edc8000da1eb699c5bfde4e9d1f2e7fc3d94a916e3c358b82c25a8fad21fc0bd258645d8600562a4298542f6d7e31e23fd6064542d3b9c06d1c67103a48febbace43eba155a7a447dd840d594e328c49283c07f222d25b512863b7e7210455ec32a1d6959a49456eea316081c41bc916028ce42a4c55a5ed2c901f1a9bd14e452f01731b4ca2df1077c1bef36c753c5da39f459a7b2b465dcfaa2e6e443a4839b5e8672a0be2b75adf2a2c8e9b7cb00c767587e741da626f7541d53fb6c61fb54aad2b047d00f65330ba24d04c37a205bbd19905779aca35764e71e56c2e38b28973cdf2ca184e36ac72686f9d64ec1505a5da11b6fc7884a142cc1946d011439e7d909e21d6a7c64a41bff550fdaff8bcc287a3df9df72cb4dfdf7209bb6a6fccc9326532f0960e35ff20bf4b449ccd9be7408a94eb91ebe5e26b09dd452734e700f9deb3b7b6984c44eb721867f81d3310548fbcf88c085c7fa62ccd388581dab4d966dcb23ae3a1090ffe66ae1030671fceca0362d503b6a8915c4b855b2023ef610cea494614fa1b6027863ee91beed489825742d53d842dbfcca98e68fd520f2dcf9f83dd1b535e7012ee6758ced513b7566c376474caa18d60a002f1778ef90965c0d180575adca068ba87eae9fa7894b43976031780192869cd4131f70f95271596edc57bf2ac526b2678313f9241552579efd7e914b3d26c2f586338a59ae2636ec47f00b1a461e515916168a3816d142b84142be7f14de6ab855b7d30b30e641974478c61d852cd8d385eee8baabe15134ac672cbe85a3a8856fd0125e6d4836c5778e482efe06c3903983f77615e8937e034d18b34aa18f34b0be510588194c122f1b6c746d00de6645eabeda5f1b762d3484463c6bf1c7c1413ff0c59da9d75102c2b95e2347f06f186864348d627a412aa78847e95ca985478f84dc3dac6681d808887319441302b8c9825b094f8cf7388ef929606f21dffdf4d96a9e93e80559d67ec9e8ccb9ee785f213f35387fc47400e25dc4d1bc83d7ed8c355e99b263096d547f5ec23ad3b9d5be03a4a7c04004d514bbb20debae908de4197b792c7fc0787ce90650160fedd90347c63a608e13bd8041931ecf79350a29161f948545f34baed613dbbded887b00b7d98b7eac890c4c5fd1f27e61dc2f2c9ba4bc0c2dc1dafc0bfb499f0e6404b342bb009d102cf71f60f2b51d4e1522a12fa33048b77f9ac9c0a008084e7cbbfd3426510a92b186174de1ec7716b99ba2b5669d52c5c8764861e047d2cfd23b8e0442a03a12613cf10fb573950c0145de4c35186a59d241fba953e8ba0fe1fd9a6bd0596ab210d4f0de5dc886b38e4ada424cc9dfc01a89226db83ab3857fff744db404effa98672a6c3dac49ee67117e4c4a19ebade5b62ec0c7d91727782732f54e21c98d09e7886f00ec42891d1a4b22a39036c05a74de4ae0f4c72d570a0ae9d2f6eb55e0e3d4e52e7fc2d3ad6862f34641623c036cd172d19af7d7dae935d72aeca5a1aa5221287630e363820d6118663d3f5ddbb67452e28c77d3243f6aba8607bcc1a7b81dcde00e3aedea562224a79607acf8c5760b61d4b801ef5f95c593460192d483528263b7667b6782fbf3da5084fd3be320f19e0d2391359f70756e24c8e48934466406c0c1446c98f591064d2963288272cbc89957e5a7ab898efb4cd3e13ac3d7c92141ccc6a6bb8f8118cf2748dfd0646aabd94c8167cf5d00cf12a8276a0d2413dd63e8e405dffb331a16392377cc820f50b66b633d612c090dbde7443a0710328ad3fb79a12f1394c3adb9da4536830e88f3e8ae60fc35211957772e7801302db2dc38d0c87d94bca5130c7e47f04210b3280aabe19c8c0dd450ba130533ef329d515b881585d3f8f9b06433187e251f437cb2046a9b21b5554fa68223d467daeb263c3666c457f373a3ccc88cc9954eb0b821b323828c58c9fb8f1d9092bbf12f8fba061e1ad08a76d281f5ce9f56f5b66c06c2389eaf746f8bbab82210dc82b374a219a4f5fece714337c4ae0441d1643020af7d3953c39527bca5adb181ee458de80929937d0581dae29ee3aad8e654c1a8ea6fbccf4a2679fc6c75bf338f69633d88fcf2d6e2b3923efb864d823efa5a2b8b86f55d233c922d87a7028047702e6a992e7aa143377799be0d4b5f36541c6e8419b765f42942480a51cd5623061ad58df4ba72403966f7e5bd95b4a0d271ca57403cb7552499e013ec7cea6cb6bc3ac16823b925d2ce9eeec1413240b5c511ac27793c4eb7c02e3533dd31accb65252de6c1869f10076c341026ab68a5089948af58650c05c077174fae17959d02a5b19eaa946692b4cda1c0ec6e6aeffaf055e210cf0224385391f69bb76da5c3db9b0cf7cdbc9d8a696931a48ce542b7484cc77c3d1cdd25e8f4a0da7335f3c57c5eb88df6e20cd1c5a5a7d023da65d43cbc6974bf2f854dd09f8ca0dcadb0220bc58d69a5781fc0a1021cb6e357c9a6cb88e4833a6c053c904d9ba29c8324da93d45720e8556d5a0a5ff01ca6e91ea81cc371d1f15dcaacc1e033f09ad0fbdbeccc69bf3fafd15758af2e47dd2842a2cafb7d742baa38858bab449676d91a5b65353ca854bb847cb35679a06ffabc7e7aa86ed70e667195f7f290867f2c4b2ba9ce90bd821e738db9ed8dfd3c8b5548c2bd0cf2c3c17d7add97e631597ef155fd8c9cfabba9b107dc27b647db41c20144533d95e3335b79a77a809afaea900e61bbe1f346875d59524fc1c427e6936d42d1303839720b103cb74f00dfcc878bcddf289ce11d081814748a23293457a38be77c46d09aa56558da1844d381bcf947a5f3de1ad42ff80356eed96975dbc22f67dbc0c7afa786a3800f7a88ff04202b3be1abccc6da3e3fb870218e013fd3e77580adbb4c9139c8caa59d323769ca02c17bc900d1a083d2ab85cf6f90242fa07e0741bd1e4f8450a477f8d87e0c7a1ecd3b453a8a6587958362d1e2150a94c10b4c34cc204f6597b5dce094566bb12df6c8c9fe5c76e0b9d6bbf3acacda43220ebebb6c9f46e586abb7ac2580a3248579f3580a39558c1171f94021aee86846a53f261ff08a62aa5e8d6c2b23605ab87360464d2e9210566b205d66d85cbd64443a541cfe42ce8c0abaf08b7262f8b0764634df90efb70306e8772d15cc630a7f98fe379913786919ee38ec021c0b753a8cec6b1be16a79eded1ec8894a1b2b28ec97690e4cbccb5d2a8c69a8550594c4bcf082770d94edd3f8e5c8ad6d5b6287481977c3607b30a8632c104a1b9f3b56400389ef633f29ffa52a400598696ed856be2f968f47033e7a80f345e1f83b7745b9feb6f433797300389cab06afb8caa6df6963dac2a18eaa46b53bd945f5d8c4f51f782909b0059e3dbfa733095b403313537fcb915632ca28efc1ee5f1190665a841ee876717edf4d257ac7e1bb6ae78459469fe5e03ea183bc15e4c4b3dce6fb65966060a9c246bcbdb1905c109156014b80d8be4ad01325ea6bd2761833b92cf66c4116d56c24a9a3545419fc7cce2d482ca2f0c5609475da49ce90486779204a184191e8cf0863f4862e32d2d09504727c2b49cba72a0162707daaf4677448ed89458558c2d2d48aa02fd8b1f9b8b64ae8dfa84d9bf23d9910d6ee1a8830ae2ec7232012748726c2698633ca1813e6372c37ae974c7e39ab5ef3f1ef451ae65539ba5fb336cb2fe3bc7f0f95f0a59efa4cde2a59409c5025c17f29455ac147f97472d79083fb6909a898a7326828856c710c3ad0414a86dc4e2860d5daa8d0ad47cac91040ccfccab201d136875825e32628c5cd2f6cbf31b3ff70e7e79c3183e6a3c7d07019ce009d8f06dbd80477492ce55d2d2c85a970813121692b589339917f8bff77a8aff019805a05cb1bfe565b024f01b3c52312379ee4f53510f4fbf631f093e62637318485e9fa67552e17f16b16387423a6678ca7a89e30e35fb1e532bfb47977769820166879c1d5c09897ae07737c25be80979f2ec46880a5f37aead6b046f3d2bab11d9f764eba2837dc7700c0d5a699149ba44b5e4c7b30620f015eb6b3f71b9b2ccdbe5c8b32d8f2cb08d2c3136cc69a101b636087139920385b58df4fb0ad1e92f29a9b60e1ed5b8554a398df40688ee4a467b637805e29d589d001de29a39575483a2c11770db1a3e39825199e0ce0135a638896c0e74c0b42eeee635dfdc36b1cf939ebec13a9d9a9c736f9349f51eecc414724ef342d16f7997560ea58f752e89a5a8a7837a61dee83620d12cef7ee44005fbf9899e4a6bc150f43146a4360772205d9c35c1d9a493bf5de49ec2cd644515631ea2233962c5404e035657517caf15df8b90d84de868c366371d11ace09e04cec6fe2ec007a15b757d4fe7d056b98a741bc05f5988e00a3f51cf2030231002c572c8e5937564137f4056b0713ac74107c70d686c202edb2e2afcfd6149323ab4bef9337deabaa104ae5cf527c8a5e2a4919f224a2e05f93f902561bc8c69136b9c88efdbd83a1fa215411899901d1b00462cb88b884be8c09b7b62b2ddb8b69e1b4ea51f4539d67af1b3e2a2d65549ee46c7dd1925349ac5339aa7274e7ef465c2ee724e743a60b032f929cf21dd65ebef253cb62f7cb0a5a2c2a4f34b62abbd7545b783ea5926ee9dd687e4a0412e938500497470c4d97b3a03bf64ab039704f212516ab943cee137411b585c630429b670c43b024190a609ff98ba812ad381d3f399c51e99f6bbc19b43d438ea9114aa6fa6465939b1499f4798aacf8b6fda2edf49eb72da66b0459811b7c989025ca4d5eaa69912f9cbdf7abddcfb67279bb9aff54467308079b30eb995f3fc9231337d54be3dde6c59e9963d6d1d8eafb68279e9ce94ae4dd48c5aa7b35ee34564f06bf2c50ca71f5f0836e76e168e14fe3aaeb59b5ae90b01a211028db9eacaecd122ec4761f68e13bca442627e2352aa0d52e4510cf96046de253a51cbed07630a78dca4bf4b07eaca0388c45c23a4d908c3f1a87044c76d24c40f6156f33e87799ccc90bc801e2167e8fa7fb376100816d5d2f08facde413b82dbd5ca367fb9ac2eb39133535735632cf774d9eb509d82440888d2f37015caad31f7c8a4ea816f7d3584cd8f6e6b76e0fef8a4e8926018c17d686f7af73c65cefcc573755c08ec5b4b19d796a7499192655366477afcc51339982aaa5992aaacfc7293ce289c5b2950526735ab1b425ac942b2e87383d7202cb663ab6cf16d702fe58797003047eefd08d60c316f295d6675fcc3abe7bec85275743d76bdf5a70a380be97fd957b1c9668202b9997ebd75120281e9826ce9a3185fcc9559474a923ac4c29052b7651a6ce9710f30a424353004e42d8f370243fbcc1d80b083064ceea8f096790015de6d0c1b348fcc5be85dd2962aae563cce0b5d2f6bad5372c50a314a4dd0bf26139127078155aea42219a5189564e8d99a48422061c264e9e37958b557fc83e7f554b8dd9be548c4e958c8dee4988b4d304a32407265f52f7dd97197d04a7eb1a405d463d1e7b8f1d00416f04318887c58b9a39d678f712ac8fec89d8325b3583748ce1ff8c51c1bfe794085581287eda9d45cada83b1f3c0d6d15081ba1babc78952f5bdc2f89322808ed77159bdbe586eb51e5ffedda1e9ee2bcde3ff410a2abb6fbfc84970d7501749e8aee2c5bb72ae5d364cb28e44585cae5be90f7f634064d48f655b6c8dd2c776fd3a41fc6c084bb0e04f0f7b91d8dca59265ea9e2d7d94c233b32050d37d23e9dd35e36c0e24efbd764966507bb373638694eac7f1faf7ac9814445957b20e9e105e715596b0346cdde5205a0caff9a78bb66c63f1ef8f52c4203f8c879920d9fedbac324ba14d2c260da6a8cd4c3353df879627b615dabcbaf59e510eb9531d8f00b2de41e0fe881a1cfb026248d79f510a90c224214dfd5a79c983b714954fd86f2a3bdb41b67d73985f246e7fb8d504e74f9fa6a1faba4cd17dff12b62658586db59053cb13ff281cad17f5682cbffccd77d75b6d478804f8c4e3bd8edca589ba6e895341ef7114cfaf46af8404249e0109209c68587b70df0bc12ac92da0b3004cab8bafd2985d68b820552c6346a44f55828f5b26b041e19c40a602263e95360e19ee89600f88e57a91c962c99c94a388fbc89c28d44e5c6450219d224cd670ff068be9cc012819a1bca25109bfa69951e3b44d0a77e41a7e3e8232d54ce9b8020c0d2c23f688a848c00f4a1188fdb9a9043b1cb4a397e8b8393b5e41aba87f17dfe4d2164b4adc635281ea924ba31ba92d0dec558210dfed3a020f3125e687d77e7f170af6a1f2854cb4b9f84c20e5877f856018a003dcfbf6d3325cea2cb6962f02fbeb194e5a1aa52a733334568cfb43d8211eb0369fcfd19b0005376b541f61890508f0025e2fbd18168e14700c0646fd3185a525a8a839c102b988687a7cf53ea2983bf756192e4f32f6f1bfe8ac4195fff0df3d5880be808e80acc35c5ee7e34af5bf4d865ba990de4740ed193b886c04d0904917a961f79030be491e936ece6c2f8f1296846716737bbefc6b6454e6e32aa4005cae6dac17e6e3bb730f07939d7d1a924520a682643c360006a1577e01922d8dfc8027baca5fd08e20c473bbb446d6a5bf45f14c5273df8ac28f6262a2cf29304f5b68e59c3f4173b24cdb00680ecedb5e4a3d2334e9234b91b5be3e8f4724e5fe31047e78b8362623ff590949f7fb853afb70122b75b3e586ed961ab744fb9fd3de4fbc1734f7cc2f39620ef00ecc6ebe47f4f82eee77a1360abe831dd07742bb849c0d099f1ccf64b6e1bc8c20b8d031b65159c29b8ee49f12f3f2a446f728ab81a07f09d759aceef9d2cb3374b449763d7c43bce067049fa672e49aa2eb361be77576cb9dde3b63426f7866b2e8a0d1bc563d4213aa96b987991e3ce3f469c3441116fb39c0b950e71aea653c84e902c2419a42774ebe01f30f6686a73a3327aa1418fd71c525c10cc3eb648441325166dc92dc507d1122c78cd6194ec50f8648187f8e83820e88ae5057bf65f781e071ec3991cf7afc8fbe52bd34ba4d907691108da28c221e55f511beb04eb1cc16c387bcfac82dc77012d1a2a53e41347dc44730febfe2fd34d0929ad5b4ecc3522bf595a2f94bb6973a97510d3cdb7bd82501836e0250c6a51e8d38a0700fd22423e966b5c51a5a1b2d960cd81d245b8ad320aa030e4e9ea372eb49777109f874940248bb58f97ac92dd43135f3c3df1bc2854d7bfdac928a500de0efea03ab1d4d8afc93759b8c4e7fc5c5d6ea5a23abe343ea3741da4519fe5f9283f645e67c01aaa855e6da8f24321e13e3c7dd6613da0a1c12db662c2294aed6c7e285ea6e29f651530bfa5167759ac456589bb4713754dc61f20ac6f9700133d89476183df0543e825f47d0339568252c75e2d6728027bd2b4cd7dad04b903a35334c113d86dedf0a1e8501560efde8880eac643ac5b5216aba1965fd78eee9b3af5b771d2fba66a4b2823ee299e563861ecf87a38d77e4e1b75b1dc3b7c816745a25e38c35aa54b1d7940426700876100777071b392d3af44958f0e54e2619badebfb1012357ef6284f6edece5997b83473cdd68be9fff27743d398350435fd6b90a22b4cbfa3fc0f57ee51570d49474e1ca979130054723f32aa10e38b386586e61fbd782cf10300ed50e6392898100f81ec25553f958bee0b488f23036b2b61593adc2c3647218ca617cf2cd7eb570b067787bed1e2b716eb30297b5c44e164e43f580d99383a17c827bb75edd66dcce3144b5c24aa0bfde353148e6afe59df9ae462e696c63fa3cd0c32b15f6824cba2c5f7336bab100f77f586e7656d4325217127e2742d54c1e40a678ec80b4d19a4523a7c1f0e2c82206a99746a68d19f3241a9b0fbc02d0f70016c4ae6f9e15b9d359d117a8502bdcaa0b402f7c13da01b9283a4f7d84826871dee681b39bd8914cfe68afa848ce6567d57b873d01e893da164e1bbcdba21745ce603ed2dffbce4ec97bde484ccfa19033dcea486d61b8d1a5eba9b04b8e735dee70e86bb8647bea04458bf5659115b4f2dc6361c9c7e17d76447700c1f34fd07f61534f41c25a566c175d5892e16da6303fc3b46e2dd76eb990a18ca9af9b3760c53ea5936d8709b5eb1b5df741144c1b1b4a6ff1b625e063737f4b42d0f72c1c270beae6bb84b43ac51b3d0343fc0d14fbecfe12191131f38cd69233848070cb6f369550cbc36208094672025a466237a4bf20cba18ef731c465fa74b057669a20fca416cd0851838042a820781c2728ff3afa0046ee0d85800c57a5d04760e56a06379cfe65eed72039c4d4502558594a88bc4cea3e75555f00bfc29b7f053e819da2315f63e41b9692143becd14d4c8026e400e589a15105fa63adbae8389de86792b845374eed0205f60fb9ce705367924a5079fa2a3bfa78f9344bc03982816af6d5befa5adb42fc1c437e62b3ac4fddc04e1603e7b0ef7c55d453cf6731f4e8d7b0734460c6a85f7a5f1b231239dfaac3ba3b4bfe5c907c6a624a81660efb44aea1f8125b32f237454f526bdf0e7dd0ac8eb610a291eba1afff7e9d1ff17a2c0338e7de1fcbdfaef4f14e7870a28385433d1d81906ba2bc8566725cc39ecf7ec2680c66ddc93f2e26bfa82bd9ac8f04a0abc9d7a8773a8dfe95f9d5cea3bb771e807d391138a070e04d3417fe89e3b8f65365dce1f4864ef3c881f0b6691ba16838b3e69b92670f540b03562826dc92b02f4994b18d2708d2987e619ccac694408660ee4106bfb5f6ce645ee38b2240bea942d3b4f8dc9d3fa6f34f92827e189405cf146af15a2d655cdf420e9484b80ace4a27b4685a3dd99e5fecf6867dd17ecc6e9a6f2926629c79901e916ad5cbc2698c6fd8146664dcac6e6f91953b34593944d71c8152ded0841abac57e4a358dccba946362dd5c776755d83ff64c40de78489e5c3922af7769845c48c5b5bd1dfc4b7f0466bf9760d0b4ab17313278db13f9f978497e6e3242306fced4048064bb66e29955f4e85ec0bb9799ab0af865629e8ee6c389190923f6a15e424b2bc478dc1339e188ba7de6b2106642e21e0e31641a5a88e9ba270a75c43b3eb4a1aa80527e2691c2590a7f591365f701024b243654d838016ff74734cb0f559522d0f89e021c9cf517aa067aa1d5c15f000952ae9c80ece58533f15bcf8e5c59f65a99a116de416dda164d79b80dee2cdc79b24f8cf199cd28c4df96aac79b7431675d1d6581a09a5c78d2e8c5b603c86ee61efb16e76f7c15e00c320cf4b8122f91aa3a9082ed2ebb180d2a05c64509590ef7cc783900ab1bfaf544d5c571c40a8568104b5c51afaa749c9a46558e538eb29afc04254ff436ccf1525bd81d07425ef0b692e3992bdf888edb60e48f048e1d3e026b364c7e16323299d55f573e9533f59a055b07328746f03ad205f5f08781e5eaa32a24430670485aff84b4b097c4e974b9e912018f0cac0e01b3cc2325bc92e3bef008c3180b41f9b6e89d48ec97887a194402a72c6221aecb112bab656d3d3f76caf3c0d78c3a168f4e506f0cda79683552c65f852e46672499e5be0ddbadac6028d25365e8350d8ba68d1b5fb1a94eb112edf0d769f2e747de4ec31864e686a5cf0b2a9078d22026552e2392bc048118e37d3471753b1d39cc7a8cc8a0d1ddff9bdde7b8854305d8f5701dd191c718da751f42e9dc97ca0296972164818f5e1aece9cbfbdcdf29742ab8607e6c2db2dc8eaa9067705a178595ca23cc3b0058b22a6fd20c62002a781d9b82c9560ae24747517c995bd55ce99a8574f1f4b3a0551798133846e46b4c0711b998f5113bbc05609c8ece92229079cf6f3272e738d0a85558c20a5cc246781f80a20cbd1b5d524218cf65fde89f46dbf18caa3a70d71b4753d8564b1bf52deaef3f79392287135db9c2f7f10f168b1a6a69ab9edda7d97ec2be1f2d31c53418fc7aa4cd38a667c47d599ed91a79e97d9e94c9189577ba9ab08284143e6af3052f13048ca7f1aba8fca396474518302984c3a41667c89e7b56891ff96239dba70d565f4bce806faa492978b88033b674e0be4449a971cb7969617c2a6bacbffe9d089d28a873761aeea42e8128c9d8019bc6a7dd9df7b1c5f3cf934518dd88d3f46c1ff6eadd04c13564ad781dfb2d11ddf14c2f6d64141a3f23a614520db89b29a954d3a3086c03a50c49a362239f85c30cb00d7d7df4a321622010083dbaa0b7d70ea872622255a144a76b31826cff21e9dee779617818203bbe46e45540478ee249aaeb90357edcf78dbc0df1f61e4a03c48ecca82c40bfc5466eca3b8a0444d637e70b29904b24d70a65baefeaaea9580a26fba0b550a8702ef84b6eceb8210476fab43eb5c4048576a7e0b522ee30ee0caf3d372e863141a8bb85cc7eddffacbe38bfc120e9b9cd6cd1752dd53d01afdb7c91fcb10a905b98e2ad39654384a4f6a04babc9350869af7bf95179e932d180a48d4fcf26c063c4af15c9b4fb3d018b18be5aad507e8d30afffaa9abcbcf971b91fb424d6dcac8ce772e89396ef3cd1e634d32169264b9b3365ef409800a84a1dab8accd44ddc3c223d40a130342fbfae768bd30dcb503c480b488d784ec0ddcd8659fb8a12c584631a339cd73b99226b1e94a2859406edf14eaad36c09a05572f7350df3a5bf8e7765f286444cc67647b10ea05c05a3c380722dd8fbb97623a09b14de6fcb7d4a803255cc9cdf8b031265b42073723b25b722347a42a52bd2a3b67f5db29ea6411c9d04c9e130e8b2e43c70c1d4604980e5e704e705295d5b9906efb1e6500e355c397e5bea63e7716fa1296b92a3dbd39f5fa08de79290c67274fe794e595d08a5138099c5ec8d848431c41ef66da71e6dcadc047cecde82ff9419430482e6ee9af350b66f34677e1babc03dfa58c094bbe0475ca65b894b0f8d5b3c8bcbd4424c6d93407c3be00618e46e6764fb55fe755c2596849f45cd460e3473e87909fcbbb4d42ee4a0c83e54615878065d150b1996952fbb04834161a6a28f30e6a0bc204f6e4a820acf4f2717b97ff83243c51c87f7cf7dacf325157f28090f6c0dec0d7623f4e73beabcb6ab45ee01e27886e73c5a4fd29071021cc08501df672adec40926cb95fc0e558c66733f656c41f0cd784cf3d72edabd21e7aec647128d57942f5f793a1b441a323ba054a00e5edf40fccb1a9a0b786e477037bad04e5f360b5a561306b5c43db62ddd5b8b31e633803ea37f725bf4817514878be81ef6c23dc7ea0346eb0d4f1849039e2059ff6b608cdfe72cf90390ee6ff34371751e69fd9569a145f01c0c0b505db663d94a0beaaf2d9a2f76dc97fe87651359ca8cdf67d6bad5c7d0636c34233f87622665eb70c704eb838250af659b4484b3af4cf7e1b312acceaa74c5e80aae2b0c10ab0dfca3a1ad30ae3311fadcae4c6ef4060c95516d53fdb8179b9dfad7e4f3ca41b196542a913f87d19ae9020c547a4e437b71c7c676ce7f41927b765c7a8ff5daac4861a9e7d50cdbfca05c7dfa5c904cf9c45150c690c17ed1c7fc149fcfd3069afbdee51aa87ccc712a444f827c3b0e776741de609220098bc85c9a691c2cfe90e72384c4ba6a8f6292d982d0a8dc4032f475b880ad2bb23ec5ae315277b7cc08d3138ce7c7958b5dad1966d545e1de8eff4ad7b085aa279fe76d1deaf50f405689f8348e8690b95c6ca34fc003f67248ad3fabc4da2f8ecebdd04df62a9ecd8979b11e2b6248ec5a626b9f272ccef83e874ab3c6b737fae1ac12d0a192ed8d811730ef75380bf9e1cf057ba3ca6779b944e915fa3d40859b761d6e5fbaccaab112b6a94ff465318120d3093363df1424b1df07e24dd41689db5390884335c7a763983fd30d16b52aa793412e5fe7812a176a46513829ebbcf9075a3c7f95c722f3c8829c999060ec2c01a9e2f1798ecb63ad8d5652bbc178155aef518f90444b8812b411536d33200d402f366421071fed77a038d199fa4ac6cb8d6f4470ba5a5740b25a15bcf4876a52ea09b69048b0e93bf17f0e5e56d3e09622e4195bacf413cc3e6d9ddedfed024ca8944034fe2874438ae36a86de4cbf7857bdb5ca9fe3824ef4d80cd21a71882766535723c68093fbf59638ae85bdc7ca64f77af35f4559d9053eb2362b901d80541e5dcacce344f471cbed5978bc183edd0cca745764ee925a71aa49d4dcd9f96fb0437d60cc307d5ef8ec2b11100a0153717e716cd9fc3792b99db21a3b99008c765777cdda8cb5841a894faec47a3ed51e3b218467b99a7772f6dab9b02cd96ae2f347b404b48dd70c1cebf01fdc9de012e8ee5b2c2221155660f313c727e3d0a681fe9f9c3518251a186b3974fc01bbcd26772e7146e0a43d9e87d308144730cc81df30d365308af18241ddbe401e50b4ab8723deab1d1f45834db460716a936e3f1c94a32c544c4cc14745d0f569d0db828c211c17400cfdd565cf07b78f5e19db5a2bc2b5cb4c1d13ec092976a3ff24a9d5d455ade036f4ad1dbc2c62e4ab9f39bd739fce0834c153c0b5014cf2381e9bc16be05445221feb75af99fc1953b0eb5abd778e5d4fd98a8c454d3d2ec933150b6e5fe410469379a3ff280833c3eabae117016c7b7e07c0d3bbf5107dc95c611b327d9f32f7d4603e5ae24f26667d3ca1a259eb150f6d93232954469cb0af0c9fb75fc1de5c88fff02559897bdb3e08660d8f193e1b36ce22e3d3172820788cf94c5315af1695e4fc2afdbcc4cf1d4acd0b0e038ff810f654704969425b63948f28f3f99a3b18631b3ac1100754500c9c0990bf60b3c0545279aa2e40cd30fc20950968b9ea9d8ef74de4060fea7f73d13c1d9a22eb5131f7c9280ef643253000f0a0dc36989629796faa310a162db70851d7dc9602f6e8c30e72b039b37b915b1c2ad9fd94283861c5b8a459595edc7f3e20644dbe56f6fb172db68bc5ce2257601925996f6ccba217fc8ec08b578757d0c8cb71c82262df975f9539eaf28688554cfb9027ab7083f88b8c3aa090649f7da599f01a7ac9d4313ac87da51be45c8cc735483db22a7bdbbcafcd42de4000a4f3adae96854bd1cd5edf46af81badcc7f9255afd6a9b81d04a6d64390fdd5adb3c8ebb3cfa168f32636d3f46dbff43b056033445ec4bc2471d8c6aef6fe3b7211d6636a5815ad78d8b1f5a10f3ae70afb5ed7cd19ba127bb3171a0df6cd0f26b7dd1ddc74c678db9e0b5fbd4f8fd2c0849c10d5e464f8daa0112cc97c4fae01b8894afc06ceb25f8fc4552632e3b454c3fc600e4bc3aef6b3cc98a7497ead696d4775d3e88006e7aa1c7b13d278e02862977773125bae868a9809b26ed131beefebf7fe677aebc04c8ba68ddd00b19e0e24ea69af6b1564678e7f07d5f219709f688e38c376b47ebbc7878014382c95f876ff19ccf0b92eb0a13d0eb8c57ac29f8ffc23b1fdae979f61513dced5a7e6550d9de0ca7489c17040b42528bd253457e8f7acd9dc9d6083e9edd172cacea2dfa9a95739b320a3d38310a18782338af093c7935772183959a981a500ce9f53530177c8610bc5b3aef050850eecccd9dc414c633ec798d8f3ec3a61893e3f15f17604fcbdc22995f4c701404843acbc4c3462ef107a42548bdab24ab24eb6a9ffe82c457b17abab42e7024e95db1011b854cfa64779accd3cdb60db3684f9fa468d1151fd22e9235439f993a8f81e435b582f78d08d37ba8693e28e6186d0ae93dd53c25a8cec1a8db64dfc0905e622b5f48fc58bb5bb59ce6b1a98953c79893794f0dbdd0a3fe274f5dbc8941887b70724afe6cc4d9642dba8666bc93c205d88b8cd8885b8a15c314bb2e8f9ce3555da790d06dd0d63e05a1473d2058a882d5ebb4ddd84344b9b147c858c3a6e0d79798746b97ad1e9615c35678ce7a27c0097a379c8c195d7360ffcb9262f5df4927250619153330bdad318735321e0e28c934a940a562d545a37bf24fe05ea07f318cf0a048df7b54618aafc7ef8228f9bb8a828af1219111a479ae57cbb604a850489ec7a41462fc8689bef5fc7d32b76ef31e538f7a5227b1c33fd6ea4718de39033910afcb5379262def1e27a30d4312542d665bd82bc833a818076e7e7d28a5b68894e3e26d7078aac7efa13eb39fccb99d1f24ec2d0c745de8b1502b09daec869c22cfb69d901ca1f2ad1291870d357ea240d48ae9c2889a657d0432eed397ef740ce1dc24577ec5a8fca646db539c172d9317e690de9736910144ee5dc0ae932b2efff275b9e2142abe702d0d78707ac1e12776226cc16aa54c19a6c225bb79384c8f367db237dc3a53955330b7aa905aeb4ed95d3ada82f7a73f3b6e3945dec7a2bbe0baf4578af4bac43c74d742db2397450a1d1e902e6fcdff9a89f88907eeb2f17152c756e21bb97c576b3c49c8338ab41a595ef224eb9f3a0ff2216cc1f1da72b8889a42078043768b632db03d8a0d517f85e95ac7ed4a5af86c3dfd16d74dd1cc6498a32dec157cfa2cc07f49564b15da8dc1765b473d2419f5b8c201363ab7db7a5f4ab62e7542e6f2ad10239c06e52cf1037b772a5d14616c071821b4ba6d8c642be77a14db5d1230a5f48eeae5167b35b8c31341ee9034baa3e8258c85528abbd14e8fbb89493ac14c5e3c0153ec5bad80804abaa298ea55060eed511ae5f02d48831615f546837f73373d67788c52d7786869179c3535d276d595e65b4e1bb36cfeaa62eeed3e2d9379ebb450d75c97879b30a9f5e928f83d39c0faf1e07534f308f7155ac70bc8d348dc019011a22401ba792738fdea51a6851ffdef39ed9816401add0f46c0f0c416e657324a8d598da257fb0109f0c983557ef9eae65d2b5010395ff4e0703215df0d6c3054c8ee37813106c079139c31b51125a2892db2faebe4d80c719895bbdca7addc4e3d4cffefc0f57374302f06126e0c89e4e2906043d0cd75c8b9c9c92348c185b80b38d9e8d04deb79942b35d82fa727a34d0b96ff2cea12dd9cfdf436bc8bec3ea27a1ac18ef0f6d4bd315b45421d918d6dfbe5b2fa65ab7972b178a322b90801b66c113149d6c4164d0bf884e3739c1384dfda9ae8cea92e95c343f4cbb72dec2ea6d0b9a88fcdfc32fd62369561e1753ad94d30ad08ec20934b9fa03679f0ec25d2364ea4854789da784345ef42ed294bf6c064309d3d2c47269c7a4f0dda0b1445a6fe49265e8f9e46ddb2d17eb3e5ac85c3f3ea5a1c6fdeaf75601eba12b5a85b96490c93f8232127c159a994c107bbbf3ae43fdac0429edc819edc51af73bb82629961e228679adcbf4ab7f7b56bf362bae7dffb242d206248ac3ea8850084b9e87c642815351afaa522c5b0803644c8f277e675db915116fcbe0e869c32673fd3e92a6e2451101e64a2bc05e3f991da5a0b886c0c1238f4c7725b24dd631f027c38f4aaf2c6643dfdd7cc961fe03459afe059dba5bc35cb71376e76611769e3333f81f604fcee4248c35ad649e3a1187b81fc1b502cfac75363d35280579d983d12bf9a5f91d88869b3361f1244d8fcd6829598ea4b2577dcbe7bf80bf71aefe3d290a6f9dfcbadffa944f0f9a3a655f962423a084f179234d66a0899ec7dec5cd1af9ebb265fc12673b1bfc4c777cf41d7bcd3b539f6231fead70b6b79bb21d9fc77ef6a02913ef88507488586c7c7cda781f07c8e6d1385f37e8da1a3f9b6b731f977b226ab4f840bdfc8a91f614550bccb1099c8587c12981889710b9a576ecb241cbcf217a523bb23c1b26e6180f7e0b3768d64d212a158c59ff1345bd538539d2efac99293d3321a786988068ccb02d5410fda349568a5cb688afd1bda7820cd16ee0f249ea0242ae316dfdab80f90f09d7e0023684347b299b9580562921aa6b15009f0a9bb911e8d6d609de9e3df0a8cb6a7675e30e76594c4528bc8c7c3575b2b3e8794577cb42d6e845a6fdecf462c106210fc7275f051e4cd42b4304f3f6dfc7690b3aba7549865bc683fcc1a45a5105b9f08cd34911cfbd423db8d6ce503e2d916e83205578b9ba94c733406c50d79508ad9472d0a291cb6505f706d5e9c2ca24b5fa5b77a1e9ea2c5f390a1cda2ac76b8adf3e8e4d387ce408e1a3b970fc7bbac9493d2961bb4dff6e44ee682b175454e133cfa74f2c679afe605351ac6e0e3d3656477fa2cb72f1bcb9f8686ed3dec9cadd1e3ede3efc1462437716dc67936f3ace31d033f92ddaeaeaf219acfda4e21e67f2fd81a75216d69e1ca7a8f1e56232d21e366b9c2008819273d944daa34ec3e0629283c1b11657f66947a4bfd6fe4cf40c12db5e3fbbc8d60deff8d99acf4997322c685a06bf3d7fdb5a5b3d0a7e92083c8716a5a2a876dd3a0181a858b9de4c4f027ea33f70113560ecf41d37f33a7fd0fa85bcfb77253a519bb645457787528fcc78181fbb2aa5552a630f1ebf5443fe984b3f5d45a0c13caf4ceb2906b11a48481eed5ee6259c73e62373451cbb5769d03760a123fd8bb01018c1cd780e1fd6b602d4326d251ff417913e3e935cac37d5cd109d95872f3cf5e7a11c73c7338f5d55300f7ee45aa757c164f985d1e5841d58337b137b7e047dee7233b0f6a1e104bd04537b99722e63f86b7fa6421186e40c4b0f8b8d51df25b9cfdb7c78a393794431e97ca8584b9fa7102e7c9f7eb0aa425cabdd49293953b90cb73a091a300e57d235ca251b0f8ffe177ae4243ba4b1b4ee085e3ab07acf5cd296deb39bae17cdd2ac65284eb35f0bb4158e33e66f80d7632156e3679b44b6d24773902e4e6d08033f7fec7d165c6b1b467c76da00623d1366828d0a2ec4b55b6197d24d6bee204763ffc5d226798b36258759e537e80adfa492e06b7a41f1c782fa2712d3fa20aee100e35001cb788486dacf0628637b320aac4de22715fd0fc6e36697b6f85184ae4fde5d2f0d419938756af3796b759c1de74983927b54fe14aa8cf9eacf98b213f02176371de3d766abbe52cc5b2f784ad158bc2413d2c77c3f0c83936de2fc1e99193ca97dcf49f1a41cf42dd2e35582eae612f662b80a92a26ce6ac44a0f1cb35354be8d3f8b5aadc95737c339615cb27f4147f504a767569379d31c8549ca862bb783c6974598fa253098726e7e5166f8ec469a8f39c0f3d37c6b8f22f4dc4f36b6c19aeaae3309a3d7d5215f99898211c08659b01b3a85c061d82c0bb079070f2ba22b31798c63cf078c0b06401e38d76e9683deb3b1719ede4dab2986af89128ec2cae9bb78a5203b229693d498676d4ad08072ae85e5810e1a84f4a2269c728175f7a5b93507ea0a62ff925758a9784104753a225bf13f0b3b19e13399ab330fea31631eb88b269e627d974851a1a782ae0e39b24dfd73540e6ade3d5ffad53d4cc22ac793cb5af2108ca68e7ef229dd0235b4a53fb47d3e4920e523c753efd162365a2cfdea5f878a74e6d28924309734ac2e591b69870aed655ab1f44a64325aa4950e13d70d6c67e2c6f1d4b04f4c6a50553a177142bd82c9b0d9bf9570a7ffc5d09e73c590ddeceef40f0c36481c52d745c8e0997a33525fe94609277f4d583d278737b790af638680fc59b525cc9b1437a1b8c155d60a11f98f16762275ac6ff71e1833caabf6228e6e9ec3421d5640b2b6b9f126bc20da2f4f1f027ddeca6de507b9ee04edc340744c2a161984c9346dc6c1c2870d816b16be8d606c722c18ceafdf0f946b19fa0bfe0aea7c0aa0fd662ce9985ec444c7a0efe1bf83f407dcc0d5924f083d197d2bb00c4c6a9fe4a7e0374736adc8efcacba8e0b274f99138593cf647fa07728402cd2c9f21e391d670bb908ada6a555fea69c78717aff7940ffd8323637ca0fd269c98e552195f89df8fa6b385b847fa9ff785de9682721abaf6625fa88e455321765598a32a5a2903231cc87418ef7e89233485655307b189f6e708173ddb450159cb2246fcf94945293772eb77eec7f8da31fb350b77ffeb3ddbcd7b74675df87659f257942d561fc8f74515e06bc32471bb5ad5420c577a153a1db90de97d5e98cac8732ad70934696542b9c6ffc491f19472826ff51c1574ad5c2996dc2ef3144a4b74743bc2bb373128767e4ad12698791bb07cafcbd048b0a94040b6b2b072681148ad82e0cb848844a672f485f685d482f3c400b73a41233aa20412a4b059a36751a1def7fd75527e760125b045eb3e103592ec68c21c8db3631ccf15911cd32a063442caf4823d2e9f68b605aa49b64eb687ae1d705a62ebb93ed221dbeaf12d4e4a0f52e30c5276db7a631bfdb528a4ddea1ebc4ba392191368d24a9c0acdc40ccfe8c6f683e7da57aab47142799dfc2551373ec599a4e635fa326ce74093b8961c71e49da43405611d1943d7846bc9b280e33870d39feb0c1a435a471229b484037627a65e2d45a43c348a5ec6bd75acff164f4002de34de5ea9d062ef6ca8a6231e32259c94d201e9b46b73994f63414274635e1dc31e1dc52e413b5afaa4efb789e447c3b592793226b6629f5b696150dfe70ed27c4674c8c46af6a23cc0a215c2848a6e83a86667d03a626a4b79d1894526f977026b0bfd2ca00e06f66274819cd7ce731b1676d91fdf0d91f7c9c2b700521b42a92de500b64f1b734d8808d89f98e419a32871cc86cc2d5c0369a980d47173ccf0cea9e8094b29b110fe7d0d9c113f06506b86603f71d53e34ad127b167079c571c2014b746353f5f8fa19f5c8b0441aa5960ebdf9e05fedbbe31326ed24d95a2c49e7c1349b3755483e4eebeb53cfd4f98f8749e2905d20906da83dc6865ae64cbd8ec4df62656092d60a6299ffb60caff8dc74e10a8069783a60eb8897233869217f8ea19cc6f0a53b3fa198666a4849741bf1585010cbbe69c6cd467bbc61033f536350daa0e6ec3f397fa9a04144879cbbea89624978f097e320eda28810ee5e21d715e07909dd5299cc70a42b91c9af36d58e5aabeca4b3c122478c9a2fc2aa3234511ecc644e7d06147ebf192661217c844ae3227f07a7fcda1ab4fd3b3a9cd3790acab70192d5a1aa2b8c35d413aaa7a942ada3fcb391ff8465791f2e70f4e8928d755b96cd4ab386283d1ec5ccdaf93c961ca8e4481ad8a93da0979a1f2e59c3c3a42d0b85e9cab27051812dcaf42808d97a90de444ea4464825568520ea5b350bd8d8c9c5479e89791f716f8db0f5b8e6e2696927d4ee0c8eab6cb68ee768112f59621f7e984c55440b2c46a76d98a47b3705e6cdd82127e7194c8809794337dcb7ee315f1702404bcf2fce005f5b0cf82be85b637e65fb59bf83e98720ec707fdae1b2687e66cb2d943e2f74fcce7052bc9b049380cbe143d10fae56148afd70350ed5c3803d7197b9590866c5d51b8b24db6035e3b061841ae70369a79cc316fd44987f6d8859845bc653636b8f59d718c5b124ced373e33ca15f0952eb2a3726df5f320e971df66f0d9ab5d84f13d81ca20296e518bac05fc382139c53d9fad929be908af23a4f74ee8c10a79f41d19d21df49bca53312102773908f37c82c97df31ad2163edf8b54079a25e70fd2196fee59e06db2d07c40ba50253fe44591aff4ca4affd8612769b182172ed5c723f4ae5ec9871845563bcb1da284d54a936a1b935bbfabcb1c537df61144c4e697b508baad159a1d281d71fe967376043336bdfdfa9f072c54bec635b0320a60c98c4e72254b1064a53522b3590fd7744d5c6d66255aa2d715bce63c16978f477955a90e8b0e0cf5d57444b0202f68b8386e71d235a1303dbd40fc18862357d06d725432bb7998f3e9f0fd265affc119f4bd45b3209ad0c2a6a244ddc21d2c469847eab2705ccd02fe649527007fba67d3e131c57d5d44f6421e04bb7edea01ec6bfc07b8bb8312fd5ae627f3d02cc87cd36ed7b0276fc997854fefd7f545dbb4ac555d04844eeb180504f34c9b1cb7edb5953af0a66f25866ca7eef1e80bdb768cccbbff3ebcff708f1e6bf7806f06982ae287a1122532b2465f1e13cf8bfdb824876dbdc4c1a966ed9f76bab512722b8ddee7c8892f9b55bf40ae7da66440e4633768278392a17c578c3725b37be83cf51c9f64f13b92e6ea581f204079ae00e8966545b38e8c8bcdfbec4d32d5694feb871662ef6778f658e556bdc2c5e8dc6cc189d6c050bf7ab45e39543012c015e863ca50aff19221e25a96d2bbcaa84548061cb0e0e2a3b0b838e14ab52aeaff6712dae57e63aafecc8293f10b094df8ab0f4442ef69fcc6399f3ebe613645a8ff61b8315d9b8d9843708b8fe96fa54256752291f123dfae1271b74f20c237471d3e7e106f80e710a1581a28b10a3890b38c3d5f2efc93719f473b1d25996cb498bbdbb3fd87fc4076af5523452abd588b7643c76ca59690a6bf385fee914a99ee56aa0fc0110417a2393fa9aa645d994fe45605a166d562819c6b58200c860126dc38fc77050e445918d767eebc77a9929948820fae36ee93190a192b52193cef214e34a16f94d27e6e59d0a120a285cdad6fc43d0c04b71083ed8b075a3950106ac40d756537aabbbc34ffca01475d1b63c5cc3a8ba95ff9964e21c7ef4bd4c8268cb8d8b746fcb8d5dc47a301f72abe5f7e19bc39f3b1d0132e9bd6548895fff610054804867893b71e275eede526496aa2801188822f404ee1a549115dac05d9cf432f66b40548a4c9bcf1c25416079e33e1cd2d7b9d8870aef54bd1bc6f440cce48722d73f05a1658f9491cd748243aa1ee5cf111dd013c4ab787abaaec28e2b49782b586d038d837d0e34fdea3a2d1cda39237aadc85952cfa9aa979d5cf25ddf6fb0dd1477be763e8986b056a376180faf93884d4ea9a83eb906961f8e5c655c0b4191367f88614f39169a24bbdcfc73b51b2ba8be79c65bf49a1b18e67fa6b8b6e1c1ef09a4902cd10a51ac3327728f9868d82d65e497657c44a8460452fd47728b9e2648ace3fef89ccfe8869f48ed8a5f72b1712866c30838bc78d090e10fa6aac002b4a2c55d0a9b4152449064626c94f8ce6f27d81d907e0ea266389b0da79f86fc4869b52728e622b398cdba3137d40a86c0e782672e721355f00d875e963e3e8ddb4b5eb308f5ad12172a5d24099c3bb29a120a378044d35579166b7c8e511fca757140db8e509a0eb20197e68d9f324b4277d014967610410a1fdbffa61416743d6ee9a59c152b3ea6ae193c28439f967987389e9461be81f937ad9a33b75d46afb5bc8c60768d78cf1339dd9724fbb5aad874a010b0c70e70307e3cc7e0ea8dea1e4ca3bc3987188415380f02901c3aeba6e88c38e224ab94234693f026a6535f6540214d3675979a1da549330a99a6c64d18e9e6cc42a2ade9fc59599776a370abd11002dd9919af97c67aa8c8f70eb0d0299ccb15b93acea24e27ee321cba83f3cf2336adbbc5177617bd6e35b4371655f53bf4b95059523260aef86eda2384a63bf1aabe130238aed38f306f10e558a7a5bb1d875a0e80bfa3a3a2b2f2151c106e1dd9388cf00bb994e4d7cab74149b3e81d64a355072435bdb72bdaee8cbfe0d12e91da4ec4d613fe93044b3187720bb00c1704e61ee3c2765a39b9bbf92fac2b110074a3cad7be790b372d225223668178b4f71a85485ac5370b9193874d6b7289c86d1e41cbbf0446f760604c131e57a14c9a053a4d34dda00b9cb844e2f9371c093d1f53c59f0b627005d5168cb0102bdd629fd4324b56532787ffb79296a128d4f49ed8a6ea5666f1f32e9d2f40731f839500d010de605094f229ad4667f039d6b24eb004b307088435653dd112baffbdd1e6d860f009b633f110422cf499d1a42d06cfdb0fa483528a096d5a12b74760838bd52bc88fcc868e9f7eeea8f02b762e825ed59533dbe5e46ac65e4d83043471caa13672c6b943449d01d8555cc1d01373852fe56b0b5ee987a7d676bf60cc3198b9b23d8c8cf285488c61ad2286174fdbb2981eed09a675d1cc7262644877afc84b843c4e929f36c1761bcabf2ab6e6bd866c658e9e4d16bd911e8c2386c4ab256f24c59c289c2e4357f8a7cede16f21cb2ca65ba69238f27c1919257fca139d57383f4cf7c7bba3066bea4815879204868f1cadba9e156871f46b684db9e3f108b1429bcebf41ced68cc06ab2239337b3b1ec5d9914015ef18c05d69f881fa283f598676f31907872d6e7455730f80cc7ef98ba442892ce03cdd939ea30179e175d87229e9592533e1b37bec0c63716b8739d6d1b9542ded71c686c8a4152012f76918f9e4219196cb1e0bf5ebc6032f67bc6249477a5f2c0b3707573b0e27a0b124e9f795835de1387df955c35e4774facf71015c513d7a29050cd6465519c4380b162bf0310eb99cfe7b3089fee44c63c148cbc352f48b123b30cef88056a033c7169fe64e1bf337e0af48968093fd4b16b02f9cd5e8d1679c3235b2299127b961268ca3f9711fcb1114fe19fca2cc8a03755b9a1c2f2a6725726eeec65253d2ebc48af048f2e21fd90ed4e43b722001ceea1b722814639fed77db5db174aa9c7734d8c061f12173ca668acb13f9edcfc364eb6fcc968dc2da6bfc914aa817c797fbaa0fa6b7fa0963c8c524f941773b65bb0d9852e61f80bb9d20dd34f10f63d86697848c02907b3244482391e759d825ccbcdc94ede790b5fea64778260944490404908d81a379c6ceb90ebe9deddbe7f9643dd1c3cd5e6da5cebc6b7440673f28aef1b65900227cbe009d12a8f2a9f4a7f1e0a63966f2f04b17d13bd9d01de8683edbe2a3b522f1512aee786290f9bebe297d9a5055103bd031c4f2aa19f32521ed2044eb203b0e26e3ec48b9a32b2c2d1c9d38c080b016b480a0f70e758974f0362af5b3c8e38bc17f1500ae5dac7b649c986c721239469e289a14803ee73d3aa1947bee0627610b95e54ad8317a24d9ce4eeb064b2715d9b00f375129ee20a58ebabe8583f4c3d0207616df142c2a84c5f5a04e2410e6d233ce4523e7f6259d2390c3d57e7941404202ef010b117c622c91e9d1c322e29b1f7b9c6f1ecbc3d03a889c2fec47fc6e825c06900643eaf505e2fb49bd96aa6b8645f500b177e4b2697360575c564dd4ac94db7116c71dc277e1562525b0cf1cff40cc7652b1cefa148ba706b822dccd3750a39ee414cad2a61832087343dcac150c92c56ec5f523951a0268ca81c11a3d973ea5849fff31a467e94834e3f7bc4ba455b3605137ec6a78189f9be56f5e0218c42892392073690d1d708e217d14a989e75df839fbd88341a83e9b24eba211235e5b9f6e772ae502c7be918f641ddc93dab2519dffaf8e085fce9a0b51d1e1e18589f32be27117fbd6a65b641065ea1a31f6fab857fb5feba64c9844d33d0f8a7290995242643e9afe4d09f6d71f7155f7416bfe498c66c0d5a9585c0a18248fe960be0cb4a512b6370e7a6eada0750e86fec8cf46f52fced349d2f0f8fcf0147b7f5f009cd913b136f9e1655be3c514ab6c009f260cfd3e3b8567522d6475249c8849b9ae29e5426b4f2c4d2306e9057f202a693b6e66c22529f1212f305865fea766e402c2b93f6a072ed2f8cb9f56428141c886f8100f5af698b5cd25f05dfb1f5d361955d1309b4da8a73118f5ba354b2b15934f72364f21844ef36bdc7be7c47d17869704d0ccfd1a5b4477de4f040d0c540359a5fc87d24d69580d2d67a781026f92c261b8e32c67221c976c2800abeee4eac24ea61ca651b3d17d1b62f0d49c27ba13d9dd2ea041cb9ac98b2024dcba32637991eee52ec66efa0d53b7324d5447684af99d0d5375361d84541788ac7bdaa2301317a06ea74fac76c9784b13b5b4b12dbc7b7bf4f5083ae1ebf2b6fcf5040fdf1044895f711b0d552711c15e4e03190563d8c565c27c7d1ff40f20fd8eec5c418396726daa95f9a9c0697cfe80e1961e633b283e977b98208dfeafb982c9de5553b6f7f93d833c571da7a6d29d013c4c62fb4b49716d54ebe4ae768a6a9e84de9ebe94d8a6ce4fa611be91525498c261435ab004e064a028586f23a1139feee40cbe9ed2e9165dc36663580771ca4eb84266d022289337a7225e0980a52b2c31323db7246033d23e1318300deca36ead2232740a362eb4d99f305180189a67d5057d2ffe5176258c03543e97477223236836fb5660325d551b72f74cfc47cca3655ff40661b854837211206b3e9c5ffca6ed9556faa9a055319c38648eae69b1aa2b08d4c27641e0d13a3962c250eda4defd548bb664f736bfaa169c2073b3dc4da143744b8e4bc442bd4fea4aa72387e680ac2cd948e2fc630db69acfad83429c0a14e7475d73339e41ec84730b2997ea3d8aa43aa8cccb90186a5b89e031c67b4130a12856db940dce7846ea0dc3c7f7e565111a43e26f29586bae6358668f3fd8ca0cfc4cb83433d21b59aef4dc9298a5c2c7bfe252d5b4655cb742f3ce898cd89b04b9099fc3e25fdc7c9ea4ff74819d71cbefb510901d24f46ecf83bfe24dca385a59e9a4d56992b27f57beee704d1485592cd9d15502835e1ebe4d158eb9f49552c497f8626866c5c9620880894a734c5fc8b4007f962b27e664546ac4640b3824892c6f4639843dd3b4808b485660e1ffc91a528b12b9cb3ba275c28068b9917e693401c2eabec2ab7baf833cca86c6119d4eeb9db45e6564138b61ff95bc4400b4715af9c649f0723d15df7678e1d89e95605dfa501d34fd9a0b6745bb8282bf6e5d6e746ce6c9de46cefc09b8975c6642873e8c72ea377537e7209af77a94c251b37a9e768d82b969c5979081bf3e5b8f60d5610b6b6e8f2997c408208da88891920049b653b3600b102da89b96bd24b8441d2cb523bee56a9c62df33695a8179fd0328fa8d7744834df371d704899b0a1566c38140126074e67532e1a01690d6c150536775c16cc92322115d79154f7930f59f5b56acc154dc655819bd609031149bc661cea0f27433523108754b03fe77585ff6bf3b1c1702af6bb5c139cbd96711e1045423810a1daf7ed453f62dcc8c8f454f78dc5879742bcc2ad16084bf4d5ce92beaecde9a718e295a27557853b2cab7bd94b9b0f2b4219d4fe2eb6f1e6418860ad8a35cb1a8bf685ab4f84212fe41bf93c0b1340a8055452fbac234f8a08d43558dd366927592f35be5a91f38bf928c4e5c0649fda753f0ee9bdaef5e81c612fab3f6f9c915228757446c54184824ef5622d025b62d970dc260adb38fe730c56890ef270a73ed66744dd05d6c8350df5173ae541212f3ceeee94a02ddb1a1966bf25395593225d5d4acb16766ff6ff500ace8367999e1abe21e90576954eba08f3898fd910a629d72b92dd6935874f1b226037ec2fbf6df56e9829cb1ff36631d2853ad2efca68c0bbf16c18f2e6f3344ca9bb46cbabaea84e18eb4e71df744a19b8a076b44acf11de96871850c3f42af483cb42317980d6b9c62f21c31990a044a37ffb0257122f5e04de682ef9e492d6fb22e97646bd120ce79a03c5c14623328f4a057dffbf9c5700c529b540095e6296924a12b6f803bb2f47ad620115d5a271c87b16e0053dbbdd99bd9f8ea18331d2f6ebc5ef1aa426c3930b91baeeda9516f666bf3df90d2ebe1dd02b518e96af6d139cb1546a2345200e75ccad46bb187b6833ad11316e83a06e6281f27c246d6f7842d8ce2ff1a53d8b91da3a8144760c336dbe8a0ce53f3beb90cdc8381d076aaa1275b681bafebe430460ada8700ed976e5f82a0a5b5af470c51ca2ba7e3ee19ba982aae970e5b93cfe9ed8d493b65782dcdc2450d92d8bcc97c71f0dc3d35374a015280d57f0949898f121a41b5f926070dc70779de621990cd5e3a691fc5e8a0d923e97d46539948ac5a0d3cb55290a728bec1e49d231434da59aabc7b06c66038dd4df987296567ed92da6ac37abd4bd99cab4b8d4ab43a059b8ee8b95b7801c97392f83f3b6339538d03dff137f6436644072ee0d5145dbf6ddc2205f71a4f12bcc3ef4498f3801b95d113b5159d75d3363f130d11370c9ae841452dd934ea0ca27c935c00cef67b857f624ca174dc5f32fd03aa70f4ea29a5ee4f179c32c6096e586060500ec72c0d398dba9aab05544e700c595d184eb9a1e5e2bdea97aae84f491572bf5e15c3e879384e1c4490207393bdc4bf6ac1cab605c4c1c87ab19eaa973640de8373db1d276abafa27b5cc47c6acac5ffc826b4a795f8a0b3dcb0cdaa3895a8a5a6b1481330fbfdf82bcb4ee73f401685605562be941f6a6e23d266d419996476cf8d810c07961e1c884982bd4af13a2466fe4bda19d9bafd51d670ea83684b19a0cfbee6fdbb49726fdf9cc0c81cb39e21cd3e4d4c9983463d79a9483f3f1f6060c1e0317cd32f3f5aecb721a0dfa1c011ad34f46bef452beabf2f5a1a78191464ff18f01cba355392f6f0cf17db347e4abec19fb9b8f24e12c6c171bed4cab2aa23a45d2b697af0cb003a835febb8ff1c7b1e1f823a36c35c287b293a499e4dbc6b0985b516ea47271046dcc4104c16f9840c5bb3ad32389294fcb696f9e6bd0f1a56fe8c5eff6e14837dc36fc351df6fb15eccd6bce4575f0ab702e3557982e4982631f1d75c07d1eebae67675090d206b088d6657e8207fb0dc1cda2697e826c0e2b0bcba034f17286ef42c0542c027c007c8979c8b43102626d2ba5192749eb8df2d921a7f37dd465257ac702be775baf041c1ede403c0f5834c7f04d65b8caa9ac94d9262dea6b0aaed542a58c544f2d24466e1dbc3a04fa0f327ba47556022ba6668e9115a270989d457aee96673c96a363a681804c13920fba0297dc680085e60c4124708eb1a5c045bde4debfb863e2faaaa30d4544e117b882af34fe233027acd46f2fd80f589939dade965e8defd8ca7e4a5932f2a50358c0ccde5a41d46b9b92716e0f00d47166a890411e3cc6f884ce8b71194e72de88c276c6dc234385a8a53b44f20dfbdd1a7f2717b475c9d482677c122cdbd233005343acfe044309fb9f2d345e557a4664bc764149a122bb1abb7011acf0c4ace0834aa8ff16298901f2bc3ccd0929576c4234c1895805abb162cdbfab397d75a125abd6d7fd4311c478cdc61deeb66a39f4440e2feb41504c9467a7dc377e2549501f608f51163b8eeb99f74adeaa4d163c19c5fcdba7ee2baf7542a8bbfe23ecdfa050c121615c226e2c272947b1f2568719f3dd5f3867f7b70f91e06fa0c2a3f11dee0f6bfe9c8e02677d30aa3af288c722d451e328c10bd13fae704d7688e0693d8194a58bfd4b129cf7dd1d6aef5ff012139f84b5e0d2c7a2c697255b670f6f33c2e037e49886e8b65839ca533cd77b65df5494c3556a879812631c98d506eda55bfcfe6baa4d35f4c59c6e8b5713e5df69791c546c77a404b64d28db059612a135e5cb2923f18d2a12f1db021d87314e9a6555c6e8c0d39ea727957db7ddd39b7a7de2b1d701e8a2f92f7638c4c1624f10b62eea190a70d301a7831a59cf658c887e7ce090402381d023b9b505140232c1e9cae22ab6f7335f516575774cc40c40fc456489477dbc650ab2dd01ec9fe0c43effd8deed8a7724bd83b0b2e5308f6340de1822e11aa81cf7820bc36048de231ed77d205d720bda8479d3b325aba2f8495c31d7cd840b7f8ee00075e71453b8ef125d0f219633f7a8b6a1dca97c47dd08c732c03224cbc9fb94a4ab959b28747fa3ff742a05bbc086fec0ab07ae5303829cbe2409cd2036cffc1053a57b369e352fdecb2b73341dfde92f0fa392e703654c2df0ae326401ca8af22228d47ed84391234225b3ca80d2610c1f2738f2fc3fbbf80d276b63757b4336aff9cc7a0191593f62c442a152f128aafa47fac5ea6ac1f6f8af754b73f8410870080d77429e48484b18bcc46d9a50f9c1d1f2bceff7ac630716ae2043e533225bff4200ddd7d52ea38e64ab9f3fc03ce5cd7fed3b72bdbc3d8e5dc7a97fd869e8329d5f89c4abbfacc8a1454652b61045b5c4ee86f9a66ab2f5289c875588df9b755427784e29762b9b7b9e6ff440c1dc3a29ad4d54123ccc089b9c81daabdc6e211fcf48f1e01eff76220c1d25c1f0f037d84e46e71db0e584a81f771d3fa2b234bc6087f0194aa18b68f08bb2738efca74128bc1cd8101f2e221ec674563746da6c3483da4d8eee2f4209a6f7df1cd66f96c0c5aac75d6962400ba0169597391673bd9083aa9ae5804a51fb2b6648c7e050e686536206143a213df138b73138999b660856951946b14070e7d7bccd9085a8d3f02e9774673542e59c8208b654eeb27e944b2263393884bbc4d067b094cc1f63b2494667161e09990f4eeb3410500735a0249eff6ee89ec64efb44c1a08ff6534a69c41c90123e6a6a7b558db672f1a5f97a06fa56fcb78396f40375134fb79e6d11594d7da88c8fc8564f2b6a7b48f7f81c8d02644f1df429aca60a5533aad9337bc2f5f1b2059d61657716792cea84beb61a617f4fc36851ec0602df8c61adbb0aaff11d809b4c8ac27f50d508381d5a2fc7b718adbe5284c74ee24b982d5939f446668651a8d6d513ad83cad1964ded4763957b73c9a377e47e5029c1a647a12d9efc116967dad9015a014b4beebe1afbf1d754fec2be67ace975867b26b3607e45ec714478d6569173106ea42da254d327df91342bf87f71b7b678114e5ad67abbd5b1876031c7aefb8bf93ac52cdceef08e038caaa681bd64b8106f81447d7df56cf8a607d296bc87d110b22a2584b0eb216f9c89c30c1b3fb8790c6496fb3bdbf3c296384885fac0ff627d557f882ce9cbf673361b2cd6ec313d936576876ac98425a92485f42ae584721344090d39722c0472c40d0763e3d5e4c0300154fc122dda45df2326f146fdcff2782715aa41911e2969dfda53eecf74563b4ef8f378d7285ae241a901ca61628775a106a9c40857abfe989d09af5d78993b840263b606f23487e77a646a8616c6457e09489d4434b08278ec9417b59a08adfd282cf75f660290009bb6b4e88f06f6f0d669234c483697f1278ff03a066b4f7dcb575b053542a1e4178f49ceb43b1755c9d6e2fed99d033edf0d5509044fa372b64e79c03a62c16fd2e95fb32f2d3f3b3fe016674cbd317eed3435c69561734e5a1bd8fcfb1eddaa7cc03f0940bd8685a34334dc2d1bae69ea55d3c903c647e7e8e57492736a450495c8c84474970a94ac1662f5184dac8fd0fd489599b9f1ed0a3c406000612d1860a3ea3c27de6c81a466fa4bd1758227fbcb00990a5d02721f599eb67b432e0923ab2cd9da8d506fccdc0b0e46002971dd6f394b397252763790e891c284c26820ef2710ca56e1e22c2ad8606400c1a515b5878e5a3a7c953f2deba43bb8cbaf19de09364c9e4a3165355793e3150a78bd175368af655fb6099f673324960c475795139be3323efc4b05b2207548eb241af557074f339ed0288090bcad59ae1f79c81b7a11ff70be722bca820218ef130e0f231abb7421c1d02090753674bddb91bc0b3749b01c68de01afd4e2e6c307eec29c753826922e2c88029426f0a9864edf60ed9cd87d0b8767294532f7bade9b06770b26b2e17dda7f79683ce5c6dd2c19ac4efd8a549970c73359b56f49e308f9c230ccc2c1d68720f748510fcc28021814d126d368ad6e1e7afef13f8bac81955c345ebb2838d182764be1ef57a25475d22d96b3006be563e26d6846bd8e65e37845288ed39ce6ab63d0dfe1687049cab42f1e856b69193201de15cfc39b2bce1eaa23abbf2f9d891ddda22ee22c29799bd1f8ac78b0e0caa205ab5ba233d7abaa456b2fb190e3d57b46ef810efebc32fdac6d9ffec4c69d446e20aa04253e5a4b6f4a53af28ca0543ab6f12173f060464b8670e56c5f54499969219b75dc2c58518e051858e91fcb43e20738fb81207ed5594f4dc95768607e58fe02e2b619eb04390aa42fb7017fd627949e968efa7cab1e5e6711d9a472a6a4203fe0a3afa76cabda9b8ce064bc3136313fcdcfbccc07092b7fc919d8850731f9b620befa65974869bff23a5779f203bbf86182018aaaeecfee4d4db6efbec06cdb8bb80f52b5d7be8f275eccd9e377817a70ea7e4f44caa8f97df1d1c6395fcbd02ad87de93ab8fa1b0536619bfbfb3ea62aabf9b209178350740dc91f2fee4f16682ed768f97b7ad1d25120b798324681f67e6774513f55d28a58327c662f0c2af9187cfedc0f1e05eff152a322bd45f2c79b9d58bfc6575747b0e2b32439d0bc9133584d0d5b4c1618905e5c88719f53786f9beb095cbd16799851b3dde7cb048e562713189fba653f0705a0cad21d9d6edb8728625250002fc398d21702372e2d616415cd9682627ecadd343dc9152ffa1fb4b721f03294c93c92fcd45ee52f08de4c26fa63e7f16d40c99a43e8c2e908f498eefa1c00acc1e419e649f89e7f2f46d4cde9820cc19c3ebbadc6fa136c896f54d0945feb8111aed99bc62601c083c51b2f257dd4a71cc59a78cd878eecc131be0e33838ea2ab9ab10e09d5f557969a6c34510b8f620403ce8cad8392b23f793fc31b4911d2ef5d1d78fd9b750307a6e94474aec066dff27329cc2ff8d3a6e6902beb22f4cb6a8f554ba3b86e03a12964dbc1066a064e1600080a4e209e21b819de803bd2a5ebc2e66097a8f6249bd6dab3c67e82e5427b156e7e35e65cde088621fa9f1384763a1bb579c76d834bfb9cdb2101c674f010be6b5a72516143463d46a8437fe9867637c328f19a67574bb7edea540e8ad2226bdc312a0edb40b96ace156c6dbf8860e815c86fd46a942d6a0d3fb79d189668be25e4eafcb0c557e2e340fd1cd16df91a64d2ff5be1419b20479daf3634284c0b1c56473f460fd49c322689e4e226a14bcaeb36a251a81fd830c0356f8fcd38599aeeb22b5598afa1a03d7a64f518bfa56df8bf76dc73e31df9b8642231d2358e5c17e3dbb8342f2943fca11e65cab60687221347c4881f7868a6cd8e1d098a8901c0aa3e0cee5cdc0d5372cd901e59cc7ad41e9a370a3182c10a9740c85aa7cbecde459360ef68c5ed57bb2ba81c199050c600df2347c0fabba632b1b6f2068545bbb0dc77b8f4a63e686079221051cc5bea837fe2f3fa7aae68db37c042a13643e4a91e132d1082ec15cb36ca98b458423e3728f1fed440f49c9be5d8182be8262e8ecbab1d55046b3e2c257e834e73b54249a126654be366a9ef7c364a577eaa9e1c3de5f3ba9b41ffee25e2de173799b51a9bb8aa1e0ca2403204df012ef4465a11a34023d2543fa02512fcf7da6a0f672ab2eb57f08db28310ab8aca4224ce2acc753465a8eba4e72fae5a7212dd298d530b835c96dd779cc659ae4f14d01da6d198d3d90ea7eb70866619139eb8c69fe7670fdc704fbee599b4b27c4d216ffcc7a3cda2161c4e91c4029efc4a47f816cac1f230617abb305301ac79b0b4e749c200775f0bff3284ea49e25a22429b12bfa5c60aef555c8b96b565c443af654d209264a6423bcce827eb11eeb2fd5fa363d2d937c676480843b9a4179046e2a93dc29f9e482bdc8369dc8c661d83c13ab99593e3eb4e3044f8afb169a3085770c6a8808e223ae1a1a4fe38decff90e7c851d107e36fa8cee389833c6f94e0301fe3d35c6b355b57ffa53cea221ffe15c88aad391d67cc2e45a65a5cfe670e3dda07b3c45916eb291ab9e1d6e9a0c86f79e9d840924f94c8531d96e16e6ecdfcc4db906e070856cf8d80bd4d2065f97bb930f0309b1dfc14c5747979fea625bbf9a88170984f209a4b2496d15b3c32f5b24bac3f952163dd69484b9705785541105c0a40d307ff6f41d858e6b46eaa4bf16d0947c24e13c3c42a92f40b864246d8824e3c89253748d89fda97a39f831f43336a45b94da9b9f091ab0aeda811f6e377a8248f7034b3c292e5f0eba09fc96ce928dd663cb587d70a34980cb1c572e284d69b45730c511b7d03819a06e0d3aa6d64d39d3d4649b82ca5219bee484eca05b7a9b1a25f58d3f4f7e2108e407b04485993373a75eefd06e36ceaac1114d4d6043b25415112713e452922aa8d346eee42aac4756dba65033005d213a6c04497a4a277bd1c5c1e9b68753fc69025bc59944acdd8e3c4757623bb2321a043088bda10ce90252807dab367b4126ca8645652caa11268794358cc2030ac76011c25301ef4dc3cfb0747cba4f3eb87cc123b7d24bbf47c178adf1e0c5ba53c52c984e749444d3e6fac50d854f32ee4d408efc276aba9ef3311f9281bc003bc04b06f9a7020fc13b27d7f72a6990cd8b6526a25819ea9773363d54e2ae712ced7a0920958fa5ea706a198194a392e4b9dc819dd2d6a8e43d5d306fede8812970048133028ec7a433fc605ddf51d3d3b68e1d081032f29b9a7d6e9da1d635a57a7a7bc66ede0e3724866dc75e1281ff128553370b1887b598ca55169921d74d8696250c1174d48729e511eaa4a7d7aa1a68fc0748a4e766b3d37b3962d3edd18d6d5c6d7fa768be6d38e0d49f23c5254daf2ea0003e49fcac5854859a12d63bdf40488dc4f4fb0a30c89ad719d54ced775e9e491381904f90425e6cce54b4bfbba52e737b0d916f8848ecb1d9658803258c90d7c649b0f5e2e297dd3b414b922960c6ce01f25ce1d079f281d2971b7811b8628016cf5386e495d83440c5404ade8c4b04e7fd4fc29788a9ec52757b5708733c67255209951b2c9bb23b36af1b6847de33a1a37a2104b5f5a9e6560d67d1c588e25caa3d581998a192dada2fd1a34a2304919f131190b2986353c9cfe128c0f4f259746fec180f6b640c79331f434d3a2690f34cc6ed5fefb200b00de0e3efdfeec304f40c5c0141bdf10982b2eb47f41f76f87144ddb61dce3b0a3ddbccc0e1be12b5491d3036447f35a5054eb5a5078911121e9a8e3eb2114130ec861850e0aeec19cb3bdcbf0479dbe386ff6bd611142b1cd7c1ac64d8ca49c917e73ec0570775211367ceabb3f8d154df928eb4e4cc17069953cf19d7ca8b8b84d4a37709bb8debb51e708712bc47fbd07ace40bdc9051e8f0aae7f69c4057c2b29ad13c76d18cd8c60f216dd43ce0b2983d1c90d535bdee1248bc656da0712ea44ed8a1a4fe26761be30415ae145e238830fd50d91000fafc789c2810467c5aaf659fbebbe69ba6ddefd57b9749e5b69ab1638c0133c9189393423def8ef82d7d5b2d0eba797d3fcb508b59bb06a3bb50f81e635f15ae33afd2813f15cde4d68f34bda92b9b812a0100735108c88c2a736e73c31048a6e524c1bf9753e245964bb93c5ba4b201469415ab88fb50d7bb8c859b2339058de4373375f98f2fa69c9c97ce7f79497dd535bfa88cb7907e0bc1e5430c6713a3f94694b031d35793f6cb1b4abae50f1582c510b089a112c2cdb3dd0b73308a0d8aad849345f3ef1d8955f1b97500ff11ebf22761d1fe87a613727f07e50fdb53af1d82d3e0f2b4591ed1e4db3733d05ac6507f28b14c4c6f9f8fa24181ccf7e0dff49a3914704ce18140f0a2c316aefe907bb7cb984c832a49ea1734c3f68e86274dfb2464bce351ad54f3b40be2d5afcf358fe090fe942673c5b7531d441ef7efcc780427783bcb047c59edf4d4db45df9b3d53010459758e2f978063d3094d597720e033cbce74a3cb97dbe848683855d1e3e4c7243870afe39d9505a4fe8322ff2fd1e094cef4b715dd4038518e26f0c955a69ef0e7303219eadf19caa5f9215ed285512d9afaf0be86944d15505318100c82d2b629166ff3eb9d372915f44db36dbd159238b6f924ed81af4e8024f0aaaab2494e23437f83e68521c0791b55a94ecf1718165d06a8e265d394f3a3f4852b46da677d20118e904040cf2efbc6e70324dbfcf3a970a9d0d3bf60dffe2dfc64a2f06470d84c87e3eaec63d484cc50cabb0a7188566411b6127ff25708f06857fe85ec4dd9c876c3938b12ff8b700d01a557f6c6a69b2d6b9c618219b72ae066e566b8e2578eb50ec075e9c94678aaba87a72a2ade2bfb76a110682ca44a7068b7c95441c92db8d0eab4e6ead3cbc7261b935aa1c7612d728270217f8ea98f342735cc236ae356c7fd8c7ae9b16e1cbeaf3475f52eae836c56536344d78e9742bdb67917b9cb4526dbd6e0bd4408d1d0a4463b9264a2400968fb9dbe05c06eab54697b13f927af88d72a5b6f55fc7255f091c3a1befcd30fb66d9f2543a1c5d39d4b7fefb2b2214d3bdb953e9013029d9dacae5116c509fe74943d7edde585e89b66e46d2e04452cc2bec18805b4dade4708a4ea487950c18731328fc7d0574a25446a31fd0606b71ecb2cd7a8a97e65a250f21bb649e81c0ba1929917055fff68b5e803717c2d8d737f637ed17e7e83f2196d7ec5650d6bce6b8d1e87e6904462e9c94823919289da1e25c7bd64055e92dfe1da5e586f57d2a957c7f489466268d340536c815bd5dfbab4da0bacc9086e52be316e7487e9cf347477e135ecf9a1e1759ded4dfecf040e51933fcd636588f7c1da6651a0b5aeaabeacf572f01c8803248b0880f3fd3c26dde042014eb67616d554bb57a0530f40e49378ac549f6d00607764fe4cbc70df03a9e0690edce2f75e87675de94ac46234a3da608142f51d844a6df1a24bfe2d60e949ca4dc10974b5476652df7a542cf3202de23f8796167abad1b708e13439746990cfdc903dd17fee3afde7a779a497b5a6a6fb2f1c60dda404ebfbcfd94579792fe9e9ee63f226aa68c7f53ca2d39f0d2029adbe27c2afa7bd737f8ca570521281b1e99476d5ac04f999ef8172a5e49998591084aa3c9a3a77aed438c5382c2a47e6f35d026285c4fac9877f3f9b97b0b29d08286e3ef40ff19f84ae58da923824f19607a9874ae9fc145489944e54ac336154ed8d2b0b72ae39ab76f47809b4210064a9a2ac650694ad38efafc3ef298536f254f73364a676aeb6f1211a6cca29aeb52b356c08a7dc5778a7038cef303687d92ff079f21dd3d60c03dc6fa6b48e3b73d440a50eb8f095fbdab7b8d2e579d4e89f90887b5e95a548f1167389cd8256d256b5d8a32d72e4a01d269d9660a3872b03421aa41e4fac01d033cec9029f7359b119d8c53b0707a8c45d612165af52d946452f5f03d7af4fb48c17714e60b6a48b07c4fe81d8648a3da1647c06bb20b1a92e74b9caca4c7c29eddb4ba6a669afb3ab5af07b1275d27fe66f08b18996918764f83a7505ac94d717791e363308a4d048968a61663bc4b458b815b3ff227162a334592c2d47cd113fc8aac832d5745fb39b07638ad4e8f7d6b1891ea79f17ff7a61b33928bde90e8d98d0faad04bf93d5f806d6b512cfa372b17cc92f813b16cd1d1360a2b61ae29ff952331674bac52c33627a272a93f6f25ff946ea97e4cc53656b4c89da03e703560c61bff237ac4aa694769f6dfff952fc47ec4e6036e118d74fa292d2db37c05d9b30611af0bf55b2d00ce31a5883406e6f5a74e3033bfd4b9e5cb25fd8f0cec0d2c199b7a4cb863a1c533c3724cf9f28d68e9c27cf5933bd54d8a57e587899b975e466c2d6daaa730e6af77b85b505dc2c31a63016ecc47acde46a9c7ebf82670b8bade6126fa40209c7e552f1b33cf90cb099900fb60ab2dba1c4da2c735ef9d271b59c1622e96a0628dbe25c11c403f5c0208838d0364482c9bb649561f5a722efabd14fa102cd57b0332c2e3250b48c0307b75630caec3e4019d3d0ec8480bb812020793d0df5eb7e4a3584c945eb5adbc35ccf611fb8236ceea02c3f3cffdbf88698541f3db9f9c70047a8f53e73570ee828764ce0c938e00db0231ac0791b01b619c79056a8271d3b39ba7d8a93025ab09b35eb8d73e05e8cdd3285ab5958786c39d6af32088b1864cfe956ac72ecfb7bf632defa9a371348a6fb550827432ac9bc4a4f3e241810e824cf4fda48d61d49bc4fd6cd70ac265edb6687d862507ed1a0f9befdeb5dd0b829b4407f62d73042c22af203774585b366b99990311b2febb2f5b384e1a69ec92ee167baf22f862ed002476bd752b86e1fcdc198d5825a64b92b8ffc57d9eb95823ea9ddb5b3dffdd01cfa290a843c79aedf81cdf90c19ec2b2bcf2402b132991ddd40ee63f64441938f8135cc9b9d0383a962ffcb5652177c1d90cdd02b75d55e381829f3a325af1997ddda653cef00d7aee774e79eb3dded2d9b5c2144ae4fd842567caaaeb4a27bd1dd374186efa7c3fc85630fb2841a00e4ac4b3c5256902b90f209043d73985507d0c1d0823df365c4587ab9c4960f653248a723afc925f62d0ca85f8115b3c96148bfc595922313868b596803b0df8a3ca293b172fa1fcefd846285e5463810300dcd966edb3b9dbef515d6e3a513b414f1c5066757934ea2eaf7f7335613989e925a56346281a74830b6ee80cf16a0f4ad04498b3b1b3451065917a97d52b701dd29d9e5f8000be480b89a8b98bac3b229b0b1bf8a54891c852f13164ea6eb9512d42c2838084b95548e599ac4b21176283aec74595ee3840a53ad7fbfe5aad77f445efd73ac19cfa51aace1611653ea17a7c4789e5fae66a754cce28c8ac3d4533a901dfd42ec9797374f9f81e20f77d911263af7008a941ff5dd08cc334d19e4ca61eca2e79f1dc33046c28b76073b0826691e78eeb5b0a0ac09962522291adadba4442ff92d693145b5996a1739ce18a7f2c15b5a8e178d2433bbacbe123f46a86494c29b7024bf2d8c94da6d7c0cafea213e257a18edc5bbcc35190f40ee36413c038c34f7a3b48f005ce103aa6a0c598fce72069f9f6f057a871145923b1c0bfd9c56f0eaa6b088d8d2cecb539887f19a6f4fbb5e718447828f4631561df33ca5bac93d037fa02885c331970d7094c44a504bdc42dba8fc12f05cc99448ed60a62308508a9264f40c47d9a145780177793104acb1da9d5452e903a9f3160dd77ba18e9e716aec66c62e5304ad76b6c5540c10f5687c5db04194248d2518f75491ffcf6376be0968e2fa564030d86dfa2b9a6d846a36662770a7c7d8ea300d5f79b1111e5631f6ddc62734ba7fef98788e97b7aaf8e2f9057d2f2b11a269c1d47399791313af8b1939dca939284cdd35badc58bda26e1d466ae5846068cef06244a912c8489f7b635842856a3bac5f3b5aa5e895618bf61fbf0c1915ca02010e5957cb88905d657a6815035e884a5f2895a1a70fc4fabc767a03a122036b6dad402216e3f5a9880d6c2e3807648b7aab6baacf8d2b41d565d5ed779ccf0c6b6d719ce12c7b03f5428f19b4be014a540d6733097518d4d2bbd527a73ecc894c4c5fba12faad1e833062fe27c05b4837a597d3e2ae8615107ab066b7e566d5599fe25c89a74bea9964e3641bacab83c363e37f98265aae425afcb31e155f88e18dee51fcd4f5d3bc605aa19f07736fa746a450d14edb19f345b4ac3e72d6f363929745a7e3f63e251702f181b81359e5e696ec3b9c437406d909ae2948f155dafd695dda7c165f35738391b38cf8752beb18f31a085ade99e907dad00507d246711078f8087ee966d03f5d7eb34d5e5393b7ffe38f1f0847fc041ed0b9620eec684a4b5639ad4d747e6bb6a34149f633300e210db48bb489cde5a4c89d9399c5ab32dab9807e68df42f8cab2ae03088fa8f4dc05fa584866c92f5f1d894d791cdbd5fdee963be3a4d54f7bba56e00f0af4fd1b226be1b5a194eb8af07bd9845dec18fa02f4c1b0df64de6dfafa85fdcfa14696ee140b58d38457657417166da9a4ef583e5ab75a5c0e6d195e3e67ebaf52939bff86448e59246a70fc220894f021e385e14cce10a0694cd05b29db0e4b3bbf50cc57912bc2b324f6bf06fb15cf50e8e3fd901aacb3fe4e1649a7c5622aa87abc919fa60a88a3b5b3e95231a6c777865782b1751817c893cb540ad0ce6274593d8a7b17591f1a6bd663372521eeac4d7fb77ff3d59db920f61e8531cdc4923143ff8e916b2a7475b4ca0c59f68c5b9d14f58d668499364d3455b007d17288c965dc7b292d59bf79eb13cab7cbd2c3ba2e4fb2f4aaa396fc21d2073fef1fefd632746cb77be53df4cec29085fc1f7a37625789bb2d8ca6a83a7d6b0b8c26f44c4d188e4303d7e66a50f620e0d51de5a7f6d2f084236e283c02909ff00e1ddafab7ff93826a39e36fcb3760cfb263c1a9ac748c8bfdb04fbd7ef4cc5adf8c767a65b72e62e23714745fb5dbe559109d3ace3d444f33c844c84c28fdc62bf6b26e67029aa56aa18b69c17d3e713e685d5e8327c279e4d7a5d9d86ea47b3d590a1257a014925db10f36af66db3a873bb915a51af6d222010169872413d15a141215d1e95cab14189af56e8229d8fc90bba488099245d6fc5dd3d5b3040a7a03eecc1e943980b827db0c046c418b41d545297d348ef36cd8b215be0412bc4675d8b16636f2868db9b50b043d63b3f4978981f53ea55ac91b6c57e73d3ec83bb79d2dfe5bd50950d8be5f35b36fcda3b28f8298c0d788205cec446d566ae3d3b020bab608b323717cc1d661e76d04ab8fb27eb16ae660ba43073a4846e1ae1244d64f409562cc46af3df106fa05526729231cbbafd9eec562863ca09acd60e8ef4924fbb23af5803ae304ce45072f5c44657bfe177bccb3b1a4c694d2c0fc317f96b8d23799b575a11b2ff6fae5d32e59cfad8013dc95c889936143302c1c5865ee37fc0b5f87168280171a823409999d0d7cb826cf6dd9fcf70f1799eb46cec71826b2347293a0a35f82fba72f88b59da07cc6c46b9264e16988aab7b7dbf419ed5d47803b0e5ae93db33e5f34eb47e1597a29078b01789cf53fc7d0b3881f9fdb5f2bc928a4ee174e9f3fd8442ee22ae22a29b84e2d18af9c444ba4e053c21c63f0b91e28f63cdcfeef6aca87bf09f9353571d297c01851d3e7ff41a93307859c06f0633226a7ca6cd5c40dc61fbe13228896ad8d7399ef0239abf158374e68ad0a601dbe73381eeaa8870ef012031bce00818c1cf098390750e62c9e3f5a8a5664ff384ae831d77f5f04389560503d2f61f32773c64f81b06782300f472c0e6859a793d613d07865cc2e2f6e2409779196ab1c8571459d44c886daa33acf49483c63e168d95b0e8bb37f98d3ae5cfe1350d5d9886b5120fb8111613762967970eae5668a4bd84f13ac163043f532c92be5c8c97852fab381f7a4ed0a599038ee9ac0a3c798dfb6d5d2452ee092066d1e8c50f4951ce59c7738ac4f409630dae4fc34a631088114db4cf14df058cef289e0ed46e05c46f5f22b40c86385717f06a6bc42de0532c1e3996c4d5d6f589bdbb9d904a29787f5fa8a7768dc2ef1071fd19fd2663f24d0e0b6bf6d3605b9fad851b89de6a692b70cabcb9a7e70cacd8c6c356e67d092c779107e472c91ae4317bb3b4ffa2e753d99185c37fcc52c71bf78a6d0017fd83f8945994716a21d1685a72e83f2224e2885011bf26ea1f4d6bdb64e345b0861739db9d9496d3f06a6e17ea1d02559b778a96f2c09d153c6e3a1a614801c7dc73979f7e611d1d9949a3dc533e4ae2f1de6515302d983856d6b17d6520ff2adb32094e309d782cb309783d1b358ec40b6d6be5dff62cb3e8342e17e2c370fa8485597be12b2e768aafaeed38eb602280dc6a5abaae24f809c5e5675676da64136d9e1f70c800ba1ea028d2ccb6558554ef4ae3fbe449b6b8377de66259e73b6bf0e06d0aab71b2ec62dc311875b18f3cb8cf64c9ab2a1fb3caa776816cf315449ff4bcb92c2e955d661ca8635b1b2d9690e8d51e76b344ddd3433d5fea721373fe02cf4f8a66c3a79242ba213215894e3a4f90071d78c776c80b87caad989166742d7ed2d313eba5be44724909883751e4eb1fe830bc5f45d54be21a7a53321ef07aed60d581ce12e2ad15d9fae6cbbf91aa368580d5f9a5c3d954860faeae9cbb5c9fa784a6ae86a53526635d193c44e97af3153aebf216de79c5576b0006437f57256406facc74155a4a2606a3c5413692a4c283dad40a969cbfb6a77cbef6222a7e82fa40e5523e8789bbe12ede6cf825ca42738d45115482afbfce82377ae959bd52034def57466d0234524f216dcd902eb3ecd468d523e582fcd0be699f6ec35593d4f0cdf5e9b85cf99a5d910136dcc2f7a3bf5d02fbc05ee264775eacbfa8f89e04bb0e5d0b8c2b97193f617f28ce04b0d193bf95a5b78b5056b343aa52978e9bb9984df20ae879bb302e01a53c2561c301853c9b47b092bb2f80df65e11f3ca2c1c6693e42227325673f2573565b98c5724dd6309a2a8699049ee9299b5b681eb93d839e96bcaf534f1cb1146f18c7195985e532f2b55a0f98a460ac963ac657edb808d6aeaec1f554dcf531b6ac33229dfbe22cf63a5003bc78c66077073a51afda86beb67dc0155cfa3c652ad46468b01c885551b44fd135db85ffc1fdfe8dc93bf04522b3eabff3bb4940665727a7d8fad11d2566dd84d58c8076ceb75c3837dd69c29b689e03ef3b15e4a699710eaca8185c4de748246173d873edaf1f6159d23dc2b538a61e5340d40a2c37ba79b35bda21a013cc8888a9be91230c0851e5281af73e1e07f0b517ee3a533d5f51775bb95e006c1d1cb24edf9aec587956853efb8fb917d05f8a581a74108d35427003dee658b90f0f3423e2fde689a2165cf0d504a9f2b6bcfd2d3d2475d941ad330e727d9184abeee6c20a9915b857da40fa2126d61055f8373f9d84351afc4e424c0c29b2c258b8451293c539387a70d601805a682f99abc0fb9aec193abf22f4eb5ebd00b6a1c79c8728e5daa126bbda5fe426c834d0bb9a39f2e7bc0853031b316b5e89ef8ba95771744eb8b4d53b7e4853fa22e1745584f798d1d582d91cba717df29e1c09176f0b93b8fc5926dd2ee04962319c44cd044dbde98cb08192f3551307c4f475c353effdaa70ff0db8b99756a0cca92acc4059ca92bfb6ff8317f3e5ef52727654be3106b3f0fa937a923f5595cb315dbb95a578c9cbe2f96a634c085ea398ed444391a85eb8bb5c3d4e647fbd6b4b6f11e36ecf15388086a787f9a33887cbd4e1ffdcf8e7d543e6f9d0c52ef437d2fb367e1fcd547f92ea799f8f6fc32a08ae26d94e34696d522a884c19f8170c010e2f0677099eb47cbb60bdbdda73255de479c408f616ef575fd034cdcd894e23ee7a830f2e695b07b828c10c2e36910386eeb0611356620fc91b4805bb7fe6b6ec8b61c72a70ec2ce8eb2b9f8911cad295b018d2a762e92b25e024893cb2352ae6e994ea206373dc9e58182379e0669d658268b9e049ccf4745154d9e907de5dd3c79f27b87fd6a65d1c36b1b67455e797489beb7c778f59cf2d978babed0fd2ef6a5fdb62810400f66abb0a5b6d5cce04c2677c39ae3f84891548fc94bc2d0a9f47ae05499fe6127dd9017b84aa83bed3026f692491136164ca720d0fbdbb8aaaf2393177dbe2581bfcd4b9ded8eb7148ed6d2f89c80b285a076027b8162156092fa3a06c1c3ebe6df03d67f1e843ce0ae8e855177f347468195b1982e9f554f227ce3edff1ed2942559e7f0490fa46ec91e09f800bf85c248234f97347f34cf75086bdaf65391ba2672736392c749f05da23ccee44476927b85671175aa238afaecd0a209da7cfd923504af2837f52be5cf50ba9ca79e055be7543ee273e7028c70875ad7780cf66da2499234a17ef9ce1f1523ce0b7f9d4729da7d6f456f09dadb49dedef207b21943a72c4d6c12c8d0646a78b6f89192f36986d0916908461fb83b4d33b06a303107e8808133ed1a84af79a8a7a9494d65938c5d505423b727bec38a405b59e7328ed0f6f0bfe80355978faa9462d2eec995fdeee4cd052aa72faabd9fa10f3dfb2c7fdcf36c38aec042a5f7ad1971c7173abf72eca0eec2df947fd0fb8a80110f3868e0d3b841d8e3c8b10b0e082edf80b62c7a27ae6178981836f305188032b65c44981404945359c6d7e775f31a71b56e09bdca878d9863fc24197a1bb1e5e51276fa4bc1a085683f64921943ed6af68951945ff6fd4035df6279337f1e63fb7142e48a97fbe683295d937c31d4eef7487f7e340919e449d0800d51675b5846f2b8955539988617360f795ead6bd393450f9d5d9436cd5c6835166ae5416663e54c322cf2b184a7b8a354b7bb6c244c066938453e7c952e637496d22deca066aff242a32cc7983ec24839c46c449f9d8fd437cc7e0aac5b73c4d4ef80500fd60a0a320c73598a2cccdec123b60a3822ddd0aafa3171d6824a62dc9c3afc3174e17dd3adacf0b8148c708a02c46a94836cf24340a0d4abbb0fe5fc9eb9c674da8349d78c9936944ccbf2b55793ee8f3d446cd6441ea6165dbea5492a11f9c0e3490257c4cedd248a5beb6631e7c7b6302089e048a655d71327201876b653eae6e51b506eb56d81e282a74811eba0ce04aea5fe150dec01498de185619c8f42c1f45c211c4efb5818799b065914374b5b03b0717fb40f7cd81a5eef276f7406b194600edfba4c6c3ad05dd3c61483dad1b7cba172dd8f8417a4337a25e261ecff0c11c0e0d1e31292e272e301f0f8d7d44c7d9797fb34282b76f58cab41369c7dd0bec38b3af2b3e19fc5f03686fbd741cdaa8181ac3eccae299a50e5bad034ca64d60744d7474df3533cb6c425f720daced2316f34aaa7aab30936cc0b0ac270f83e60af7de2f376405176f8f5b62c853f169f9ba21646da2e17712c8de2cdd1b11a068e3fe4483124f877da4c712a63a814631effdd0743176777b9c8d0cddd00f91623c65b087a0963800a42b77dafd0253b5c24c59f1af25f85320d90ae9f7d8a0e2d8278800af66960ac10ec3b73ea2e8b07267695fd9b481997b28a52971d8d556eb27a111c567267641a29af07b32cd8b067d3c475b724766b00000ce4c9f6b1c582edb1d63cb4ea639d7bd768318f4ce7c413ddd0f5b4230de851253fa05bced6c57f690fd4a4c2856f39e657fcae36e96c82e246343e6c17057b5ac6682cbe420b1ed856951ecd7a50c2c34d32f787e453afad70a75fe0d795895c960ab1747014c7ae5726996c06a563893d8dda55c75bd7670e8f4dd1d25d204f35099248e5e9f13285f263044bad3c05273c4d5ba0964d111e2c3bd757a46a4bcd545b762d1343bd19dc5f7d2688f13b796c48778f44b9fe41f39b4d26c1a4de36f7a19e54b3bc3d659e25c69e95b3e244adcf2c466f0e57773529d221e95689d3675d46ee8b8bb36bbbdf4f5e0aa6e33e960b1e184541961cb946ca162da43e38909453ae2811b5d30e608d40c6eddd925c50989b95d60b48fd75db8edc9be9a7cbb59491b5d1ed24602279eebfcba4853f5209da119e5a6e210570b5be4fe45d6bd03796417a97568efe118b97f9bee7f65c82103215fb58ab0b394cd38d80a8896d5204704a07ae2e46d66f2220e5578d1e3a00e32bb96ca514a9af8d8083d335f55b46daca21edb8ea4df901a8875550790acf5c663440cf8a77ebbb24a5e24264b051a4dfdfd09a127e6397544923c0dbf277c5a15f82da181edf8b78121c4396119f05f5cfdfcc3478b6e6118079a9d092cb80130231eeb45c726083eb627a8e21b3c8399288c26bcbc23a1eb608d3d4b2005075391feafbb7d968dac561d1f9e836039a5f90cf310c2e9c402bcce802bad4a2b2f3cb24d6f3ea2b122885139b53a088727dd15c22548f6d0d522c8fd13a3cbcd574889d1c9d8179a111c60172b36ad9c3cc4986acc29bbe524ff1c7f264a38c65d3eedc1a84f37316704404353adf90013d79fcdcf46228a68005135f8dbe63df7da8fd563a95a322abb18424816d937955104386929b44b6f107f5b9b0eadb79b08497fe974e46ef198b3b5605947fb4afeea617c4ff1172e6aecd88ea1da1e801df86b4363f4d5f8824fa55a558231f77718564b6b9d75055fa2728ff67f56ad655ca40f772ba60b882bb602651f8c2ef11bf761be059056936ee06d4021509edd0f0d07ba65033de05068eaf3be8d1ed536f48a70271af00ce2b72f3f2db86d3d7d10cab1bc79622e4e7f24d27dc4f5b0444ea94c6562f2eed46270a384f3c6e388a40327ef32621741829702cd832b6c70d2d1e933996b8f3dbf56c8debb674fdf641fed79bc86b4708ac6cb5a5e890f8635e074ee9cf9ab7a4f58ebadbc0f1b42885c395e2ed0615ea2cfa564e4f0fd675091428af89829715388b73782dc5068908789b7ea5e973fe55b438e9d36b0263f68d79a20a525d9a68a1c9b394249bac5ed76831dc8265ac749c0d7d997351b5d3c1ae0c2aa2f09c81b22b52f914510b21e14d2df431b03029969cf83f09e7d50e8044aed03bd9bf97c4f61230f2c4188458af5ddaca09ee89e9d88321a1a597619fd4f6799d91f6bf81962ac0ed26779a445bc30d1712a81fc18aaba2c6ec3084783282fa7aa96540b835b0df8a613e126fed56a1541d33bfb087298096a73e4467976c6939b8348fdafee0201e9091dce943231db1ce628c297b4bdb41abaafa4edf4f6a741fbc1d7dec5a5f57c5fc3c84038a47d4824161043f3a248d658ffb11d5fb3d7a9db891dd29b17eb4215533582ce44b548ada6bfdc3a3e755db72241b8f3bf0cdaf6ed95603fc95083a151041bef0673f342114358a89f305f7721ed8db39b1a5c5e77aeb664636fbcd6f485c6dc34595a8be22afa9353d20273d4339be56e96c42b399c3f4fa9389ce924c8e66256cca92d0b0d46a9870bb4ceb5433fa7fde4150d571a69fb27d81b4d437fd239abb489ce29ff06e9017fff91f6240b15ed27582cd7184dd06350727c6af94517ac2ebb6ac38b268dfd5aa00fc40dc0eec581048dfb0ee67497012f2612c84c1d6ce83376016b064cd4c8f50c70409053f474d2e063a9b8c4306ef4cd530bd70150955c08db3e1a1bcd7112667b3cc18a50219f33f87e50d5f07bc709ab1799b7a8e9bfbedc421fa2f79ac88fb48ad3e5828986716944993da51ee7edf01b2e10f77ff25970db0da05fe6d193ec93bc7637ea0229321eba9eb00cbb77327a77b9bee2469697fb3cce99275317cb7865b8ade3d0307210f55f070f100b5615b7e990c1d06bc49b28d3ec6795046cfa3093f542b834fbea12e12f5cdbb3705163069e3f44f95ef18b8023745db2e412c6103f75fd9832dae17fdc425ec228be26c20532576f79db7fb71ff342a3208bd9fbc227fec3333e9788b1b8fae7ad80f1bb788201f0cc241e66440bae7a12578edd18f6515a35927138af9f7fed24eafaf7cb6cad2384ed03bb110dbbeadb282a92575a0b1a8e8e35ff5811dcf242d1bc3097ffc3466345fdf0c7ab159e3bf84c63b4ef2852001bb81526f146dbf246cb51fc00310045b4e4b351be9af3fec2a371f18d3f014b0f1c405b4c53990cb026328e71e2f94b9e2995d7d46a5ab76a76ad8d7ecec0c69eaafc8f16aaf3ac201e947bd0409c658c21d25291df8fd4d30d986059ee3271ab2a3a11613b8baf9cf7932a55b3a6da97b8a980dd87fdc4c7ca06f07794ff1f7770cff1724548b819841389942d0dc04feacffd973bb16604b309340c52e16320a22d876d5908bacca5a39d4c86271301db4798102b053cfdc069c56fc4566fd6138f5f1b1ce1b2899d273f472ff536d1109aa3d29172d41747d383b4d32cbc218a0c18d17582dd67cbd5d93b9e3bff4f7b7f70ba2dd115ddea431351b4c1fceeb1649e3bc473ed8ae5bfdc1890755c04e6821cfbbf1891ee631d19d7c590e8b3dc8427a27bf7b3eae4d7195c47741257ae527f5d561b506dd796847bb106a3994dd4bcc346d843bbfd89af3bd804b184437d1bfe6ca205a187f0a8ad76a9c051a9fb37192648592693b1c1cbea806e8594a4614f8164982710dcbc302c6219d1e247be24542ab32f6111dfe827abdd9c6288d04a88b687bc4e614b222d834aa91936a2824a42198f6ca8647bb1fda5c82c2e11ba82867f073ffe8695371161d8af45dd95cef1a98bb2bab3e8dcbd777e5452fd53d560848e3ca18dd620e9bccc28e2bb3eb669ab2dee486e311f506a929082c89c1e3af6ffd97cdca543be8c219dbc53c2692fae1b836128ad361aeb4f0fdd98f225658a1aefdeeb1ddc58b7e0f047be233f36925eaa5511b550053c1bdc36d47618694bb67573adceabf568b4bf6731d900dd8b0d5bf186f8a9230b27936de301590b6039d511de11852a6191801d5588abc0cc223efb0bc5b282de7f09b9e58e50f76534a86a50f3a5e04a6e3062271c7d9e3973c60d6f4266ad168525b45d011b2afc46a291707d6f14956be3dc8dcbd33e7edd2f5bcf33ab30f0eb2452114ea1018e77fc5fc65a988ad6ce07fefc638d4c0ab564d0fde5cfce3588a6effd556e3fb6c0e08c2df1f1f6714232cf5a7d8963f2d80d319a84496169e3594a3cc4d7b6705209e2b39db14d4d685963231900fe8a4cbe893ba21b4ab8b7418b5d55e944fcbc2203ea435df443ba708f1b329c14ba0f615b9e411c6c948ea8822ee2b72954a851166b4b78bde6e5152ad85bda3a0a28450a44faaa32039a0b73d43e84e009dfee6f2ab9f123a55ece90c463cfb521f3d4f1ff374861be5826312e5e019868a472937346179ab0d069d2655ea7d8bdbabee57206dd467a7df70b970f0857de97d9edfa922c55accd2d0003bdfcae3e29ea52d1383ba9a87edb1aa5f0a6893b4408753073ebd98e1a552673b8630c3c8a809091f1d97ff18423f4f87b4659d6d36eb2f1fc27d19ed7ab87ad31ec31de5a2636ff89334d94b5c6056ba86f3a36d87f52f49f2d9fe856f519b683cc38f982554c4d0b1d39fde1b048f6bb0a3bdb3626114dcc58edc1346a6910247540f691288eb753806068441c947a871c4959d77f65bcf64452030e5891685fb82aef50b7dbc6b5d59cee1f99677bea14eb8b57c15dfa7af6587aad6b91c7fe79b2755f7557a0149d0bb434dc530692824fd6ca954a062b7b88c36c80e7e50efd12d7082bfc1b9972e273dafd5206c849eac253b2c646945dbf7da0c3dee18ca34bc4d9ae49ef7d812037b71bb1e1d486d3770f4b2e1bc5280f8771925559d84f7dc14f372bdc712e158b74cb4097997c49bba996a9e688d83771b08b4f212fe06a3bc84afe4c83d7d8816ddb07a07addbd1fc731cc704d8cb87661854ee8969f04f00089b31abc5d5d435234d0aff57bbd91dcfc59782cbfd20fa2bed3383cf17e0327e26c1790b3e07da03592dd2755c44e67e9424c030fe8dbc4875c0178a3e77faaa77bce6e498337cc4f0e98a7f16525656f39568854439cc1711c0f9cb5967d1923df91d1853e7e99425e721fd5d001faea629767dfdc01f68c41f8214b9cb670115a036e42d4dcd0136994b0ed3a0cee6f88a437ffd7c26acb274efbe7cf2a507fd8a3879d90f3980568c0ce29fd3faddf6537a2683e0377aef5af5745c0e1d76db4dce7a5f0543e90910c44027ff5279bcc954bfc961430cdfadbdb6bdea2867111b29825ded4ff809c54201039f0f23d12599ce6bd1fdbf263c87571a9ce2b41ff64f9c0fccf1cebfcff0935204a44894fe6c89954bbc972dd91f00b766497b5be60fc92aac216f4cb1fd765961f1d9b9a09f5b072d48b82cee91aba955b4a1b21b077e87d12c42210912c26d3a7bccd66b5263e8b53aeecfec7f28303cb44c8037d1239ca6e49428c101a036964698fd83f6f26844da266645d29b561b0b68712b2f95c7ee7f157e668bcc3a871ebd6a6a6f11355106f3a1c28efced254324796021a46b072359b7b0471abfe8f13a414fcaf8b6977a0aea6cea96d2dcbc72d5174f96d7413458bc798aeef468df15572e6298291495c63957e98882fdc08a2338c2a202a078ee2fa81cc2783a4ed7110979137bc1d5a491f72947043d406784f8f276167e745c68ce64a42007230d53cf463eff4082c8f7eccb1a1d097048d11b5bb2c9f2b2c5f77fcab51afeb9c874c93e26f2d532770855bc9dd7c96031e0ad4c6b8af065b2c49fdeab5b17d01e5695d488ca5e919b1613143f5a5dff68d87494a97871488fb22819505de6eea777085a575c363e42dcaa9ec30dc8b0f8968884e93038cb2bda32cb002d963f002889a739cb26a21fbb9012c31353dc3123c25f9b66ef5b3c52373a2fcae8e24bc0b0200b4dd86f088d11fe280d93452633f5c2a54eb205d987d4b7e1d4530dadf0484f59bc65ba760434eeb330110d2dc02595ded947c1fe2695ef308c942fcf1cf6bee6fa00d2089d35df78b1908b63ab2b3c94bf9a8a0b937b92ca1c55655ec820fc2a24d18a001c85985d752a012672764f075838b845ef53fe713fbd3c8c13f098cf8f742dc4a05c30a01bb93e99f2e2c6eb46e02054b70e7d792708ab8c4767e334a4d0135e455d954e534f052340d5afeaf56e52c3e7a53e4dc179ab1973f887ea7819e0fd4ecea6cd81b2d4f08708c720dfac7c84b64e3eea404d97f0161eb439493b4f72ee611653b913476e05c7f19da7ccb969dbf9d9610077be51e2cd6c9cb60fe3b490a1086cff80e5d2067c75e709a20dac6864015aae532a827fd6b441db08045cbf632b78e3c8959101d2f87590dbcb68333b888ccc9ebc4c707acf8a2c3cc52a91edc32a65ca5fe7b6cecd3520c9141dcc1255467e4a0e7cfad9638e2df7287eaaa1080a2b6b4d53689fc0065379bf300736dde9a6128b6324fe8598f1479fdfcd5ef4f408ae95191c07ab3fa2375768158d9b6c58e54757c196b2a76e237b78fcefa397d6a318bef1a0582661b5c9732c1ff7d7917afb308ebd9aed9f134696263038f795306f908668dc7a8767025fbff05bc84db551cb5d50355178972d2b6cc329ece9de761c6a3a54ea4be3f62147401ff1e0a78b8b55b4ebeda49e404eed87f5a1dc3b4fb6a23e3b9c8e4397119b632697a855ffd33914e0901fcbd6b92a200b15f67433f4416fd8a713e1ac6cd9c72d15098c938dca6b3b3c9162464af638f19da232783309ad0a998de7f0d1dfd5fb57bc6cc5a62ff229a984190182f36ca560e9e83a111e14b0f177918bc553bdc16070831e8f75b5acc168224b09a8e757da7218fdf1ce822aa6c114bc2f3b686ac4b58043be1979af22632a03281c2ac55adf397b7a67840f9e478aa0dc24199e2e3ef4ec2ba729e29ff22d5d4d0b939a836d4d8c181512e6381f5af4fce075cfdc6aa02cd36277adbf614f058ba48c84cff048bc7adbdbf0c3b0f622a3c900bd1e2b415b39086a3a3c0919db17db81bf0718dea012990b6804a2f03b499b4270da2ae0ebe095ffec8662d558cc46fd46cc03539d981bb431b490ad2c4add26ea0b51102bd22928e23eeae6c5a8949308122d67e788445e1c009d4b2367da4c5a3d8eaaa11ceeb0f9bbd249864d877bd29f9b7e249f3d66db07c47b2544294ff513fbe56b4f15f0b48886a9348187f50f916c8de361bd1a89600a5db5e8c4433cc246e55601e743bbe4fab3b7aec4e4c7d5a87d8d965bd03e9176a2d540b5f6cd937513004adcb62540d2942e62e53bbcb16b0923c9f55a6799db8ddd9b47a1a864efe98248cc5acbc54ec263d0471ffb3a594344c8239fcc677389542fd71f1928def97a9b282da8cc1486704ed2deacdf64478b27bc2d0560496a7c9325e87262dc5241e949962063bdeb5ed553296cdce7cd38bd681bd3dd74a54843434212aef3a0555bccd553cf38ffe285741b00c8079b8e244b8639aa2302402008861839eb9bd41efcede7040185b71e360ce2086d04f93db17b8a8974922ded1b5fc844e6a1b6e451e0b92f1c96c58704399359490756aad92f73ad21fd94d38e5966a552975260ff4ca52defc9014a56b3684aaea83d53c8b9183ffb6195dd0139084527664ace8bc72611e0d7d416dd311ebf339a2ce22ac5abdd3163457d5fdccba372b697688072f4a2c8daf83c46a73270ee5cd7464dec7516711d9a0524b0844f39bae8f224b4c8da38a0ef338bc0cb67b65cb9341eac47efc9189a2f36f10c70a426de2f4c16836889ad9915d2ac9f86bdbd28a143245a616d7cf4964f420e3f80b4046930a3732fbee4001fded02eff75d6d4493f152cd8360f143bf555438a0ac295ed5f511dc538f31c35104869dac88f9a2ca3ce9db506cf6c0100afea3211f0238cb950fcf53af4325bf754b6729c25070b56ff93cdec281230b5b960ad60d4591c794ae53d39f1743d712336f8aa01b7efe799b5e9221c9bf66ebf9d84821bdaf553e66251fd29b1a85a7e00efa8aafba3f63e6d44d7d1f8e1d476a2d6cbb32175a5260f96984e340fd05a15aebb01d935a2df3210c7732b66a432b4d4bbdb057cab6d46a826157d07fe5aaee13f487779118a46f1a84bb05e09f1a743aa2d09f1d23dbad45fd502cb81aaa208e0a79c860692718067751b8386cd61fdc5024c3b14075a9efb131cb859d698f7fa369e37838717c92bae8ef86d971c7063c74dc85f93c03de32705d26582d4555ab37daa7daddccd370aa6bee98e031d9b1a0e891ae4d41a3463223e781e881d396d96c5a4e26b01919b0240948a261a38d35d74aa929555126adcc6f5fcb41f9a1c51ac342e72ce7a53d2c8a007d791f6435901130616404ba1e058066dce80ee9ad34cdb62ba0fc1a8930794e60a3f125916aea10f8de1c7de228755ba86350cbb86d4ac896f96d2ccda918cd677f524698a7baac0f474cfc5b8086334678d74b999362807f1a6ab790d5e1bb1b81b55d38c6f548b23e719798a37639f16a4eafe5fab4bac4f7a90a2e14bac48f428d4c4a4117600775db28cdf90afb07bf1592f832cb9257fbd9791ea977e6b2b2badbf01ef81bc20aca9610236c7783bb90cbf88f9ea7a8310053ca7662d1d8a45e3bc971b8355e6df98558f6fdfdc6d9932881716788f668873d75342c848d62de003d9a8295cadb4f36f88250482c400b71abfc968d88d2ed0f937957b6d5caa3211f132e7c17c1e2b77f167c8cbcab267c0bf1e9bb71c7d24ce492fc907891c9d26b5531b36b5c49f978be686d057466b6f7763372f6ce63028c6c650b9b3f4735eb82f504c2280640643783265c5a90725a5ac34df444fd780cc7128717f384c5c12e9867d2ee2d0490499b6a20199b862a950ed7e74905525252dc5169900faf4702b9456f4abe311fae5e7861a2b96c7476ac3448cf54f23175873798cfbde67fea38c651f5275caa79f049375460b003359d5ed11bfabbb9a395f86ddeb88037f8041b88f823a51f02ec5897f26dc85d940c25731a3396644762d079224a04fcaf9ccfd7a9a9679db9afcae42da7cea7e877ede084930b0fb9b236bde1126166dc62e9d562c3e48d813c92afcc5b7a5aa5838f0fece767ff09ae05cb6b003e6d2a5f8522e7ea9104d6b54727b20a4a25a224c892b42bf844846a5269811e8e8ced60a282b0c57de819724d8d077ec04351a498ac42b71e6add72c2708da7626bda060d3f604ded35431fca5922865e029b5051aa801646e3f6915d9d7c6c68ebecaecd857c8d069b3f94a62cae7f2dc9230c48594b069c4617cd82e82213066c32286f7da706fb7864f2e236e3dc100e40867515919a97f987553459d4e6bec30fae3b79e452a2f9cbb01112404c91a6e82b5695190c818dd1ce0ae9f41ff58f0a5aa2e5e47f47817667f7af0eda6878824b0aac25fe806e11dfdd8fd0600879eff8743a8a86554259e701c59fac14ab53af52b53426d914787864ef2381dea64e35f0fc94faeacfa56ee7e7a04c2b5f7523505a1ea9a202b4f7ed8198a00a7bdb288234b565848a6a3318a86365618a3c0b77c2d7afe1631ce64bf6a42fce805d217cc1e8ddabede40f4b425904b325c1befd1380da97d063f21aff4b909cf4a2d3e9f65f5fa9dc10b616e3e65f361e5be1d16e02e3a0d5f427aff44dc178a2eaeb4eb984501e96b8694a5b8ddd9c892480ac5ca5dba5d775c1e6341aea9861cc838d84167b95b6f57ad90ed74cce0f01cc40ffa914547aa753c3fc9c4c4bb44ee1846c2fc451b6bc86c159679303b3de2770bd8b185bde058ac2d74c72f79ff1cd1c8bdd4c2be794767753490a964ce045436791afdaf8236a76b52f6004f4c4db7448a78b7d9b9e7c41f283010276d327d041f1c21b916231037aaa17ce81702b806b0a05972f1de91cad426b8a3eedf12676e7d44a6ed984c5c4baf43f4d5c12267e9bd9f0d30059f358810070014c1437f79f451220eefac763819d273c59486494270233b5e5bf23814e35a9ed15472efafaf560d14eb64a5136c4d0ce7869e0bf011d4cadc82c7006566401da558697c05cedc4ade2d14b7f24ba332462a4eb53a297916b1402099b5b5f9968e4fa0f022e51511b5bfde0dfdebf74cb714ae7b58f804093f00e52a67be54db96009073417ed5020a8e15be7b966423965d28e96cf0b6cd3c4f958ce9c7d19d89f716c764955b9e12e86ea9d229063a6123a53cf5370e515f5f880896e619d2af02ee1c051aefca4402cf982f955bee4706c7d1cb298cfd3987bd7ad788c5b0e3b52c76dd72488f2c74a0969a61f471e2ad37a494d4465eefc03c287deb721e4de7ddbe7ff7cb3a29babc0b79266ffc533f0440d57b4b919b2a07f29fa32c6f7233a4178473948b5fb5e4608312c8ce08eb09b13214d1b3d48597c5131920c00e786effa4a94d9b9528c926a366f7e11a60724350ca3ab238b9f26cf907a4614c5a3c4fdeccde9cbc3da3062ef8df5a29554027927bea25c34e4820a7fd8e0b97bef239b106e5b4a3fd5cfd77b28d1d1802ec48973ae32a91f4227bc8dfddd5365240c203f3fd76aedb49a3c511c419bea50ff195ce3b57ccfd28b74865aa2f4a039258ae23e2d1702a6f823da6dc2a02ff23fde632613eb1de6c4e549a714e82841793ae9b4f74c1a25cbd9599f5a447d44cc3f313292a09b24d67815eaacf3aced7312254c4f42adf9488e12170e9546f35f36a55b4f3f9b6e7abf8e4ab86229ad5e0a1bb0846a3e28538100dbf9c0f1bfd05198f2126c2ce146f4a34e4b0b0e9436a4f308a42188e11e6653e855783ab330f018a0fd5d2fffc4076aa5e0a0f7dca4aef878558af2ca6eb732a04eda8f3e5005bc5efd0bbf336558d759a7cf65ddad9b18a01a18de8e6bcdd5bdaa04d4cbab4ecf428238a78785c4219b16139551278e47499eb9c51e4da84ee11e64a141d4ccb7d09fc8b01d7b3abeecc955b73b3759f77178909dde4e68c157f81f82c7db9f2f9897ce823980b779eefd0a58b25a3a938dfde2362bd1507956e296478988449b9df051d1992e7cfab080397b48bbd364e280de89eb985fa49a4fc6263d81b973fdff83c411c9b44e65bac73c96829dac60832adb11439ed9da96a0258be44abc02e0fc49d8fa207f9376cf8ea53ad03a24f13659e01b707136c67374a607d75a948b8e653577c05dffc3fe2f8bc1ea0552f3964cd43956bc22b04ec2df56013174e248f2e91636966722c141cc1320448b8c3e18c576c5bd2b9c7b47a7161ed5da9e6a01a3e8e55b46e4b9f400fe51b5923853d892af86001076088af3368d182805ab0ab02d1c5bfcc77d794dc26f557ec2aa25a6467b4210287cdcdc02f9bc0f732169777cacdd4b211ec0be9c72087bc7fc8f53e1bc697c634f4d89068620744e12a370af8a511299531db93cb102225107838ef59f2de1a79985bebfa56778dba800b6d5645eafffc6501009c8fd429bce0629db514023f96dee1d3f76be9a1072733986f97a7062cbfbb48cda73f9bff0f98ed29e7260b37517d060bb26777685bf659f8b8e591d67f0cc662d87641fb93611832d7fe5dda0b430480cab07e35923f775f071152a6243b61c83d82d688d52b93594155dbcd64cc2f2c2cb5c5c8691b5cf039f07992f854f9f16be1d0e3d1c44809d289fa519bb6f9cf0b1fead8ef3c5c6b2d1a35a604a5d7521202feb0a3e16193200a65c147221dc199c6db4f9b798335857a6eafdc42349f26caec6eba9a9dbf77dea4d56c35ecefee3b6f47804cf681c7d0e68fbf83ca893f122899b6ec395a252023631297ac99733fc0158f52e216242f9b2adf817deffc264108a0366c87987c762643f7033ac24a649322cffe8fa77f7b27699c0c23d747aa5893bc69dd0cb5017d2084748d09056d05bd279d4037fc700717f7d74a732a6162122700877e20e2228075e9b6b8d03a559666491754b5b442a1fab402a778690bdb03bef029262e50318353dad03d1f8089b9fa244d0e766b6ed0d2b3a94c17ff8640abc2e7c683cf976a55be8f93cf6802f3d3a93c3ed18e3ce7ae548eb829143eb291284c10e7e7c46c0047474ca774c80974c9e4d2154e507219830440b56b7491d5aa8eabf03397cda0db1d97ba9ee596d64843285d6aeaea492a8c5cde9204fadd19e6504cc0b3589a0924d6b8ea7458c4f77df570bc61890327bbfb6331f56b0d4e324c870b5cd854f6a3219f61d6b1d3a79bc001d344cdc99bf0fbf90ee14b4abe83022fc13da03872cfc23a02a227f9d257642db153fb24e7d5b273794760172f4f673f4a5a04bbbef9b77b4fb6e4a75341925567f79af66d5968be5502573cfce184e18c0dde7a595a349766ea3682d03e4c46801af4ddab63daab65be3ffebef9f879750c0a6d5eee1efac63e109abd856bac7cea98ac3b63b98c8c990ee85238740723ede918536e900b36e38841150b1bdcd617dbeac9f0299b1cfa1b237b8b3dd1d06be2e46c765abf680b576b2a775ae7489f526c233842357749a25faa85ad65d728dd72a4ef7df85c575c9b36b07c044f0459f4d18793e7897b434daf13b1e85e0dc60c2880b0bb8c8719678521aa44202a8feaf8649a47f23384a347f14cb29cdd1021ff49de31655034865fff5e9a97a5581f64c54d9ed2e84abadbbc133db6c02fcff086a67aab63f87217eae82fc7775aa0fa9d9462a19a64b2e490b064a3af1d84ffa0da4eb931d19ad97cec0d309e009d07c8cc837d97002ebf9760f4a9cdd6a60b3f0073bb77d41e2287e211d8cafa5a0b5c4584d1473499ec2474854ca6db546f5dd4f95b6b3424fae99534b8e3bb3a0605c5634f317602057dd3adc62db32ea5f2c4568237ceb45e54a5203d9efacb6f7b045a8f342483db9b777e702a66344cd0734013899e2f73640519d4a831f24b4b2e61db88fe3797f6ec51a35e00b21a693663da1d484961057d2fbe59211910456490f120a7c9c26dd4eaf963143c92e49ae307a3909c083c29cba3c74c794a7d3b46c7f22785946165e205aa36bc8671dcb0ebb3e2cadff6f87ea2857a6c3d8b3b2397136c04f120e966d473808dca186fa7d874cb2ca9df2d30cff935d2e0ca61d4c3b8c466eab93d4adc000ea991697a8359197d12dd9fe28c666a4c84fbc29dad59f56419be8852602f4782e25d3957a321a04f94eb8280d2256b54db46745514397be42f93c0b02d09c680348659169a63323bf3b7e349e485492243ce42e0b933bec14c7b8feee51401331dab94f89177df5f89ef5ccfb1d27352dffc5e0ca6a696da19786d3bba0426c70aaa7ef47efbe07c4a1e4679ed589cc43f0b1099115be8f450879cd8775437ecd7e1a13521d72e7ad4a2a1051324871925bf05bc10b8e13654f7dd9c7651d85a58da5c99d7cdede20fe44100be06063a55afe39d0d8f1ead5686e76fd7c17f47f5a0533442882e62606a965050e7a3d27f7d8cd9120d59c9140e31cf12c567f2596cb0cbb43f864d65be43abe88ae5cf32b84f00e69a5ade1cbe494ed7c70a1c30598f5a797545194a2f631da958d3e5de560118f2b58da5a726021138a55e8aec51d98ee25f74dfa6e209864dcfb40aec012e18020ce9b800965e532b4f4885ab73824c2ef747def0cf801a12a783fbdd98136ecb63eb197f8a73e05898f3591d82982838cc75f77a8969dfe9d9a1b688a8c17a481c07ac0d3b049a38f9d3d9823cceeffb12a83206f5e532050ccbfe54c3c911e34c2bb84378f9f8806a14d9b9a5381ee88a5639e255bb42a80e0fdb94ed33fa6ed00f6a0d688d82e5ae25b35991c368002f74d89d4a53945177e017e88fff74f4c48851e4dd565625a6ebc3df077b33d0d7b48347a06f7d3085e7c1b0a155af4a4a6de3975135a66983c3ebeb53271b2d39a9d03d1d60abebce56ae97fdb44e82898cf7a5b67c38f4003e108dcae16ec9e592f88fa58b9db772f4f32012ceeb1f4fc34923b6a6e41debf19a9da038af96429d5bee5a2826bdc2e76c5060779326c283fa330d3d1aa59c3cf82f5f3014f8931bf92a9a10eecfa7a0395e7a2046f201080d1412a75a4c461f1a0bc9f108c58e87f949f8bcf17434db3c7b746d78e2b4f7ebafa102b9c81ba9f536433ba91b173f2622a18dfe3822d79ca5f93093d4d17fe0ccf7366200c087039912bd4b03176b6c8db9b739573358448d76cf736e2ebb9bc133fe13998a68c0a00060f3de960c6029a75d99afb2e964880c32e8389cd1b2be8bb89cd680ce2e043916dcaf81e8b6a75f936a0d8c2cef973ed4388a9e621ca7707a64c91be2528c723ef627bfedc8ae2152cdbdb6ac1c7c8a239a4003a8b57824ede4cc3589b5f1b4d1628c8dbc164a39a93376ecd7e6d85f5cbb4ce83bbd4485472956b9c6e3c84d8cf7674529c0eab1cbe68ae6348c6a274c3d0cf89459b80edb5612ebfaef7ec602c00df1ef0ef817f581759be7114a4ce43374e61a1135546f2055691eed65ed1b7ceb6151e9efab8172ab36e4bd417956a70b792c517975ec50d73a7b77d143b91dacadfd5f17a9007fc4c4ef9a317c8615b6a38fe8d84fa345c586b82943636f492a3d8322bb81793ef1cdb2f78381faed4d848b8b056ed86f1dd04625a72b10dc32d7740a0033979bc158afe9b19916140f475b5367f814d55ece5b90a0cc706417519c8f4312ef563b0fe1c5d7ba5c63d13dc03b05282790873db613e6fb45a410cfda4f0cb6228240802ba859b09e2713b438a3fdfda94e633f535baeea281171beca8e8adc2e0846b77fef97aa8b9fd349c8d08e4162d0292818992cccd758a7f3d2b1a871d3225aa4cfbfb360c7823ddf4d69af06bd6c6dd034021dff8cd610961dc2ab16a3e07a0e7b7e63f6c476215b1afb363f428b59c6ab50612cd81e9cc3d0ef63cdf2a63c0adf58d717f49a40d6da779618dc2c41f8fe3fadecbc773b16fa0beda894acc02e16bbadfd6afd66f5efaf20286a5f7db2781143c7fa5c2775d90cffa5027dfb02c5dbf74a1c24567cfbdce2a735cb9b0aea0bc09b4afaf0db9a8bce0c61784b5d032f4ef7d3887f63c41b0d46822add1101f5f8c80db564011a477dfe89525faf10f2f4b6aa168ffa90ebea48a5c703698a9ee6a263a33c08fea21a1026926c7886a310935badc5e1bdb06b09c307720f0b56a209da0fa3a356223415b014b6572985eaa3eba323e4a7343b0691dd988720737233f6f88459e7f2f57b248762794e20451f604163fcf5216a114153b6aafecce6fba28fb4cc785edbdc06efa25708bec02cd342c30ef087a65a0808e4c53341054a2abddb38db5bef9f5a23290af3ef4f321458a121d65c04588bad75da9431865fbf9a3e8ac0fc9cd92550ee9d8505bc8a24a7e41e1bf855cfd587a71bd5022123c8f74dbb37fe0fc11774492f439839d84d483868fd9514826cf09c03d59ce8b703d509625e3d2562f6e455c003040d6543c815c836d5d83d74d6ae9926eeb7f364ec3397d03ceb6c89c6a905d2e63e44bca69f89643b6353e03b0ac49d68ae86f72552a17aa2b244d2985ca388a3602438d84bc157b034b618b39ebe0480bce56e20810db3b7aeb083a75d946ee160062d475e10b500b7468ee95c11362d404a0eaf1620efeb25d0d5991ab68f0c704ab04e336d2a5bf508421ac59fee6fb158715914c7f650323405e475b273d2f4c4c93f5aba08d4e4495a98c5aba3c55e405dd86978381b124106540dc222a790cd03de7f363ee53cf57e48e9e589b539662a9b07a6c02708393889d0001d42347a77e4280f86e9df9ddc25d6e4c347b3ec8645c1b16371cc922d76e74e061e4478887d3ca020a1d3fa5bbb1b9e5f8aa8aa4a2c008bdc9d8c7df1e4e8f74dfacc35c0ca5e2a8fb0c9dad1ac51ba630e5f63d5db4560fd707cb48ced609a065ac84cbc601b0a9d1f38d0a92f86deae699151f8907e0e9354f603ee81a70978047162f0c8dfb977da0118d48cfc44d4b38b57bcbf233847d72b22c5e40624b283a7ec0ffdc528b630af7c8da13b3f720958666e8d2e2ea02c66bd502e32d78ca7668f50f12e30f7573638ee606c7bb80dfbed3b55da1439a8d6e19ac90bcd0e0248045a072d322ee553160b4e0827f564c068512737ee6f46f71821f7bb7a296d18bea49abe2acc587359da06d84809379753dd448c7412247be46f4a178c16817cba3c75d8bdb2902724fcb441ec95b37e2592a38aee0504cf76cb2a55e5671fa5335a5a400c464f3369fc9be7f78c89021842704f6781cc29cb995316b508e91324067c375901c021a4a28f96c9916ec7d2f78736f434062d7fe454bb5003c66fce0c04f5caa9ed6edc697c84f13e9c274c7ffd7cf2a3611510f3100bd5dc6269bbd48ff0cf994840b549dddea6f74fdf70aa98b3660b2e7b5db04129c5bcabafdf4caefcf2a623ad8b8f45a9cf644ac964bd5201e868e3a29348f0dc216455f4db08643128d33797e5a4daebb51e50ab6e56da35c1999b5ddf2efe26b32c570089ceaca6dc512113b346d431c44daa12e1d512ac311595798d849f287ee66cd8414cf1fe5268e06024df0358e33da74092c292fcf777b1c277adda59a16e93f19d6b94c116585974166a47ab53f91727b5e7248090f2f692e1b70a10114890758db86a8590d1d30d588a15571b01d07425f146a2d1ae0ff325206054ee53689709a13beb04647c62493cf92364222ab2781bab03d06cc84ad9625e0a7204e9f7cae5446888ffe25498e00e866239826d6625ba6c34ea96a140c25d66a60a201ab870b6e0b1e47a3d1b3088fb9f6bba7759100d906d220d6f409e08b4688ce0e7fde93512164d1c430c082abbc204b7303c66f49c671967fb24c0df6c6d65424604d159ced5ae844327e4d0b22caae2e3d1aaf0a1b3ad0450e605b59e40d31fbab0923005c9bcd34b6c4ae7d50b0ba8342f5738bf48e2e4b7c9ea33a27c31981ca912ba9f102c0da18862f0bbb58bfa9a233e3729492b2fd082855321b1188f5f88616c2a19d799582871018f68acceefa5800bfb748cad2836b6e56db4472b6237e6ae4f580dc480b2f5434acb149c09964908d934c34f74ff680d28593c58d5db145d224fbe3ed931b77548c73ee168656033636649a449dd03c334c24545300fea49389bb59b1b6c23672295b49ff47842c87754b9647324515104cf27e4b44fc5b894204dd3d3690180dd16b7dbc655dbb54585fee9d0dc79d9285a1c6d84dde9e26296d1c2c8f09e396ad9ea1e09eb231c2f48cab1c6a9edcd3c441cdbc375e172800055a9f4b24ea072575bf5e08ae9d57b2df6de3e2068751985df6dd09d380bd8dfd1ab0d3443d67857bd726c64bde9dbd1d9b639ac9d6d5dbdf027847090c18b2351519ea79091457f5aa8ce909518baa71c23ee677c234302864e8a1fce9cef243c07ae49669ba67679324c05f9545ea837337a86575dc7dd8df0c39a44195438239cf9f35d517d3367ac77fca744e016ae992334c6b21fbd845abc0f11a8a09c08c6bbf116241d497f396ea892c2e9beff2a280e790c8147349f2d33872fbed3ae9c05542f1cfa54b2fc2340985d49597a611e94cb2c3657731895867685d1ad801aa7481550a7e2129fc0b9f8d854005bdce5346126068f5adfd70b46f8459168a0a735186f2561e2cf0bc49ba91bde23dd27d9afe0eac52652f71860c8a7c953734e9b973a810371bde9d04f7b697ea5cb670df49817c32f96d53d2ca94ee72d3bbbfe2b07c2a6e49ee742ecac986efeaea32dad5b4f04ef120a6ee145d879d7830e5f2c402817a42cd80fd2d03415bfb684346a8f8b880ff0c5e0ceb872affa3d22f700e2bb4eb524806a9e2c6deb72b41f10626af2d8e49fc8585612f7a2a73bd24d4aff2a87d6c65245033c57820b9e459e0c781dca745364c6b6d9d36251b7d4d6013cf8387b583ec0a12d01fe0c1064b2b319b0af74839275f284c7fa1bcc22a65f53f9611fa388d3726354f0e6c6f5c6bd003efc376b183a90016b0a6bfaf0f77e9225ad9474bbc9c2f5c7bd9724833c46b581879388bb4bb32f8cb247a6378f715bb455061da15a042ce3f593ae32487114e3f4251e86c4096558e38397cb2cb0a28bdf71f15079c8089049ee90e9f3cbcc4df44ab51bf35dea5655787b06a19c72fefe3bcb4b27f8bb3d09e816fcd1f2e157573c981df32a7a33b7d960ff391868db495a88a8de052bd35465356e46ac2bb83489559f3d71c758da683b53900736cb8b438850652c7471673b01c4879dd50c94183fab232cf65ae830647df4fa22ffd779314c1bf96406a9f23562b9c3def15b62efd664351e32eb9d18c0fe18842fb20a6c1f1650feae6f61ae7ec1e49b677c66bfe75318cd4432f5f69c03d74beae8d226369bd0f8e99a77f0a0bc3d33898f2f4e873b5282425ea083dd6e017931fbacd160cb003c9a72ae9fb230cf22936a5bff07f5c134ce1d4ba62b4714a36923f2e06091ef6ad973d307a061c9b73959397939e394c9f8e40a2c3a5cea729861df4e85250c559af54f968869c5954ffd8a284dc3d3a08b29a7457de15724f31e811857b8ca821bcfa25c7ff6a936bc0cdabbca05ce0823ee9f0573775b0bbd44c1be55164eac296a7c92dbcf5463c267a2a494a106c618a6bfe47be52e0d76e014c6c356b64232ebd010198564bd9641459bfc92f1933ebcdc3e4306a72259af0f3983e7e4e725593fd548e8c7384655d71be878c59c6846eed4b7a800da2a2bd52402887ef22aef5f8211360b9aec6061e40402e101c8afbe4a94676ed457f2f7193705b4ec1f02e7df3f0db5013b6117770d1cc856156456a777b9794863a5d9abadc8e214ee230d55e797675a4e47609d1d1789248bc5afd88dbb8f7ea59306f5a28c6edeab6e58731288e4ead52245e1162f6e69b0f7642cc878e0555ad1591432c74d6f8f21b66a03ca84c989fa0311152d3c14f348cfd9a5af0dbdff90efa8909ea223b1f9ce91040e2f62f1cc0fc1f212e38a12457f768c58043d04da0c585b7862089ef0a3e1c88b5af57973bcc7dd0272245aba0b7fb06ec26ec5f62c4bf2757e60ba4acc795ca13516dd6132c0fbd453aeab3bf5d58dbdb87dde3356afe56f3723741f211ba52f9a2bd77b0b8a1e0d56ea59e9c23bf0b4eeadc6cab0357242487a45c53dad72d613f12d7b27c88822c00bd38a8cda8b850cd3bf111c930d7d594587cbffba1f72def706a0db62c24bcd3b1b43ddbd2508f6d8144b270103c7c197136c1cefc17c6d218ea59e027d402f15176a39564c31af3ba8ad64bd1f8bee34b89ef49eea34bf26ec34b79788a3dc2ba65499f79f13d98cd28086876fbcf01a79380c3ef7ab78138d5040d6d70652818d15da5aabb234f29065172509c691f5a2c0ebe8f11d8ac7b6710cb494bb981032817c1a4c283fa60327e8e0eaf704a9ade5bfdc0135e5756f528ded44e0da4423fe0938abde28763cac7e05e702c2cceebbbcfa6b1aa124b49deb80fe5e087f75ec1d97c5e01f435de5ea66a05555281c30264a2377c232ffffe631accd1494ecad617f8483238306ce67d6966cfb3e9e57e274e9c5bfcd1e52ec066262a24cf698c01e66e95dc015df17b14367acbeb288a549ff98c4795bd80ccbc16d82e47460b802b6224e16b489fd514674d5115f641b3865b5a88deecf268e6f0d508d59baf1a055ece3b9b495099aa0e65dcf323cebd9ab258ab5c8c49c2396088eeb0e4f3cd1144f0c6d437dba6f0306be49da7d196654a6fb71a8696ea17b081d97b33ba04f9aa1140ce31f513a2becdd00810e133dc10dc39580ec62e2259f939d24b1763ddcaeb704bc6610684c012b2b04c0a260b69135c72e013a070fcefdfed779629c28039fd63ab456eb8fe9d236f5492cf4690c5a05821c86f2e9fecd2f877090d7bda1a098fe38a1b69ff55a3d6c70cbc524a31b1c9199e7cb76a05599a931df38f3a407741788b2454368db95aa4261c17270013261c3dd7c67d9ef17bf916c70f18ac427950427120c23fe448d9b2c79e313123cdcfc47355403257b6aa28e13fb3078836502bb21128c8084cf4d46a35a49c4881781fee8bfdf71095c506d1945bfeaed82b46775c0be60441da63c9af0e91f35ea759fcd2cb909f90be4485f5f07a6df72424598b1590ea7f52c15dc62f87cb6d34acae040e2def944eff8242d171967149fd1179c7bef825dd22e8b149ba054329e885efa4479aa7e71568c6d02e1a7befad4044fc14ae261edd9ed9e2552a9c922914cf4f3a61f083d1321d4249153afea9d73be562f846ab9feecf193e425993f66f0baefde6102e7f74db15e9b36c99fb8fd1f6d445c5c3bd69704199b8c02640f56309242ed2acca0e02a0ec7ada2c6954017a8810f8cb88c9f15a34175404a51cc0052863376f4999004c7fb2ae4eace5cf253b2788029a0db7b77cccd2ca2d66627d0ae197adf2874f80a03b2f677d777f6764d278552f0c40648545257deaf7b82617b77a8832a899f844a6002b1f23bbc0f9ddd0c6cc1faee634e196c1dee2678868b99590a2b2dea869ee03c3a38539c126711e48465bd56e67209ea69e2c6aa02c9c0a61244a728d34af4870d4af12bf84a6e3308dc745a045d5e29cab77ebd8664a114855d9da3e70df35ff41f4bc9eece5352117a2ab1dc9326b2dab3b5281ebfe40d42249bc458d1451ae1f5193801a8516977990cdd6a54c7e2ec66981ed3eea62d356993cef627e873c1fb194ec420b1e4ba5feceb7f2777e6e379937f3a29f75997a25826c228ee6fa4de8caf5da2ecb63ee9c1c7e208c3fb4995dcded9425dae69f8222bf05bfd854da4b69bf54b306b77882517faa6090b5812e896999e5036739c0d9f8215cd208f9b61c9e534e207fa53d5ada10cfd8ce9c3f4edb45cbc774b2d999f33c7ae6c06555a53376e16c194f1358d0dd928c1962a7d4ada5482cd3b8b2b2eb889ff7512b1f3b44118197bdb67ca74dc0364346fd4c8776d6bf69101d58b489b5bb897201180d67ce9e81cd2c2bd02bc0d61970e7e5bfa1d1e6219bb396591335ae139a8a3da29d853b6b29e835f64406824e505c8827443b8827c89ecc888758718e06f93dfd6280e6a606d269f96d1d183d9d2f8cb1ab27b0733b1cda954a4851088f59beff780271e24f0849636d05caab7df508a38f92e147e434c611923100c58142671e681f2a168247ffce153db986045eb56c43deec4a29a9a2be2e8de689d41994c97fb06f667481faf272573a3b949038a0423d0a2f4328fa7195910111bfbfe6e076b50d35d2a613308d52ff062d4bbc54373ec2fb0aa725a92822b07a8406f49b829a0238a3be02d061483fcbd53336a957a965fa5f28a2c1f074be21d33fe513c6082850311f3e7c6aac51644ccc321aa45291cb2f96ae8bb2d074a97320c7fd30d25ca593c26d042b6f5e22c0ccc54b43698c20c5dedbf45e1437efe15ebd117b7f87a08a69bf9850921d7643d44b35ca212a15ee747c5c39d1dceeb55c4c7b98d5250b9d38e9800d724c2d34fadb0c6666d7faee8d4733f8ece21d4448de84c23c0ba56c421896703ae41f3f59f04a5aeb1131bb106232d5d8b7087ae9f3b24f9ec5aee4ba1c824c4af49b52d5d285e52248b65b8789864790549ff783ece3e7ff2876319a3f8576bd8a1ba8f47dd408955074c3c3973a9d1764d7d20e7334454abcdf1cfbadf6683e665d9de19a5c8dc4c1bb45f35bec348d9b2f928d2fe90d82a687fde06e71d9c2283f3910447f28fb151a5142966286432c1e1de6d485366cacdbffe809f606bac411d52994fa0ece3a5135b93b931f7a9592601f02fb7c055222686d1386a23bcb513a7908b0f2958077a9ef6ece7cc786a9a2e5454238cc0c1a7f25f099e20734b034c038f5bd727dd443c1c9759929a7ba1303e5b85e176b94e5d7e0ce85059449ee6a5321311bb7e0c7d2936848394262e7d406eae4ecb4d9cbcd85eadf62e31831af30fddd09eb79f8a13aacc9c9096108af8bbbfd53c73bb20c4c34ca94c7bf332f5052c2c1a4ade2c67b60753830f95cb4bf1a6bd4f558353a51f2fb565dfd9dc8db7035fd86b63576884121410677278d1c7aa457cd4498669fe673d23ea20abaf0dcddb094100d2441dfb116785086d1e76078e460086f7f60de6c0b881595ffab13ad1b26b068674f4b81a65e9eec7393a9b47eba142ba8e9e33e5445cb13090933dd87978623a695c757ed424d319fad4a2fc2cb160095428c7a88b86398bc76609bc2d6d86c85e0d6446cc5ba7426c0b8604b6e0a9d090c5e35e59df7c019220298e6544241474a32b3ed1c82056ae26b474330fb355dab30917a89a666a0e7ea3781267b84f341fde918a398621da7390a0c5d58e2aecd4f65173bf7865c17e2f009739e4575f648102928fce2cb4c46efaa2fd4fd4ea2d372600aa52bf5ce378a8fb8d982f0fbb27f325410f51d5ed9f67d2065ccfc26a4536428a8770d3b4742c252d10e74f5098d049d63a3eee478e59b2a28ddb83593b1af0e3c430b5bdc5bc84549a77deb287952f6e2f8a9530ea3d9a3727baeb6a121fcc0d49d8710c31f69634355e0a57606d9b3a1523ca89e617446195c7ce36747da0b2a90932752282aa72f8fea4159bb750000d3ff5368554d46e011bb0f51a6a7054eadd670f4193d3a54f1b81aaa7ed7d982e23c0a2f32b46e0b315a606c2a169c4e28c17b513b0a5da04e54336652d8b71d75b8338f721bbef468c9b6ba65234acf54b1d86b57d49131b7830bc2a6aa50d49406eeb6977ac05a9f3184a9785590717dd62669a7cc8c378c525a55f86de8097dbc6afc69e4540ef3b046d96689aa322880c7b63e9f45462383e3ca4d7c0f5e794cbfdc5053ff3f74231c3081d3da6827822b0c33328a1c4a05f328ad7c229ff30d1530ac66825dd0830dd76eb850626b9203574bd2e7c4f2c911cdd021eee1f0731b2dc0eb51037108172ddfecd0fc74825fd38d31b2960781cf07a292aaa39837c1ff959faa32e943bdb8d09ebe18100293ef5116de1bb06568cbcb084d5664f34226bbdd501607e3c979e75e5f0ba46cdf5cfa9d8501a9f682f6e8af97ebbbef2497aec0d008d4628c8eb024d8255c8cd5f68315e188067a6fe57e259c7635392a6a6b997ab1f26e5b5a7f4d5353ab212811afbb54c69bdb18056c42a65127a5a4db52be5c17c49b6467b304b762d25b5a2531e07f5c3194723033d5c21bfd4a5d8cc77783326e9cb8b36f14b0af6dd04ccf6a6e091ba8990b5ecb60759b5aec6e3a172cca9ad74ef3611efbd89d34bc34ed19cce8f4e86b2eb5f968a7ff273278fb34a84fe7639ee1c7b1f57de448f553c1fd587d4c234b1ddbf553099d445c603989d7cc04a2f88b77c6e24c91e1ba4ed1fc09d39d659e4886ea405bf180734e6583ac8b1b8d4f3a7a7137d5df2f1bb7cbaa4cb0ace89ef4b454a5ca1297930e7df22db484a457b2c97646482c3b90a4932fc7e88505f9b6a9dd1fdffbd44d636739e263185f351ae0047f6740f203c75ebdff6680fc3fb8ee839092ec7070b08426f31c9bdb0853654a41c42234854693fbb6453428e0643d934fe9aafc29016017dc7a3b9ecd8f2ec807650c23da04f61b3cc64f1111d791bb0863ee93ea71e97863405225767b4d1980bd51ca67a97527700ad0f4d12049dad252dcc48a561d9ac9d6bc64c317405a4215e1629b074e769f3b265dbe61c66ac100dc5d6522358e87503afba0dca67a44a49dc4c845ddb57d3ec4be7cade01241f97d377162f3bbcb930ff600d7525df8d4bccbdf48cf52bc54735a8f5c876d4e91959a339502eb81608a89f0dd6b8635febaa3cde823d53e1a409388941cfece9f436c4a0d95d9b63d6ea3614757a42f4042aad13813fc30f5850c9080f324aa61d3d19551e832ad698aba5107bc1d46347bffe4c1161d4afab2a88dedf8a51c3a0e8c427f9000b3481930fcb3794b8f0b6cae9107c45628c5a1eb00dc0c225181972a686461035d47903d51b9ae861c62a0dbb615142e47816b0a074a8883cc2392cd65467fccea698d64602ba746372cf6ea702e4e36f1803cefaf1eea9938e4732df39c06f83a1aff41fd4520fd162a31562a0cd81d7bb3a0a1157cefa08f69c6610643cd0a4debfb4ae8a41a05dbdfacbee0465e356f847a53451e9ce965826c17e2bc6abaf757f0fd82507a116f5fdb6eac34cf47f94ab68a95ac2017719e6ed925cd134fe93daa15e384b2ddf9e265dc579e68859ad5b514a53418f6ebc723b3c32d1cebea46f7f17f457a30c27c5fca3cdd932e2813f0a299834bcc32a73d92231c0aa7f6710d5f6779ad4c6347e32e9e290256927b436b250c9cea0d4286773269a73e1ac435b1eb525c60dbac9ef9accbb2a6f850b8d202f1657e579e2d11d86e10a4311c49857fb2f0187e971c120e2cda6015c07c148119207a8038683a1451955787501f5a3209173748d1666148a7dadc1c3633e08c6981debfd0ee1c0194d16de259c9acc424be07685abb41e2d4526098b4ab3e0baeb5945ef0819b6ae89de5c98ed291c55fddffd82f8d21a88e600e00fdd6a8a7c2d8981021542c30b5d062d6a7e3bac076f71877618bcf981e279b3bab17787c4669c76346bc937d460bb6dce8db2f018091a1182fc1620ff016f010a4eae014584519a7e54314581ab1ce1ee8ddf6acd5b965cafe75c38e9cdc36933b16f7d83d56b1045ad1f3d015a675fd8a572ae1c971aea1af14fb3c715d6d7baca432bda8e7c38a568bc12620f33d36c2f3e221e1223cabe84c170d26d02b604ff43e226473bfc1f0a60394e6fbed07c3ac92663c7d4a88ae2a84a5dad385dd4a8550dc67d6e60812d923a2ca24b1a9b09b28c37d3edb4009c897cd86ea032dc913415370f641ad5ff579da2d78ca37492e08434fa4544211c2a2167904d25017b4d112fc2f92baec2d2f81abe20e6770089742d21df57091d9a403ab7047eb79a68c9edbd35c8054a2e4ca00c1f995e32fe5641d285614a2d892874c90f33eb182452600d6f027c82dff51dfbb41dbd395497c6bb01edab32ed08557122323a913ecefb22060ad3e732e495c7d13213ded28bda2fd60ebae2f80ed7a04e52e395b11e4251adf4130b2bd95d32e3c1f91938fd98a392ba3d496278ad0609e0b02a9c8b91504159ea14afdbd400a2d6f19bca636332b3627e47f958cf9d3df55147fdaf37bc313d6a3a09304b7cee1309453cc42a1a1184f6108e0c541f740f0a5a3e32bf967916a24cb57108f2bc3700af5c56623de3fef6aa278f6c8b04965ac38d1317c227e4102c7fa874321280e0a29b5f1ca14e5f8f728a35b2a45b424e5709a08187779f564ef1726a141003f59e5bebd5f08677f3970e80d8724f9c5adeb849301279873b3d13a0a1ee114ced55144ba1f97eed809ec6010dbf83fc5f9458f810f24909791778d2f21fd9cf862e4588b22d299e79dcf595f59adec62cacf397989ddd46850c9d06ee88c9097bc33732cc2348f881ffd65d4ad80ccf0cc84ef045ea557e00e29716df0e365498639231ae14cfb0f486b2407e8e16195a83220f2f95ebd349741595e41abdcddb91ba8b735fd272274445e8c5980368b8e725cf2abf66402f2ea8992e18b5dd1a830740bf06086dcee176731216904ca1f7e8450f6b27a6a2936658d4fba8294ba4c4a8b330d8dec659605942e9f0a52a277f220d02be4edcb8d99ef0b8e40dc012040af3a5fd06b37ba236e03aa8c67ddef94667d0135359c63b90dec611ebc42041385355b615fc6a0c7f44f469af49fbb226a58523b0b27b063b81a4e3dad3d5d28081d183040120924dd4451012974d67b3e5b4410a60a9d1e17953d49d486c2e3e8fd07b7573e692cc6421a00ec91a21e32757f56017b6811112d8f0d3d7d2f9c142ac28fc25de6ff07fbf5ddc4199f21835496035a11bc6de67bcb5d2422bee7e13e2f971d979727e641af1d0167880fa58ca37e5a6e5e7e41b8cfba9b84e8a9ceb964438e901039a20719a777b565c7c39ff4c3c3737e8df5958f3291544955e240cd48910102f5213c776aa0f4a2f2ff170a57e7bc6eefb953833fcb6cd700ab6300a3ee51780e97663d4df085ab4170589236d5d5c0834c2c2d221f6db3b343f0f909f0ed921d25252971d4305839bee19bf06ad55355a109697685c14c59d5a5c9638b1ed589859e0ad44a2baa94cb9d54d08ab25c8a28ed331a9a1544d11b6eca5b6023f4935c42395a6d05e189ea3d6db6cbbcf13cd0d9e88e015291ae27f3a00487b8f0c843170ed1654cadacac8179470696f6a43c9a216769355d8fc0be48f14a4f018d3ede31eeb264b237a99f838d8d4b26bcc3a8b56414c50ceb1042c69b4144bf7cbcbdc0d9a7e2125ea4fbe2c7f1659a3a75c8a3f20c4c4dea21677dac5cbda492be57ddfed2d00bfab9b100a6e28ba772f5ed4d130140f5b7fcad20c05b1ad22032f2dcef91c92f538787bccfb633e685a7823b3ff21c4a5772a683487b928aa81ca170e46a5f23df080f088bbb6e49bb52033db3dce2aa592e40d51fbd883157660acc1e33c32983e1c41b63845bdbfb2524650cf3ebb054fd6b67e12a549ed0da31dfe5abfd6790287a705c4506e356308a1f34450a74c3a51ffd57152d9a38446e2dfa4cf578871203fcbd2dbb191fa440d5e13eb4fba67f3a4dccf7ce87bbf5e35afa342a2230b5688f6b2b6d46ab24457cc1871cafcf0a108861ffefdbe0f159714cb3d96c06136e69a95543877e97ed01b44b863b2ea0eb1c3eaf2f44395164b5deab98913c50023083207d3fb9728cdd66e0f18bc7637c9a53eec7c45fbf0d6cf4a66c40a17ffe9728305af8a73ce2851bc9a1d60de719f0bcb39998889d08610947c02995596482d63f069115c6fbdd40fed573cdef624e4172922d3c3b7e79528f2f54fa3c1366f015eb237ec38eedd6b24e2088e4c6ffef11044b211564f2dec97c1cad81a0baf7e53d20d47a920684cf78c2a8e9c396a7102fd902818538fa6257fe8e82a018a277a200b7d623c4a4e11571565ca62683b03a21e32ea500474e2bcd6a1fbba3c154c8b5c226d2ea3b5d5fb7e36126aebe1c65905e968e318f697f7eb94cbb9ab346a1483f0006956a51e61613e13f8a782f7a0f025ec716236c226522f54e7fbf5cb51428d2b629819d829cdb96c57b50740c4e396eaa0536d7a32814d4910bb267fecded0d6118bf765258c277ff4bdab746be09ab3dc201f6f7689d3986498b83c22ad25c173eac39cdc930a77e1bfa3680bdd7b61d3b44058442e1478f6ac762d45efdaf30ac9d2b408489881565d103a700b8133f9b456ba60d0107c4054ebd73a0485245b5c0208188dfcee2abd981f3a6f93340596c0778328dcd09d94f856fce5c4376fd9529aafaa1d115efb500d5cc46e3072c0a1a3d9d1a923ce17e5a617f810cd5361ed6523fe09d99329ced89eb5987effc41a4683a454fa76b3df0c2e26595498fd6b23fe7c79908edf89de1c63981730a1c44516e065029e813b5b7552a0afd3ac74cf81070d90bb5bf78cc63828beca13a0761b26e7583289e61a3714e5cb1e7ff97e1c8b357ea733eca3de145cc35b01ebe28d4489eb81f6dc7051c6f3ed95c2af2bf7f4ec7585943e697dbef511afe88dbe5ed0e9dac1e39aecb5e5fba8da3f90026df4aa6faa13bb8d0dac61ba5c67131af28f013588b1ce4ae9bf0296928672668b8d3f6a120fd4ffd019162e37ce5b56a1b5057654efcd9d85718dece6491549967b7d1cd89dac7358704eb6ddc57e8e3aea45fd05d48e3bb9341c0e03cd25b58263de6bc11061fa0dfb5c772c038da97b27d10a5e462847c4ff50ef9a5f18647bd68a7fb202af9cabee0c7fc60b248a9a62ebe987751aaa7e73db0a60eae3026024700a36f95e8fc1bfcef1950a06debf069ea38d01e2d7cf1984338f8bf25843fe4d52f994114232c9a86741b42764873fedf3e1b397c435f4f02b6443d9dceae8e58c4f52523bb84226efbc3be61b83137c43d5c79e0d425afd639fb0a862517fb667aed9116f5c87bef9b75657b4b74ea91622c3a3711c28bf9c06dde8772685ecf12fe0e97effab6eaa2a52e5be1b3d6fba65de6d5bfc3c937de21992b14cc973ab1198cc1dd5657f9cd036a99c1d3590f69dfd4471af49cb5580ec94a859d8512f36b101a3e540b13da8fd34da1df2d933af38eed018efd65fb10ac3a1177fb4caa5e992b28a26494475d4bfb1f0be89d4e2f380deac2c02dc4a672be6061d5e55062e8ac3f533b79e3f72e6b75e701b7464889652f41e2342a54e3ba1d9c659cb294aa336a8e0d177d07b714ba42f0d63c37c96820f0ed19a1f9336b2f538217b7f2953a2fc74df8f38f8d33cf31462fa744d21b42dccfec998c9d83ef5999e169fa746f39cd0e7374ad55346ef91ed514fc5dbaa15641236f69531fd280db498c3f0ba02088108b21d7c0e5a30acb17b10e61c86a017d4bfa2070e1b111533bc4872514e1328c5968b754b7fc407daa5d86040723baece7008068161e6c56db4325b418f66328e24b51ecff4abc11d6cab9e9fa76913fb9e1ccd5cb312c8cbf2a5ac0809fbddf70b7d6d1f925a1a9230f05873af3f45d185d079af4db42a0d086ddafe89a2905b600ac5eced39f0378bf4c11fede792187e9fa694411e06251b77fb6a427f7eddd148da10a4250acebe673dadf81d2b110f0618ee7656eed502568affc88aa0103b65a8d531ef31b2a8dda86f6db5fad00c0579d52798357acabfb5dddd0dfbb2d955757643da1edbd04e341e6e13dbedd8a227d92cd2fea14d0d4182bf2a343c3f50e60cfa2130351696cadd45b110cfa3661156a2262071a1bdf43d2f537a38234f245ca56ff7ebf2f609003c9d19403ede9f80676bd5e635f39e1b95d0baa9c692102ca965936a7b91cc78e6f8cb1ea41bb5eeb7ad4279f4f4a3550d3c28e9841c8926ca17da1c0af0404c0578c43eea32b8b4a8a9c6ae24aa1ebd8b0811c38335bd68ea8f533b017f927737e624dca2bdb167a2521d9f8a2391b4abf705c731e7913a5249042c2b0d2216a8a1a2371d597896094f5286061225a035a050066782169efdb5074209504ec5281ae5351e26ed8b277a83d3f74d7ceddcf0b9ba891909a90bb9fae5cf5fcc880c56341cd14a486c0d62735dc94d11153ca5a08f0d036bda582c3671e63690b17c6f77c4b86e75ff9bcaeba657b127379ed45f87015a25de815cd94580354589ef1383e25a3f80ba1228f05c55bb4c36b86305b7257fbd700889a4ed4ba156c4de03b83438ee10da8827c0786dd167710d9058522f9419551867a349442288fcd996f1b965b1f89f7b25aaafa5fd5a7f5e6830c27600558e76a0a71c67431eb69733c446c440ce052db5ad1a6f6e338e2a511abf48efa44f7950937a73f696b1b5da0b1465589a76e61fee3fe6f5e947fe8150aa78d04c54838773e6d90df28b6f9cca20233c946153dbf4e7fe89b35e0584009f9fd5a29161cb6a29b7c289efb01e5781da2feeb70f1ac468bbc3e12216b9e1f5a876247516936b2c0a307bd78e1bdbf5effbb1a7363db9501a6353c438d61b35eaee9a2012cdc25e54d79ea761df21620c6dad25365b3f9b18c974217e607da609bd4f56935972737b1c111b70ccb60009d66e186cc051d9017cd045d434020e64245107f18b5b7f12c64a4b59d9536b0a8b51361a68cef74aa72ffa6801aa3a77acc6b5d8ad39c22783c951320ea0d2b1669ae80d88988d5f974a0815cb9e6b149f551d227a6ca4536148469a093eff1c44a4c5f1068bdb1c3e09143d6aabc3c28d95d3b4a592b30c55889bdd7c2af626851c749efc3ae830b3532c8f090245c7c994d22050f4a733b7a81ca0ac6a5afc8f4f09e9398a6137b0727506a7520fbdea6c1ff795bdbc61c40f0d6bf40ae69f4b85345dbf092e3913008fa89931da854be7fea30109e5c21be32ceb406d946ca76dc909462cd8265d67978206cee59f8a316a8814baa72c4110a4a092e757fc129d7b06b8483d0136f6ee856af8db9b0ca2b554c227acb11de5154181b2741b0219dda1b36c2aa7e77c1c0713795b82788c9b32f49641297c385f12b527db9971f4f1360affd0a9de7e75cba7b6277638261d2108856b018e22c1126c501bdd5cfcea8144323d1d2ef36bc49cac36c5fb7aeb004dc23c3967457ad9f8c3cb0e4374d9eb1c4738150c95a3d1bf3117e836714a34885c8b907fa510b93fd67da4ace26dadff9e406253c43025ae8e69a49ede9f61735947d13212ced23a1699c7a1ed13d9919e812d01f6a22f5e6464a4afac20dc3841fe95411e3179761cb8d42d47fd542860f545b8b4bc4b95e29f5a280d4e72fa78551850a8547596a960c3e2a9ed8d4f361a4c3e31fbbb704ea1f68dbe34e64c6d856166f38f81cf1431c856e5d93345fee56cfd624feedfd8e083a460a01fb78c1fae704fd1916a0addeca1faaba17c42e10784b8aa6424c5430af8d8b43c3912a9f5bd517550459a93aae048bea91a2d946f0458411358df44ac5ac79f01c19fd44d839d9c8c5396a8ccb6c9df607b4aafeed3efa37fe642454c5a0a54ccb4a86450593c423517419b02c65df329ad1b9a5fec16136e0e71bf37d253811d6bac00c2bb8694e22d4cd4913a297cb4f27851ef6e3dae7463b756b45ccd39fcb34087778a9c52ddc226b5883f00a9d842c4bf17192d605e4ee6fc0fd7ccf7f54beca6de896d300a179cd2ad86e3b03bc6ebf878d419abe18957a802e698f54069d1c79de151831d01c27427c58c926f5e3c04fc2942b6d1a44454c5703d0993362890769d32b9016c37b680340b89d11c041e95c71f3f9a836d453f217076934d898df52f425c69d3b2dc421170c7c2c7049b0eff275089dd0ecd0fe884686a4008cd80ea44a51bcae0c4d21c8b4df759b6d780481cdf23bf280408777206d5131e6c068569dea875927ba31bb7e77e8159dd7ebceab484081539ddc7e65251bf636ce39eb118da0e3c5e1857b6abdc22daa68a981e418fea6d097a916b4a69b01c549a35eaf55d62c35ca21e9f84f9d2812f17c0e03a926f446259d59af1e0489165982d3489ea549e36bd3fd89ffe5c7ab539388831d0f2a6e0c35763458f5a397797ce1c904a5941cd729125cd2c3e20da2eacba6e46a2774c9d1d5d3ff6afb6b62146468692fbdde5f36cc423962c258a03e91cccf4029327adf22d6042849fe05b23a055085771f4a62e987e378b82d70462935edb6c468bf2e7fd71360a19b781da2fa51e9e22cac6b766a14ff73b2f15ac1219dd8916cea0cdfb74ace541c16f4469084397f1404ced2419fabdf194a80200c9b8dad5abd7bbf1e7c0ae6c9d2c1b1791241cb53e67395329046243cef4c1210e54330bea068ee9de71712a70a382d385869869a93c096a58aa1a02a8d5ed1f30a63aa8b23264e1997730253aa87a103fa990cf26f84c566d6d5133bf00ab94f3aaed0f1ffb20908d605eca1eee6eb06cd1175ac181f905f07526e37767c5acd0e7196dad5eea39108764c5e5cea82e467c576dcc5088047f0debc60b1129582d38e3589aacac941c4b1f4a41a2d957675d0feda1e677278c8da7afda496a0a7dc743d1580195bcdc16602fa3ea95925b59101b1375c64a52c394cc66c03d0b685a847fa2f49a38ef186bd9aaccd4b7194ebfc5a6157b1d1631a1bda60bfe565c4ac64edad5f00e984b78f4751d737bf2a097c6febf1ffe03c5f0759721290f2ae17c52bbf094e94f5436ff5dd3b164b337a4d0fede40e73c9659ee3778e17a8d14effc8dd9c34a630343a5eccbb25600b9088f8efded6be4f8e6a758e66c97c8fbb8a55dad82d193bfb5e910a900a925ae791311853dc441c255fee8dcb438aebb4a60e887178eedf59421dffe05d0e466c194ebf0c8e945e6994aac3cc8b9c00f6622432e8d6642ba5dbe652a9195fd4d3d7db52b2cc408b059d580df593d4609a43a6c12358bfd2081e860f15ae175b35fc9eb0389650fb77099a2e73d7e7417f116466120fb407fe3e9d6797711e2ef2db641c706576d5b6bd6930af02f16ee0b5bb4c1a5187faa5e68e73fe5e22f2abfbfe81c4af512030e7395580eef4b6a0747d5c8e14d02853d70576f02eab5f7c4ef692803ddda8acf2fd974dac14a27cc6d91ec32c114a1c14ea46d159b8984fe7bead76367285e5aa9d7cdbb283a0f96bbfbeabc19ba889f3ca770c4e25df0c1b4f5019d3629700b2b7428b9be93d673cc3b32e4532d447fd813b59b4e6c12784326a06dd20be27751d8bb046aa6fac992fcd1d7a5940d7e715b478cc52ae520b948058d54bd79e37c312f6e1c1f8ece4d99e404749dbcad21bf586ad0cf70cc6cd1664125002dcb13ba05ec23b9d80fd0822b9e55f2588fb89ac96d72e7291faabf72ccc7edec5d95b868180b7891ea1597cf1b29bd6e9f7532e9b1917a5fd52f48f4f8b6358efb7b5ecab2090d45132c65d3b738a734c8928d6ab6ef8a823afee0ba9351ab22bdefb08f1a054a4c136697caf26e094feb9e2735e1b1e5c6b1758810ecd6f458ed48e5cad820c490c4e4fdd60e9b3e005da88207fa798edb2ec5610d5a736d7f8962db1215df0740d66cf3c092c549e26637e4d8ad0e453fbca80000b55c8c97c4bc9faf1fd100784e8d163ec99442dac218c7860994e703f977fef2dbfe627f856f1fa7e9d776e90216911e44c139d224f8cba10354b3a99584b2112d380497e6ec77b088f4c2671ede6726549a87eaafca321df416a619d7c8274f0fa6e66fb6938cb7b7a81d5fa1da8ccd919ab63f7b09273b170c82ac18e6a93b6b019404f811cc6554e72835e184adfe560c7c1616bc684f0c42369a457383662f8b37af62974f18e19497a90391f73142be703b00531cd958afad23619bc702ca88dd95dfd7bdc60498146784f44deeb8c77ba82d52cb03e3ca4647010c41c7b3d0758b0dde9ba1077acb16b33ca8d0605d210e87cbdfd678b475d6ce33e02ae266eb74c0adf59958973473343596b6a4b527b36aa9243a48ed525106d142305529e3197db688970ae006097ede218e34fd433926c9147d67a01803fccf2eec8d8cf3e80016dadd5328718f5eb4899a825bc13981e5023e4e7bd07894791ad40e68679fb6aae2030c09fd0272448db247075459325164655716b491c8f11babe2d2ddf443f63aed3450bdf8af14461afc6a8a666781c86845ed01172506da9417105963fecff0666d01aaf1dcd9c61f62598e5240684730b9e748ff4e4c586f3c8b8ee470e770657de64bd014fefa8efa512858519f70c8be716c3f7890b815e8e48905d9162e64cfaf19f5487780668ed53671246205823e24e351ed934b97330ff501ab85f012f0a428f0bbed75f4a6ca0b4fb55807408afdf16a891b8bbf3b7a97b00d9bf64f12e51229843277a714f26166f431e67bd629e6a6404ed784a150deea59c051698c3735580f06426d696bdc43470ed6f6ba5b8f5d9663111509c47fc19ef328c3576ca62e9c2ebd6d7d49424a05ba9826b2b47c2c25a26d409e698b401e2acb4b934c8a14bfde5b4eeb36e82ffcb7432b9f3dbe371bae16f48ba679bdd182bb99001ba277de6b17ef922afa0ceb1bcd8245ef2c97b9277c4f832c0caae148212b86a43cbeb75479bf152c9790093569becb4a7be4d59966a03a8611af2cc64501ade0e794d7976f5f5c28f1fe7c0803353d82906459b82a3be9b35d62c6e509be252286f7235ae53c11d6e2eee39b590d0d31f054c1ee95b5ced7894374a67d7c40d7a80853dd594424aaad59cd0f8c018ea4307ca13e6e84aaf24ccb315d03d973da528472c6ef8748dcd173408a1eed78b4c13a1d6e9f5c41b07f41adc7a800d952845d60b4aef5737b32f233f1c18d672b86ff70866f02dd431dbec9ee9f2aaf2432e05026f1d26292f7d2137568de5cbf013266a25462cc54d3311cd90192d9f18a834ab238b8125ce07d42eb701b3a9a1d5ae10c46e8ddfaf292710d45e15caaab72aa8be2354888f71393f8f2cd4a72c58b57afa20be7cad45347e51104e2ac25b49f59761f0b90db12d00b7633b832dfe252d006ef43f301184faf5e7f3977f8c2ad5d89b8241a0d0434289df613345f2aef21abc44d68b435f2a1fdc2d4e6e05d2bea6fa3a56ff5ccf27f9174d1047b9f60202761e62080f1c99d06634809204cc628377f5f009758acde35bac8c13c206ac0b007f562378a23c0c6cad17c1bd9fc30a1976af719c0fce36cfbb6fef8fae76d3304e11f5c1b82c51fc6ca6079d2a26e1de7a1dfa6cbee0dfc85bb3e6902465ad8181961b0141e3c813138d699e65f58f7ef235841a18493d518dafc6c80061bda1d8cc4b7856086e53b4010905b2f76359fd4d894bb310fd3177c44b31f3e98bd7331093cc468ad7ce9adf4fc38e0b7e280d986e5ce5ff92a4becd9ceea184b4a1181ab26058803eff06b4c6d29981bbc0a20199716e9f22ab9dbf2760d52ccdb07f8914157c79661a00e841d8a4da47ca7d6ccfba1a1df00b0de7ef61abcb2e65197c9b00f6073a6324cfc445a4ba0417aee695d9bcbd09c44ebff0b3bbcc1caa06552372040b642e07fea4afdb23fc186a1577230a0bff562e0f2bbc4cd674ca8e9fb98ff8b6f340324c9860df67a4d08e9c0b0b7cd173b0bfcc7d77cfb8c4f17b251abc41755a2d75e586ecf5d49dd4604c931eaf111bd7e646b3be468b1ad06cf97e7d4ef44dfbe732e532f84279626f7cf168e22de95ee9f5d03c0cb5b412857d78f0fd1976b6d4ed81196dbed8f0309c7a2b9699e5db51b5c3d4cecd78069e3fab8aeb143de17fc89544b24f7cc28439579eb41317344a699d5572945420e0ffbcb2f34cedf852f5f4b8ef896a056f78bb7901ccca3b4f586c2def5f48281fd1a5075360fa1edbe2bfc0742e0fd5936ebd235de653b59202bb19553194c3bd13410fc44c3779d23dc8bbcc2bb68ab5876798b60f1c7cb240cb4405fc30e243616ed6d23ee8ac4fb65232b8b4e83697818dc9cee6719a660a7b69cc53111cfd7ecee14470db91e8a4852de85b071c8f6981a11caa34887187ab719486fc7bee7c267b15c1a22e3349d1049ecf28d3cf35fc115a70acf0ff5990aa23d4131a79a0b39fec15dff1705b8c26ecf76263cd84a8b9c2e08f93783dd26310d66974ba93f8ebe0811b3592a58e07e92934071cf90d9ea7c583dc3b294bf0ab7234226d2899e8878d139367668213f6c8d999948b79315a222439a56d12cf94b260382568347a18be26779de4f52d9eadb5f20769661e824e3a084a73f641870aeac0e7a00932470409c29fdd131646ac05eb2d06d71e37b269e12c28e0459df0fd04206ed79678a82d37f917ffa48a12c96aa90083c6f9313828626693515e57f5d0ec93876f7ef8d44d9fcb6797915f9783a37f2f8214dc6383437fd5dc7e533ff7a13d4b5dce917f17bc434d644a46b4b9e07bbb4f6fe79591dbe15f430d4cf49d839bc00cc79f8d30a1334687bfc0d5c83874c3ba2ee83d78667c2e505bb241cfb1418a4c5cf4c97b1371f457c20d41003034e48c6db1de8e441905f7f393fc8e88264f436d369fc6223d060b9e992023b0a99af9f7300e7c04252f10efec5dfffe4b03814a09e5f00ed60663983c6a097eebd6ff0ddcd432cc17953c951a8bd519feb108903c701e44e8caaa0eca8175c4798e052314cc2eafafe4e068b1c27699b3ea9cd00ae0113259ed954789872cf71f36fd7fb6f97bd8ed989219aee9d2bc77b4c674e402f4ff4f342614fce34bc093f9ecabc362d2000093a3e7f1e3edc9299279bd68e225c610de4dbee64e29c73532dc569519756382d016ed0ce4088743f502237a1400eb72c95ada2ae33e55746b3b4ea9e02e504af72a137837e4e6362603e451898256287f9c910dcec54204f035f9c76a96692780657f1ae8a045ef1382686a0bf8977061516b0f293267cbb1effd0c21642f83d7a134f9342b5e691bd62b43420372997a9764d137f39dff39ffe4aebc1e7ad55e34c386ee955806c9b1b3d305511550bbec18610f50bff8fd17ac28f4d1f0c21f397a5f82bd834096d84c6318cd9f789f09851555a77597be3386612f366d80430952435a570113ccd92a4cd0ebbb38f7684731ba4a5ab29f0e856f2e48fb3f12452578ebe82a7212be66110603b4d7fec0008f28ba478dbc31b27f6b191a57cc990724505248b2b71523dd197de31c2569401943d5087f2971b6126f3059c87a4adbf304f92d63cd1ceaad4e57bec78f668a10e790c645634ec0198cedce6796bab1dca4849a6d647a2cfbe6611b0bbd35e003863e8dee82813f80619a2b80820417d8d469d9c54a88707d6b4e1d46a1fff88757e56870a116da940201aab8feea564a773e25d39442b5f95dec8528fce3e7bd28597382f13416f5e681439f94e3c6df3b730128b7fd41b0fad01484b11886a6624548a820ef815d0e51144372a2cc44aefc5e73fe445311f6407775a8714bf0bc3a65d9c28595c367ec3904b0672ab224ce18fba86decc6d66dbc722543c3566e6035545adf1780dda7444e07ae7f97affb3be56cdcfca4167b9c15474005a39d704584b725a9327d12e6d70704f951c80acae05edca3f231430131cf7c0140f8f151166316149f7fbbb7b00ec7e00280bf39bfaace1f04d60a710e0fcc1192e236c702b8ab82baa33c1c3666951c9f3376747674376d9f3db982e949ddc75e0277d28149eb53c130bcbfb230636163bb9cf77485dfea84fc1218698187b080ac655309078ce77658a88c0cbfec8219ca5367342a30d3f87a121572af0c9050f7de1432bb50046beb76a13fd401bb966e19d8a8d4d94365edbcb8364d2d9750acb2660c115040c80b687d8ea1300bdba83508cf2987e56b8456d10d07148b25435f34ac3fa309d7c87ef567419f7a5f650174ed72eea1fecb72aa884afe8ec596b44088bef4ec1efb397bacd290ff975a47f2a5bd2f643e559a819d9b8cca7c07df966c0bc13b5496da293b8cb6cd79f63357c83e4cf2917e6dcd6a3fa063be063fc73cdb2f7a9c9eb0c574c4bf57145d429267891ec6b3f47cf750b0c715387492101631e2842e815c0dd4eb1fd8d5070e20cd0b8ac58f83ba02fe7f2c96f125f6e290d19cd1a92ba95e11c1dc303c210d499ede268721bea0c64af638762d2255cc4d7278c3b32e823842243e043000a20396bd5c3f1f38b8a14fedec7d4c272e3f2025a2da88c2a07c9e311eacc751362dd97988c3f41a136669fceedc190a1db97c7d98cd568b9f31995bfdbb67f147f7a0300c87a094e1a2532048ef7f0b140d92b218f290ce20cc043c20029ac308e891b0889f63bd1aa3d583b869c464976a4e744a2838435e16e8e7499e448ad0689d79a17b8233d0443778142a799d28126590773b2c245f1b6ed688db7418a5679758d85fca9db16eb08a57262dd36835389a5e0fc84fbd5928cad1c094fe8e65e46e939af68e8e28f7b0eb3ef86e768624090e69811cff2ddb082a75274965cc38ad12af58c8ae9afa2a4f6bf7335f94d2a832734c16d1fd7b34552a2ce6094345f9876a489382d6bb79ed11c202ed0550bac016f5996dba58a928119b3fa73c2b8d4ccc899ef760be9651fa02d79de7d04d4976037366fcfdedde86d1d9e2982718ba01d13e918787de8dfbc39f04a0ae8bc070d4a11d0c1b6092c676ebadd0e65bd6c9888b712c08100933304eb55099e241af5dc7e454aaeb6e2657a4c2b029dec6cdfbcf54d950ad0ca9ce57902c99820158732058a1fbf5322dae7b9ec808d6a0bcc64f1a5cf043528e39508d75d59c3fda9eedd9959992e92cadd490cad6181efeeb9fb8fe695b676c84223fc1b043e823251ad74f38b06734f0295103158ca0507dd4985780c8e275ef6575d1f05f0f96f40f6d74dfde3f116c9860ebb2f43158f088fc2c960adfd8d4d6e35c9c41665a223155fac733694e211ca837ba3f4732f93ba8c3e45e556927e6f9c3a6c0f1a7e8dcb84b72d7b890817bb5da6f73b3648c283ede1c68c207aaf3843dcb95cdaadb6a6b9fb4aee34f2ab381af69509c46a6cadc5e17d4c30aa9c9185d7e5e25d4ac8ca21698408509dcbee8027baa617dd6b2b260e4eef6b5a8f01619c960bff67f6ec93d374cb9c1a77f4ec7b2ed4091be1931c61b75ae515a4dcab5ee5c316886a862272711e3d10e4d67f371854f75345868272b610d23a61431d947286c326fe89cf63f2bd6a38fb6be024d430bce84cf2ea0035dd0f7e9d0e54798ae34a2ec5260a258b7f79e02606590fe35279f099687b6d0a91ba68b6e10f8c5939017f68613d1c73ecc36971e34149e637d9b63edf5a3f8cec30d416dd3c10318ea770508e05129e7460c9f3f1a527acdc9039109462a1ae1bc9710dab478ddbbe7b479ff699b164bc430a9443f9834c2caaabc8f6e8dfb4655f43f3e06c4d0173cbc3fb31970abdc6c2c913cb89395586bdf2f9c4d1ee71f2db21a2ee5be09c89b6932bff9d1003fe3db86e3c0cab062ea02ad0c189e118c7c6383b1693997feff1dc7da5291e515d1c1c892a483ea0b43520a4b337759d5c03863d264f0ee611c8b70529986d683c244f2ebc6fdbbcb002d95d913786a471d75fecefc69b1c5f48b0a4db9d9f2980ad3f5f92206fbf54d709a709e89804fa72ec05b4afe033879348e51445379d311fedf0b9646b4fb6689dedcded4fd64d49a7c9ffb43fc56e7674a51a2a1e8cbda8f07ac24b44e5d8f732364b53f714faade14eb4a35fc705121d9184da20a85e2df09554ddf466cd359bbeba56fdced4513d2b3285ac7274229e6ca41e6d4474e62b6ae13777d4786c5e70b44b73e6366f3269e3ccd4355094492fbd7a189ec757f38d45a74f04d4933723adb090617dae8cce9d074bbaca7ae2ed12659bb401cf2d3479600170b66395af0a7c2e4b340eeb729731c8f80c2622951d8552b2d3cdd3aaa75896107ea218d1b7af4a238d6442a6e7f4c65d300d357468cffbe4936c7329485f1b4b26e919d9617588df02625927ce9704fe877b3d97e8783c4651f9afef113c21ed9fd52231ca60ad7b4d574caa3a4f05db2b75cdf1d38b1d949fd5bbb3e5123d397c2c71add5f8231a07630baf4eff9137f8126e59435f8af99ddbe26006b989033dbb7aaf435378b4a415d8a7d33032fc72aa1d502952d246fa6002263bf822dedbe462183762df5ba40ccb6fdf27b82b4972cb9e67f12bbaf2440bd4b4f5a8555aa2850bfb8dad09138051112195b66756f8dcbf0d202b589854838cd013c13d6d69f080ad58ddffcb3fa9dc8f50028568aaa9190af050d4d3b605c011af950d95dbe7a7b9148db9132866ce76e9393031e7fe08d8987a09efffbd7ced07534c355861194c2a7f6b70a7f045169d38a217899cbbf4606c02a502ee0c4184ab0d7d8c87da2a039914e289020c5672a464dca0b8da245e4e92fa726ce58d34537e32e4392f9b77651edcdf2da941ce632641227a05d5a72d7d61f522e454328380b34e883c3f82928da281540c49d221a026997f1b1ff67a30e8428ede2afe7858bd233d7b151471c2ec612f817f3acd88d29958573eaf870b78a14080607df468cd1351fa031d78c9ef9cc8f6523f199a4f8309397c140e38640d5f795798bcff2cf873719940c33b32f300967533fda419578a40e096a912cb1723fddec31932fa068523ee398eafcb290ba8f33334b687dd49782c5b5a238cab5c9031f158c9bf671f91cea4bcb8f19bf0ccaf45bd4aa60aa7cff09b05a6d4a0ea276e794a3f6e650bfb99bd5379f4230a82bfa85b94739b7645bfe7f1a284454297a69ac4cac782c7b47eacfec10c6489f861ff16ceaf2f82e2ca304c97eec6f04edd05207c91860647fe1b89c29d61f23b8b23b8a0b3b805847b42a4adb3b70f6b8ccebb691b2d2f62e32d694acc579ba83d2b5197fef13076c0786e86bec2d21190c575ecf18cdda0135d32998da030b22b29b4a36fbca9de2b8482909efaff218eea34ecc9645d1ae77d1ebf8f9531fa5bacf7b259a107f7209d9df1c51a6f313528613cfa7833c5efd98d8d9d5471b6c9def751e67658179fc7ddfe6b7e90dbec00690814df95a95a577f17606599673b68d261781b9d9073b049451687e35fd12a3d506c96eee354b71cc29f58a2dfc9e76bdc00824857dc3b47220aab7e90145bd274a8145d771e3a2de5669b5f98d1ebe9a877d5c47de51424ec286a8a101eaaa2d49f7d33f0b462a0fba7e2f15948cd3880ea4ded7e0b6c90aaf8f5c6e2040b2a78241679d63f27076449c65031b1b886e1d36d9458ec9a1f5fdb6f02483c4a6ace842167f0a89619b1b85cc9b6287cc716acc2fc751192c9ddb63ea5ed8de86879d8c1ee540adf308a67ea7ba443673249e6ef606a1ccd6a358f23286cbbf0ad2a6aa3304edcabca1bd402d9bcb9a6c2a5cbbf6b7be00843a8d56299156197c68fd033020c0d060918ffca2b01170bb791e631befb321e77346e8e1914f40dc44e88e571266c3b334771670c38ed1612efef026d757c338facad3a5ed8f572bd1221893af013d632a83202edc33f861c7170f682a454eef75fd22e32d8b63249a84f155099ba571421553f91c9233edf00883e7941b36d7e53b96ff29c427d052a774f592115408cbff4688fa6ceb8ab318dbac29b7f9cda09d2d90f224faf133d7ba6321ab6e06f89cf15d4b78c8f93c57669430489db22dc92dd908349ad985ded39673a12a439ddd68f1e705ab9ee414159d307321952e2ef578ffcd9383d3be777bbbf94b886625cca82be1972d693c290c079582cca6b966eb835793aaf2524f4a6a1ed83998abc49e6c49d83acc49ded4a4e44afcf07efcdcde3f0d120892b1bd29303ded8a0a8f887de06d792be48b42645a7a6091e28a524b6e1c1c37a9e2c9ed833d64d6b728975f628caef20569cfb63f7f70317cb422795a8735355fa779be3c9b0275bfbab122ef6c1ef247577e840ae11af149e42d3303d628d65622f11ccd8dd374d291397d327d80192d92dfd1c372fdf3ad4d33adfe333b1a642447b8fe0e574da02ff90a89f4f57c2fdaebdce9551cd87cebc01cbcc2b058fdda90624e9ab7738d5c25a783bb069b7f0a1a27c105d943cef67e271fae552ec160a175bfdefffa90589acfe1408723ea214992766cbb4d541732b73098165d00d71c2932fb06f7f70680b1e68d5f124df2582055c099293f9f23eaeb3728f8d57b4533d92e14ba777b9546699892a7defa9b1ababe5f59208d50fb1284526c3ed62c929cefdbfce227b36eb166ba3f2081670099c6e8b75519aa9769564027a21db56b0400d1812f382b56a0dd9a72e07937b32cf3a629a35e8a97575f5005125bce19fcb28483d4c24a9c32b99c64c1cf482f90e616fe6814bc1fe866583b900498401c1e9669c95f63dbceee4535f6b72aa5a22520a1dfce337eb7c0aee43fe7a0dab94e12c5c746370182f61e503bf7a0cb6f55e70c81296639bc7aa2bfce67181a13dfe777ac1b09700ec07d607ce8e35da9ff440b271ce34450f3e27d69ca8954b71b3cb461d4cd9b78b51c71cc037a5de0d7fffeb7cd9ac92398c532ae6d4347422dccd724238b1a20c981a9de44b50ce5f729467f44e5f90149325427ca1fc1cee8ac6b27a9e709de0c37760cd5520224032cc1f4218f8eb00c495f4ae81c020606b27eed6b0b9a26ccf48a03fbf700bc06dd7d03426b582353ca0522fb607a639e9ffff0c3c03c266933cecac7e928e820dc9402901e63f7613c963d33a8b61230e87071d821b668c9bc72e266a4b6d40011a18e4fc714b58be93f264bdbca875e9974820399feb6a78a1c1fafe52e1ee10274a9eff51886d4a0869cded3b5400612e9ce3d38487342043927561d4261355ae0d067a1363f1eb35bd30705f72fc7ced702f8c907ddb756d566a94f3d60c215665366457f0d0edab6dc979b3c9919c9c5bdf00e0a6edfaa3d0846c421261f92538e7d1555b2896008e97a9762b9a83a5372b9c6448ea05ab248c8f4b7b50a708fa57f655a48ffa4ce334fe19968cdd94d7c3605e390c94103c0ce40901429a4ee4b63e36afd010ad1060e2c527baa49d45a52ee759c2f2a23a17326e79cc8e2dbe0680e8a1e081c3d3102ad3e046d81694c00a6a59500af402115ade0b69e23c35c513f29236e6345127825136880ef83ddf7627cd5108ddcab672b26b1812f7c5d324ed87d91b297108458474b3bc62176271b1961986f8bfd453b7e284a13f34655c06c4bf57cf4bb9de60f1058016cebd46719a270c23bc5b8fac2b6b0fd793f1948066596dd8a54beb120fd14ffaf9d6f820c62e01040ac99ec71c548be8e0723730596088f77cad02770e144399bd9fa2516765a87f3912173122e9d77d286c6dff8e473e67a42db5a2b18d7fa0f698e8e5d119d04104870985886d2408417d606c92e36d657147945aae6ffb61fe18399456c53639425b54559726afdc128556076426ddb177874399f01df653fa79596b89177a19eeca89a57bbcb1b0709497f7a808e612569b750eada36f8fbc917f48d8b398d9390a416e02de6f228033b17529facccf92de699c43c8715534150160719bac35b373181d23b13f32b2bbd84f9f738c3688f6f6d9f1edb0856ec974ba797d892a78a66c115ada0ac34bc8e466d483fa18614f6f905524e62726a4fbb29367577d7b86cc20d324161d7eff933011722e4b258cb43fbf1847e4e43d4a77b5901734d9507e126415d56e0fb69d36dbcff527d63b9f9a54ceab8ab337f58c4f383e1a3b36fe2557f22d44932f57970394f90a9b6578060348458b5a76754bc3d3313f4d2d3b87050ada14337d8e840519edd4fdbda21e428e437e3255535d29048d7642ef7adf41427db7063d9ac2b418c26b3797b932a6ba6c9d8432ea43680e76d668503ac5923aa7e8fcbb832b3cff65398cde5bc342f371467bf73fd19acf3b3ace6613463d8b2fc7533c9f2d6c2ffea9b8b89109dd7fc27ba27d7eadc667adc1f430af611feb4558a162dc495912f2ad6d8f3b6dee62b861f8f3b6d4e2144f4e97ae0c8c5a461e056c0b97954a0c533bdda1410f77203dc48da5b9162ef8a3097e9df32fce0f5818530bb73bf8b05bcb566a4b41a09c7092b6c3f695d5bf41dc624c3d2762571f42c79b7bf819d1d0dbf36a9e092f3c6f5cca158f4aeb6e48f6e535ba45216f8d175376659e453b086b543364a3c6b00fd874b0f580acbd254c9255dfcd2329fe8f73e050921d4a3a826bb1fffdfa1a54619616c65718cc4013f26812f849f8e587002af74ecf998ca75b1f4a3a336c5575b465bc42d6204847b38e6072c67d343e2560bde0e17ef568632088305c32a336fa6f0f896c2e6128dcf5a6dc784d76d8e836ec695418b1d6d9dea7ea6a376deac8e5c6c8c6d865b80f8fb94b18f3b7df466b2ba00c1782073b3de126af9e4f7bd8f0096e0cb5b13ee2f1dfc72d82a3bdd71f614d95946193446c21d7f56945f8e7e2cc3fc7fdbdc53de814cdde3a714209f6e3ce2c9392182278c3a47b901812fed9b6d35975f09dd651d1ce870d0b24e7e54495230147b7a99ef767af11a2a0397c9215c6b08ed9af51cfae2607327a7316d4a78a592594a880e3b0c4ad4f1b322d1fc4c384bcd0aff71e4595f08b418f386a1e323c07593f80f540162eb023e85ea2f5268fc99d44d819e193aa3ed87226eea9f7963ab34ede1d8e58673af845e02a8f9616d79f18443fc2a3102683b6c98407653a68d4f368a0459c5f1e01304b12cd92ba0cf9d611c79db16cc0ebea8957909ef303f014cdf9c42042b34925f4eac0cbad2713002cc51a90c5a49bc1c563aca185d3c0fddce39df60a00fd6ffdb2cbc7742d17ce540c564292b585c518d459d31bab020c5edc167214f0efe56fd725da62707529d29a62169c497ca36ffaa9de0ff7f66b2fb2ab6df0ff16397b4c5a694fe15fadf33fc28f1934b8022d2041e31c0ce2e4db848a5c37920cd83fc2cb853ddf4ebca455ee214cec55ede007dcf3598090b5e852152d37a78e5ec4c729d0ef527ba47cddd4ba2a14d3c8542ac6e49500cb8073ab64fbab650096f87ffeafa2e967d4fe0c541de34497deed6e626aee2ee549f157c093ecbde705e86624da66af531ffd58c30b3c576974a85da587bc18348120db29c534746c147e874b27d61ec9114e5c69422255f5742d8f984c30bb91208072d183f709e0b67d6c78ba1cf91156498a0342eae04aa8c46dd9e435dfd7d7dddf9fdb625132b28e09807790debc28bc7941ea10265d7c25301af67d4e6a49cda0adb27ff17b6b82d391c950ad1edd47b949e48456875d6855a14ec304ad561a62cb5b97c473483241aab9a820cb715c8a32ddc24e039f9acfc7e3a7898b7c886df9f8c8c5f89f99f81e552d09213541948a904e3a71f3b6787920c310ef3f7188f1adf8358e3a0dfe1664d2babe79dd9cd1a252f956750464ba83bb564baa133a9aa282bbda62b2550b706ffb065b3399fc4b71979a3f1e1c1f8f857315d6a6b896d55b83268697a9501a6fd19e91bef3f2d10f5b089625e451052203468b81ccb5be8ad5e16410b186c2a20ff45729bb12d3a6d3d19de0a08f787517d9ebe8d080c121221cd33748257d7460b45e0988ac60e1d409a6c7242929135f5230589aaf6f25470d09b960fa59f1df54d1964c83e9947b29004338063415ce34745372b39b08b390834237a3fa6509896a8fed52f4db308c689bdd1f115cdf8a9217892d3ba812a1744483157987dad098b77743aadd1ef2fe5efe0262cf355f12b9357519f16ce3c3dca8179e6293e6b2ab0179310cb2ffce3ce650107afe4623841d145a99e91c9f124fc4678b67503cf3cd88d7e390bde898ad3fbc9bf5d2a9afe34acb9703b69ac31e0de046cad439880df70b75eb16095bfc187b095a1f5cb552890843934062e4d83543a9bcfe9544b7c52a43235bd2b95bbae5776b48eee008f76c42efebf8f6645376e2a99d95e744e1164293c09404a415778849ee4ee891f0d5ba5e0e8ad8f2d3cd32eb435ca5cab1a4fcaedda6adf1b03ab42ab8f39e7f8235613aa3c948cdf5234202c88072ca8cd42dc72e4e17f324cb6debcd351684f0f45c65c7e4cc044d3fd3f5c7b17d5840dd56819437e54e9fa014e75bbebcc73027243a3e02d0ac41890d021f6a89a28d061c77d37fd45862af93e514034fb66d8bbba2ec5223ff8e14d7fa73b517b0a7537281124c5c494356c5a9acd2aa45855f04d75db23f5cc4f3f853ab049a3db8fde7ea5df298b22c9df6555cf1d7498b0202273ed7eac8c5df07c0711217e95bbe912326b243fb9ac3236d62c5d7c14667414ae113223c4a2e4bbcb5d7f88222ea6698c0fcb83e1f1fc8b07f130e29b156cce8bda66cca5e181c2d2a90f439125b16ffe34d4f0fe0495b7c77cb54c179825275ec9af77de1507763c6ce3e85222b9ebe2b94803e17a1d58895e40c7a826772fbac381d7506ebba8146a27d24c03e301c9929a8fc9213d633244abe9063004c38df053ab3d590ca3aa7c92a494b9f2d7b105c57583240437ff7d71848e547c72e8a56f1e9aa091454aef8570199a5424ed98582636de481ffe266681a878b386c596d4363b4102e28612c074873f4ed4e20b6080bcd9a9ad73ee9cd9cfd8d4c5d1a2c171f777b0ba58c0f89b31543fc2f74e957fb6fe9e8b36997cfe0ad7966663e5419294ab0fce53e6f9396aff7c2464bd22fc385a02d6d442c128230a3d9ed5b571326da6cf99649bf018de69123967b31c5c3d4881e9fb3314db6d71ba929acb71b1aa5dcecc3c5ae71d953e5a13dc3bec7633762cd06205dcca52414b74d8ff8417380fffd938dcc2dad0324178e40a10c691a3280eef9fb0f0b4c0e8eaa061f987612600eb6c03b1870f5b8cbecf877df52ffe1d4452e705e98bce02d6837c2bb47561ae9f0417887629ff2013d53f23b0dfd2d4a46b4bc58ea918886cbecab40e51d32dc5e382b477f6a7d3bbf9cf8715b3ce387ad0422e4de1ecc8a6014099f9e8a39b659900f2f5831aec3348464f6e67a6e00a89ccafaff6aad04f6402a87ba9447620176e601c4c69c489da04962f589bac57f7d873552afac951327d4bc3e873e9c94e07b86986d0e8d13e6cde266ce5eab04ab9c203fddda8c752220dc0c8362778804bb0c585f307d2534929bc4c41d106cb19dad8314bc7c25b0eca9d1fc3fb168dcd2d0ad49223083c9aabdd477b3e8171a5a54032dfabaf9f4ddd19638e20a10a96aeed0ca50a007ca2755600af4e54df9d9b42032e733a132ee3321d45922b56de6d569f89f24edbe2d0f95313ad068626accc4e2cf3be4558e23a84093869d6fbb7562dc65f476139e7ab3b7257277eeb90f8e2e1e486e6c563adc3e7dff05bbd4891df1344078974f8588faa9ac8d3b05fcb8bf4bba472946a0a6677f44f4a007c3eda976a9e8ffd637876a6c8e456a53f1336da426226195eb83a6409b333098b849e8d5222563dff1350e73880f9fc267f8c9d8c0bc8e14fadfbabd18b3c3cdd52587798393c012a908fe221afa85e4d1339460d8f1501d6662b9af7ed97497b511d20e571e7592ecafea8e8e8ac72abe0246ad8e71e71279ad8842ef27a2409e320ba4b465c6ffae5a9115a51998630981882c31c9bb72bdab631bf1b171350ff9e55226915d1419674b503fe82c4634baba1ae119d574922a5baf7d8647d7927b09bbeb65c3cae68480516ec93d0f2e956e2573cc633c632ad3e913234f6146b5944c21a4eb16115a2f3379d0872d262febddb0a3389863ff0323096ff9dcc982976296b4a82ec65068f5b6ea4bfe3639abfb65c94953f4177566928a78268a1c962d5c603262f253d2ac55aed099fbd4dd6ee3d5311511008b0940f2420197f90a6e57a2a684dbe5260169f0ddaf621ce342e25caed26903f791e514943c777adffe5ea283e4902ab1fbd9e5417450f9d550f03d21ce09a240ec277fece7ac77f62fb1902dd43136d62512ac4c9be131fdc1d0d11228aee544f22b2d8d97dbd3a4f0c9676dc24c2d8af77b175366619659627a11aaff2d88b1411f68feb8c27d32cae391efe2d88bd795328f2fc0440e1dc39dfe382396c58a6295d28bf77de25baaad0ec2668c3559e17a8ebe69bcd92e2ddde8ad50daa6a83df3ebc3b0bdacd07dfffcdc4491c8c4d4df1d6136b0bda76320a61f063e8c36e36260f878e9d1b79a975b9492da095c330c377d5835f85836ae7749e2d6c034a109f0c2af5e5a28fb2a6b266c9c287b87bb8ffabdd1cb42e4f5200a95caeaf6697a957c243d06af47525b336da8040e715cb9c77c1e281c4e32a8e25f2a3ea28a9ed8c6ca73eccdb11dd33eb9b09919443edc544f9b76d1f8505478268e72b5b18ee8017fff75779e59752f94b32f2ff763961621c5da00e0c6be160fd19d7f45162cd6da8da39520ceb7d48a11f7fa6156af06abc1447b183d3b2bb39140a20adb75af19f65f844303445b2b1045b9cb8f632220328ec8423f6ac8df5703971a63ee3d0d3abe9525549fda4dcbbbbdc95a03ebe9fe78a3cd558724e681dd292a7313f02101668ce25a07c8ecd646eb6d3e82e6742ed07a0836e6187c5ba1f71f7fe18273bf1d214eff46818d13fc3dc5515d1b0d191bf005f21da42c0033f6fa26596df1fa55827eb2c7c538779e41a610f099ca8b4b5a4e52946858eb01ee08fa17709360d5cf26a1493903cdb698cf8be8f08dac8f4562c3762cc657b7f86cfaa80e9e9b299dc1e67ce425adf478560e0db50cb1f8ee0c81b158c3d06d06f69a860ef7204d748b0b2db4a62fd02b6592ee02eebaf7c886e9207cd496305b453185fc896006514d57cff7901b338e499f74f3f66acafa67220ccaf5b9f30f451682a7bc0ee125f7c20be2bd944693d36a682a47fdd0de6a79f3976b0bb08ceeeec0a1f37746530a93470e2d8a60bf0358464ba5007084fe2ae0de2249d01461d755c226102636d4e44ff5ec6672ac092da681dbfa8f2ca978aebca124a738486a58a685b0d39978b8017993c74888929a7bf7238c05d03a393ef58707386292de602a2c353d3dcc41345e4636629567ee4960a5e32c98cf4c116e9fb7364c77b5fdf2e66c5d56ff8939cb532f21a75c0b2770ff96cb63a29ea2b17c3cc487ceb265e6078dde0e18ac00cd5af0a3a98e001361e72edc45af9eab6cafa819f6b519a7b14a9b32013d3644d503d49a18f2d9ba6fd4fa3e03265da1f282a803508867fa41c97f04c0cf793635493c0c863184365cbcfd6b99acb1edb84ef8cba12307ac4a00b1c84e57d4cca64f4d1318dcddb2eddf052ab47118ccd95624f0c97fb645c3e3ab60a63a3b22c4cefa4c09e44f426aa741ff390f2f3d933c34cffb94983d6703aa38334b946890092ad5256d873f228fe387bc3cfb36c8fe234a40922d7634bf4703551fa587ee56ea6b4feb2ac7e523a71f0a5265137e8a4c2df263ab47f08b0c3be33ca3d7253a0699fb90a4ce880f49539f9990415fdb1d1b418a70a09f4707dea413af5c620b6f6ab40d5c476535445f3073fb693aaccc9ab13f8b070e9ef06bd5c7c387343045b8979cf98ed8de65992346918880b7d5df9aef8ef14c7d850ba6cce5c811e47eafd6628c26c3f70a03124418fbc88778d9a7fc31e52b9a0be0bd4e9a2dd0906fc2371eae4b0b5b186a4cfba4254bd88b1b58d76d62c3043204b16ef1dd2eb7b1049323fe97182f006f54b9fd1968854d3c80641b5cf0ccbf53904bad894967356657ad8d3c5952667e60bd17a2210d1055c7b34559cfb6c22959dd4fbe27497537067a17c8bed7354ddb3a18299d9fe76d2806ad0045ff2f7d839f19ae303514dfb4ba1ca83c9339aee32b8341a4bfb8be742434e678302952750b4327b05c65af8d5141be4db689a8ccf381f226734adace0532ef5306574a0be3fa595702a1584c6081746d7c608e9bd16b89b4033e0a2ec6e18707630c3b184bf392b8c2c78ac4261ab49ee727748b22fa67054f0e5ddeec6a3185532314deb5873fcd6c76f85b314004e58cd2cf8c74d22a73a7782900033ca50424c60e979dc567716a2c128ad7fcdeca70d138bf68508f7b44f114057ab1e78184960ff00ee249e5b8c67f5e0acdfbdcf33839fb65b9b2414305fd37e242293abde4638903e6e94c713194fa72dbe82bcfe6e04b1b44a00937cc7d3071b04232f3e885768c9b372ee191b513e1efcccc20bb536ed29da6bf49cb3161d8eaedb17c3060c1cc05e4f9326bfa72096e95598836dc5692d79870eceb318c6baf9ac145b19ce5c3ed1e7ba67f0a7694fd246e530f24a911e5f1cf1768ce4b9712e3b24435e854e84d3c695970a88f7cb5502dba8f14437917cdb11ccef387a7723196fe0fb7f391a6b0dc9f5d27c4bce49abb2bcf88b2f63dc991047e3101de8c1ec61b5004993485eacfec09762e4fd2a3c1aa186cb91e56370d8242e1ccd8ae6b5b0cdfb8936ac84ea24a41577b7b0e3aeb391017cd1ff6686771e781d0483e42b5bc40e423cf6a049a6387d56c752fcd6c5869be9bc4da8d05e9eff049f8108ff859e5b4fca8de4b5e1a4efe574b13c7798e408e80464353f1d9cdd147214fe58a263f2ac86444d57cb23677a52401f9c9096a9d0ff0046359403d1b2c14da51975e0ecfa2df05e8e2881f8e55d043db1c4a473c8f8f80f310e4a475312e71ed8d2c95669c81a035655c00fb78a317d7b1ddf5427bb9e3d40ba99ecfe01f0fc9a0ada55b3b28a8e03d26d7a8b3a70534ce2de9ea00db5fcbe66f09509b8339054706bcecf6e04c9887822781c019068b3b5b4170821c41915d86469714c43c82bd869684ba73eda7c2a8ce63ed3704d3c72a712f17140f8fd3a78413d3528e9c81dffd6a69bd808152f42d2b9b609d33c20105340a482133ce8d3a06b42ecfd5d665affd56dadf9801fdeddac08035cc81f29c355a8151d5781033bb66ba282044689bedca203b5d793fdcdd66d2ba86ded86bd038eeb53e20d7cc5bede2fdbe1593e99083eb43bdcb55a58529ebb2deb686cd5e8d148d40b7984fbb20962a55d96475363034d5e9ebbf6dd5bd1a6b3e6900c6397194c54054ec78732b894bc6e2b2b48bf6fd24fa313c8bfe661966355cc97b4eb5bacb45b0a9a2aa0f4fc805c93d3a92ee8fc8abd9afe0dc7f0661547a40ffe65ae386d6abd924e71dbf99cbe7554656a47f15b8849f9e2ef4b689ecaf0cf13c18acb0f6c536d5657fab066cdd8f17949a8ba91fb5376a39c753c0486c99d9ecd8f6efa2a53dc683afe7d0bc0d89edbdc345235c55e52c0d8e48fe3826a343a93abc617396cda7edfe9e8c071ebcef60088e559f307e0e9bf3813cf1d7d3eb9c10ae797605596b710ffb1b867b656abbc6dc7f2676f95b6533fc724e7c6f97c3e1a964e905a60d81591a89cbd57713734e5d552f3ab85ca3a0a18b8d9510642167fb562ecb1ea8afa856b8c89da049be64d8dc4bf65d58521f796261a774a228116f704431b22bb49cdbacd18b5c7fffc16e73a906394fd3ab242d1ebd588ece82342010374467c7288d59251a1c44c691237c5e283b2763d0afe361e36575c7109132f67eae3b85143e38ff09789d65f63686f902824ff130ae843aaa2d1f234662f2a6cb501fd37c362127db75a2d38350f45b9c6f1c9702aa4e4ca836880a6270087a8a3153b0500a1d4958dc8a52c1796e61e2525e388ee248e3f41842b0d890026748ae6b3ed2bf2a1c0a98a477ade8a8f423a0f4a3b25ab52caeaa0a9dcd98ac7938bc0ec31a463484c12fe180389a49f995b95fe16037460e753c6c629c1fa59cb0b809de953bab681049ed8dc6bcf6e1ce4793361d6e3032efe578655511d8b7a9c920b1820488eecd3c043eb0ead7e04c228bd784c375e845e2f2e33911c96b42ce39417431a52194bbf167c2341460efc7641fc119006fa4fc738e3facd7253cdfc4ead335b1de4582f31dbdfc16d77a67ffa8170f680bbd0c6612935be7e47f5f845d5a6e30fc821e6df3d69fa5201578649b5bec80224583deb6115715c9959c16369a77494632c39f8635f10f2dcee17a0476e303237606afbfce3766742affe2f576d455de012dd8a47784054b13b6c62ae69df4a98c1e3da2906a955592cb4ccca5e470f4dd96c4fb2dc8d948397837553d1b99c049f2f2375172703143724d5e342b7f9e7abee42e5bf0405ff9f546a6a1df1e7572135b02c0fa67a25670d87b01e567c5cff84554fab0cf2dedafea4d4b724758505b3945bfba40d10ac0aa7c41ef7abe9756fd4f76c8c71017d21984082cb69b30d73233e6260e5d41d967d0fafd3c2cd2d81142305afbbe5bbce973bb55b9a5e3c4d6a9fff3e043024226288a22142f41b5c83efd18d16d001b837dd996a8f03f9f1a028869d3a0f5aecd811ea28ec3edaa85d5a541b3b0348f71fbebb332f59c3a9b4a34845d3feab3e8473c1c3e69a066b17e656573e885e70f060f9d34b057f134d96ae81a575204ccaa3a0161d26af757e56d046d5e68e8128678ccf30d52e0c481c4a46c3ec4433552c458d4691e53712563f87016b5a47445eaf4d525723325e892c4a0857a66c3885ad807f2d2ea0584dfaec3f9296642943f00f0ba2082b65355b289b7d1a1ea0ccf49ee5c634fa1daed33b83ce2250f85e45d06e7c93b0823446aafefcdec45c5cb412442eee5c4eb8e0cb2c7e7940cbb5ca1d82e5e24727eca7c8491921068b04a26c70558ac4c1a36e197fb9eb40c42bc70d27fd5e0b03005e148afec488e8a1c85b6d7c80df8894ff8006154ffedee2dc37bc0a693d79c4997841ac874d6cd63ba393027c78883b580a7b0b92034556e6f42ae4e8928a19b1b44abb9d6dae317e74354a7db291339ace92a613ecf74500a77bccff4a6d637a880067465d5998537d0b83999103c1f9a636344d053e1381597892ebc73050b485e0f54acac2acc7910d215ad89c89f11ec7c3e09254aa07c3e49f45447d639b9293ad73b8c7c5f0468f83648e4b1670c7e5190cf53f995665a32aeb09ac1991f402b1eaaea8186ec6c92db53ed571711d9285d4534153a51779a6d3951fd34874a6771231f08dc8c69a008b6ec43c35de26f2ee1eb2e458144e847def5b1ca36775db98ae89fd25b181b03a656ff75e2105a6f167aee7dc0a6f0d42fdd104dd29abf052a88b62b129454d710a39aa90b6e75ef8dfd7a426cf4a2b18cbefc644d63ef05f351687e21b6afd40dfddcd05cf50064ee379405d43422015e96d6e9ad0643924fd40fb87a5d8f0d63bcb9cdda5c44e5349230cbe77c8ae763e3a86b9e17072482f91697a45754d819db947b46903f6e9d23d9ae36c3aa0d12915badd0696e5a96c4192f22e69d5223d913242769c75f1b7deb5c3dc5ec20fc20d32530301476e18bfc69291ac6d90cc0b40ba56d8cfd613b5f9604d87d5152e5dd7ee4be4fde12d3317fa980c090c43829e433a097f07ca1abb20fbe8cd56d5e9a16249b5c9dd4260e82a1d816de6de57f570daa80c4f8bdef1a9bdc19666d0d9281d6c244dffb9bb2d4c6cdbcef45717a807beaff689bdc8dcb2a6a68c1e14d1c0407568e7849d4c8ae7bf1d83198080b72f33486b1ac4601d7687098268a08bd1b270ce95ebaaa059b156c6ed9c22ad8afff4733d8ce93d3e353bb52db20511bf3201f7b2b68e929fb7320085c4b08d2b1fa46dbf8660d3daabd0fb9a93741dcf03996f89cf9aa0282e63b23f602df586599c0ca9195e629781943fe5d24a22629c51a6314a349bdb6636c1c263b0630ab450ca008bf4a14af9a0e2348fd37d4e7a5c02b1ab7fdbc740f37d0300a9d7dd74808e024a4846145a3b755c0a5bf5d2aa26c73d6c68d026b1129f49a9f0c6333c682e3930ac0b64482e75b9deaf15e2485c2519852b2209fbbdc6bafa8d04e7b85d75963f527084a824c3fbdae1d28bb11c55d510a0faae8000d82ffc437045f8d8ce4896d63fb0748de2e80272b330bc5912e199ee3ff703b31fbdd0a0e7a048ef73e5ddab294c180df274c291a7ae268e22da71ed66a39435b6ce09b631d841b36fc1cea2f0cd2e0f6820a844d9d43763ee1dced787195d4511e6249e1b969b80225650af2162972f9f2a76f3580e44706c2383f6e72ac46210a04495d4536b030e6bd7019e6c53240dc94082b83af1cc2d763284ba2f9919c2928f9a110dec1d80f0bb3029c3e03a32c47dc8ec944ea5e8ba6419cafcda6886b75b5986b4b9a805c1cf024fe3ce57205b8857c0b76ff42956fd6ad79cb136c1857bb4a9fd81ec75620885cb8fd890d9ae559a54aca76647bd1a5f9786afc3c5a7d1e6a3cd7875f3b2b65a49cd330e49e4e242462fb78cbcf01335cb2775f67e659cd54210d4b6564b5d37fe8a4d50f8679f35bd1784f2a43daa5735090ade57ea3a5c34631d652a21beb96bf69afcc8f6ff53352e44675aa5a3bca48a9af651cd7989756701925661b8647b9affe76663d6442c39661a4492346aec48bbdb318b2e0764cece175c75ccb2c95192ce3967582b8ae17da0811fbbed95f5044e69db9432ce438049740e445dfe8db362bb7aaa7213fa675e0fd4c654bb1e99c03f4b269fa65de56f871a08d6fdf41734b77f75c47c3b7a6107aa56ff3fc98321c0428405575d97c625ddf7f5df1269928287989ccfa6f7f336a37cc155f76f9128deb6eb3e21b80d050d7070ed27d22d97ed0ae51452a74609458f1833cee348e138edc6925083cf2271f7701aeccf94b7ba2353476f6ed5a2d033e1045408eca8be842c30ef997a1a2336e4c3bac78a66218ca3a7f15ad10a557eadcdfff7a4a1993d3c57bbb91d44df454150c4ab6f3ae60c8c05c7ffb913e1488686b93f39b0570ec58470d18d6e325631809e925ffae29b932ae5dafed9a21137a55b879ae3c21dcbc8226b5cf17ef3874f85e8574522adeed2d8ffd9a2108924eb1fe6e73328eb686062c09ee775e07417b931b9f479bced4cb281c5b1c7846aa9c47bbc212613992c2bd14aa70e148b4fa04476221b9232b86ecb4bc6fc36daf764bc3bd818ac09bcf6a530183ce09d393455c45b547358a3cd927ab145d919ca517af2a00bb4a742937a6a66edbc0f42e7ad4ac4208e600a19b9db3da6367d46a8900823cec8d01251d0e7668a6b79896ddcb9a3d4680b01b16eae9851143d10ed679a836d81b7c298936b5742f03508f3bd090c84ea278ede03e970e4b15e68005548f217f969df0dacfeaa3590cbea877ff6c02f23a841ec3ad1ca415fdee6072c5345ba2f4b095e6b956406ca8f1bd06765c4e0596c9cd407bb3171b68534b4cd8bea84ad6b60775fe54f196c198e1720d3da5e278cf460741f57e2ac3fb291691aa2142e90f07c59b3579658fd0c0d86a08d1e64e49db81e05531b5ba1c3f2a6549da985d5fb3051f53f94e5f945d695d222cf2655cde4645f9be510bfa983101076b206a97d2da0e72972aed0c60ec8ca41fea3aa047a7f78e2239e202a0ac5f31e7f1e06166dc25b3f6b44b42243a78012538d62b74257f1d3bcacde97a6f2f3db3e6d356d91a5da2350c5df217c1c3988049384234f794e78d083e9a16208ac0ef1e49b30992c62c0a36db8df202d816440fd66fd48926d56b18952dbabcfa9ddb7cff81bbba3787239cf09dcf14d0b9687d252b19f7270ad9c4c7413113d419a527436888fac34f318860143a1eb8d5fa0e3a4a3496369500fa8d53c147d7d39e0bd288372f0774ceab3f48c5e01b32613f71df936c0b60914d23fe50512adfb257d02e842a40a615993ca1e62be51ac4112f4f0ab3bdcb1c739742627c9e723bfdcc32be2f3b6bd44a783361648a3e7988aab0375ad5a272c5ae38c3e846eb1d36d64b5b770c0b299beecdba068db3222ecacb4c842d4b01c252cb22085f1b22fb9dbe84ae9aeed6cb6e3de01d48bfe09010463806a761748c92620924811f058dae1ce975f933c4ff23c654cb0e388fc098d00eedaa292769339d0fd82c34b4edae059ee19383e2bead1507c69a9c88d1226907f67c7a8343d17b8f939d457164b71caafa26703c75c82f2cd8ca3ad25f3000b5155e1411298f9f7619f13360fdb5a3e33d8cebfff485174f5eb1ffe50431163fef7e87cccc577e52029e052106e36e2c1b549a25ee15371acaf2ca87e4be213b5fc2165802aeae29393a82a8934522b8783010c47d1ec83d21745f3522ebd070f54f40bc470dad377d91ef7dfb47ebf52c2f3f359da4be3c7e594bc9689a774372f6d5b837dabc2ae30bebe1595c28eb85c4a3f3ad84f8d78d5461da64f13cb34d5ecf1a15e3c615526499270cb1b048c4a5f77bf17ab34511439d3a45d1d9e8310c4026487057e205afa6b637f5ad489b73758f12bc8b615e1d8eb94264fa067c331be7d8e41db10db0d0c1422d3a6fb534f7e99c1d8587df349ff713ba948cc258af26f5ee8f41cd81f5ba5797339c2ea079de7ecee0da91c2daa87674c6eb49c403d77148922b7c8be0f2e69ff8781738ce538e9fc14f86c5ff1eab830b198ac4810041074970a6c74fa021e6fd30f433864a03dce0ec64310554f9f2dfbeb6c093a1ff7d71a9ac5aebbe805070903d3b606cf9b1d560e36c64f727c51e722f1b247b6bace7e83f7929d43f315518e0efc224f02ab1345320c53103e1afbb0ae42578e63f392ce99e58addb7ada53bd480e694db4f09b1354e4ebdf0d570c945b9139f523682284144a979904c9be29bccda6d83709201bdfd0660f00b6b7ca447e53c4a23ee4c0c65665ffde4ad96c3a15453dc40c2c7325d7dc6c75d3e3df0746f3a015d729a26cb8da59b8779943f892c58bc52ccbba26b6b3d461b9f2baa4d7f97c25397afffd70ae355159475e0920d9b8cd1ac3a0dcf781a72bfc4b7575a7106d9b2ecc057a3a6fe7005af4e3cad9aa1aed89916ce986ca4cf83c94dfc0e4e22ec1b111b3fd3802064a4f2be91d0e3cef9a52ba7696dbdaaa08f950fb1cdf6f9d5197315c2a6954ed7e922afe7184ead62dfb41ae225aa9af2538af7fa1c55e3cf9550529dc041c3b76d9f5a29fed2154e4a6261461bceacd1ceca356ea2957b1660d0b93305834534fc1d1ca9284c8517dad273da7a0d6784c13e51feaf0a446eb2e9eecdcc033dd463d475ec5292e98605d32d50b983ca940f04aab8c9bd969ab69b3d7d4d158439b5ba1ba8983245a57aa5817f54d9c5e5fe800822911b02d5fbc46cc40d5d0358112f43125ed6dabd4ca4c57f7a2ec17a5db8d5fc65533c8a2cee496c426a72628071f85ba3ee004ce05fb7903d59e5b710724b029d60b13d7f3fad4152062ccce80eb7104b86fc023330176e50e343163c6d817bd33e20d9279e37be2916fa4e7f9581fe097d59f99b071de12f96ca745bf5ceb3fbaf9589f23f6520703086b4d0168b2a72aad455fcd8551c925b44f0c558db849e97fbb90f73e7416301da6875d37be5c59b1bc28eea1057d72e5bddf69c0ba9cbb919edeb9dfd22d853d1b1fa413cba2033ceed6eb534397b8fd0a3ac7fb664896a29198be8aaf1d8c135602b72dd589ae472cb06ec2ea84678f5413bd61e29b09c4c5a5eff722df884d25fad908cf2852fbaa28485aeb14e2113c1ecafc88e8cd4891952aaf975abe461c2e52551b085037003b0d65cb26b94266145fbc6714a660eeff57054e7e15c112b9c1d8135cdcb1aa50739a24a91dd9fc120fe5c2fc1196fc75deb3dba4205ba24db139840f64f089ba31ac24e823bd2c07bf6e8fa8fa897b025304a44bb533abeaf9ff2e9eb9d261b969420c2cb2cda52aa6cbf84ac2e4533a892466199659d19f00e045bf12745916d7dcb2a734011e90315bde1562370cdd64d9b134e73afa61f672f3df20dd7e4b4b93ffb67056d0bf1c5c51dc7f7256050f7167ea6645d1ef8dd2d36dce613f3c35be726d119cf66351891985a5ac83e9708741a4a44d46c6d92a78f16a8cf27d37269c74c78e540560ad304d042c082c16ea4a2fb0200b254439e4f84f274e300f78a23a162fadd7a36046da6d0b00bcd8aa6252fbd81923d3db11d2484def654d60be7028794c9663d63c1d6d5da945f83abcb4672c29d32a486c154768d61ca5e0bed945da7c708115a904a963e4327f3a6014bbee3c78ff3d54002eade6c6cf268468c4d810068c3958fffa4bb93b1782aaccf37014cae98ca09fbef268624a6b84ae8e64dc734261c84ee3e7d186afcfc9ac373bb849cf9116caeeb888226d24499a992f3970ddbfab2945fefa599668411e57dcf4a550e4f5ea34d5d5a79f28f085e33e8577afde67ae942aa75ae8f5699b1e2fd62ff0486c9c927fc5c8ab30ab95d69da108ee7c98a5e0b76d5e77daee75f082335fe2202012686b2fea70893f6628bf71f1b5a38c14628988cb8a434eed608a2067bafcaa535a2ecfe779aa860b919f60ab9c74c59f7bed54891b405e95cecf79da64ab1747d676c58c37d5ee5ef32fd9c760f17bbd522bd3806d3cc50c31b55d60221b668bfa7a169ce1d6f99dd0e5cf6c8b7b18b86cb62ead285a7e0398b1af7874a60d23f2972a694085bb58219236e11413ba80f9362cbc533f5a91149497502ef375ee09514c00e1945fb0edc787295e6bb451b600bc3ed2207cdc0611ffa61b5e79f60abd2ddf75f5a2e53e2d40b411c60d219225716948ffdb0f0ac417c1ef09eb86a3f7623d66f75637440a6216bd23ffe27d0c01658647c3a1f81dd26e6e0c1019e95be1e4cd1be110c54bf1d735c2799c1258c62edd4954eac26b671f01e11f0e3d24889a6f96bb883af778501b119e45f7f33d09ff45020a1240d0052a3cc5391e8b96165c37f42e2ac08293073d447c3053897c4d1b1320f10e4e4d8797a33cb1af308d4fa8ab2b9c07226d41327606c444f0e6b60724d1f6b7e35d5852cfae0950ddc607a898adc60f6c6e0180f3245a475d9d8541a51da910d630b70e4f1a7ee20bc9143483a84897f9099cd5acb5c6efdd9bc2a25888153e33b5f707a1c590b6880ee65fcae38d6067c022eb4926a4c922c60d0fd68cd7d14844df7a364e378d095016eedf583663d75c4e87a5366a1403b80db70a66072c2a26a6dcf2128b87a6dc379fc8e19a866e41c4fc4e8baae2fa25d9deb324cb982e26f589ae6e2c625c82a38f9e37c9cdd4c308e906105d6ca4cdfb56e61584fd703751cbb6c2e2b28a060cec0e4191ceb899b037442a66d845f6f8698b79d2ae4f29a7884c514345742ed8badbb07d264955a36cb4bbc05c41816d9adb88e935ef6be9c923e868c2c45785765af4d161bef18000b3f4f557a48a3330e8cced60b5e4f12ee45a068704a194bbf471e771d06f7c2ad4f6ccf19b99293d15c557f7cfd219ddaccd5dea24a2c45aa71784613b823dcb5d475ce4f9607f749add94d0aac339fd8ccfe63ffab499b68639046f414a9b4cca11e749b30faae901b660d87a9703c90845ad8cbdcd767134c0711526244232493a30af6431628eacb790c1b595e8529922b3408ae3c8176da7e2d6b58396c73d402d35ca1e4d114dfc4bc59891fda72bf7de43e0e806a07eccb8cbb9e066cfbcf0424047fe33916b84e9d1a11e8fa79f46414d23548850c80c9d1385b33e5a11e9750e6154ea72e4bdeef913d557f04ab4baca1c3874d941b44f466d816cf03ae938914224468a655ae8c875a68a8f75fd4c9677ec4a37f23ba8eb4bf90c0d6af6c21238a0cbb0f4751761134223b2dbc3a0554346cc407a585ed3fb1fad7f894d1782366aeed6670d2dea7b892e752e288aa776603c99895876dedf8c55aaaf2529bc52640a97eac48de60215fd159134963267f4481e66d36a6d5bb610baac42c5cb451176537dfed7793dc8712f1f17eb3e524211cf6aa76d2f5a4e3ee884572be5ee8450f37440a6888876de65632c05c5d889129883485a80c7ae458beb3881d5cfe4ec63f5ebc7de829720dfded91ac27c12fb65b25e33dc1556793dcddf89b98e78fd1e7d436a368f0bf0288538313226c4a55f8db44530b8733da9c93b4a12764ae0175cc1eb0f55f091bb408f2b47a44fc02dbe754afbaa84c399b0d585b9f357af9c34be53c73f85611b6c9d5f5324e7ddc481bd223fe7ffecc70b423dcb1a52f6e97e67a839fd1fe1b75ab57865bc86f83c2a750640f112277b936398541eee2f6a05069d990231450bcea6b3c2e3cca2df9d8240ee4c6e517eb77de67bdcc56cdc689452c80558ab298079bd43f5626cbcd446d2a00322021919179ee050c8c5e79ca524a481886fe39ef7be17b2f53ac15d6c9878218ab8b511ac86251e3a7cf3689bbc44343e51da49085f98bfbd998ad905cb5c394bd19d293176f20a46e975e82ce508a3c15a6d13dac5a8239841de7ffcf202df5e4dbc9f654c3e801d9f4e0b3dd8af957cbc2b87ddea90ccae5b0896ceaedd2d30bea8e6f5155d0da95d2a15010175945e0e93a18fb76508b14022fe9de01e3fc8e853256e8121e3c8bc611e776c4a6f51a9395214c9d50c9cb69fa88e1f33b70af28fb6fc0f5c5925ae3d902c62dbbbbd0a2afea0daa17113ef5d1975863d39022656a804ac25698f380fac54dff54d1f7420fdb6221fb149c8d5ecd43924f03cac1c3e808fae31f10e12062fee56c1a938901d2e2de1ae7812014d2f3faf0839bbc66e33a4bd73cee4a305be139bf7fc6ad21e0ffc0f2ceadbf87ea17f1432e11cba63aec7a71455a2f464cd4d4190e9dca3353e79fe5290055155d30f0d2319166dfc1f9d49a0fdd9f5b4485b447545a3248af5824c2e2ea2bbbc351739145a03504af8d679c5bb21fad8e148cb51d08fdfb90f1d3f2e10e28c3091f028445f3c4e165b227dd3be312787077cd7122853fc55229100a7548d4e43e45ddb71e2104258621e4043c2ea97cd2bb6a36ccd38a36168872951c44eb01b92e9e16368f09fc130097f2deea5cd8a5aca9c5638afc5d57c624bc83a51c8f0e0cea5621d0f4d32d40c8ed04770daf11fff798afc0df31fe567330d9ff1c591c80379bac1190e43f855280eb213800bbf560009fe4b9d9f6b7f45046c87eee0103e383ea6ca3abd685e87ed7d0f5a32b8376995b59735294a939cf4c885fa51c00d44c2b8716b20641507178be20a969f2f3a368503d58724db950a3277af70da6680a0a0c1a51ebecfc65af8484fbb43ca98b61cb8b9f5d29b0bd5b095e1586e34db34eb73261ed2290a29644fccf923b7ca25ee5ebda3fc25cbcba401de3cf5ce9360489645ba4545981abd5d67fb36b79f55b2b60d37821dc1c31bd99ce100701e09d7921b419b0cfb66a29bf941010df85efd83dc48b853ee7abab100223afd079881457d3694664b6d54503a9efb68771ce34f70e471be760e3537d76bafc4af796ab09dcf63832a2f3520ceba498cb9acaba6fb0bdb55038557f0443f7304069f68212476b1ada3d3d8498390c01b0bf397c8e43123e33e479e6cc4b38a57bffe93dacd4c6b49f524a644e5f13c5dbe17b6feb3c0f7096da05e5ce7f02b629610e28fc2a168eaaa0f07637c004ad9b003c605b393ce16fa407f5596b44c574651d24b9b7ddcac0526295a40b5171b7ce72d41647d005c69bac046a2d28031e2c32a8dbb1b98396b204ed1fe5ac10819b6fea75cc0b327370896db18ad28b90d6bc4af04681d207756ac5ab910e48d72a7089eb796969c1a058d52f3c1efb166d267f21b8881705548df77484b859281b5ff2a50ac69f792f24688651b9bc91d84b9ab73521fbf4016e88be3524978111fb04127a25a0bd3f2003949dc27225894368008cf19fc5808bedc87c0f99739e84209e38c2effec8dacbadfb93991731741ab4bbfaec88b076f069b065c79ed58925a1f743c95973198b16a7ed9eeb0a43cad03135934f221d0818fe657bfc2798ee35bd5378805aca781ea86830d19dbbaf00474c30bdb09be20c4f96d02319bb1a2b022cbc60eb31c7ea7458635aadbb73884bb8a23b494eda1512b7e131dd084ac153d02985a5fef1909bfb03dfdc9e6e96c77d048283d4cbefb3ade607670346df70085f250abb69aa40e519b9a500e81956a9dc112a1f051053ac0770a9734d804b8a19058f720105bdf8203291c75c8e40c14875c049b823913e9db77a2f537a1a4278b4e88fc91d4f5cd77785e5e9e7a0773a372cbc5051601b7af56b6a88f277a11853305d006a5a514d82d4b965eb7b9e75dc80046e8a41478e6423914c139e3ffb1c1fc02f5ec59d2537c9d0e418db5301ec3d27b793eae10fb705778e8a9fa8341fe5b9620570daa54eb108b3fdf4210328111133c1e07f264db7e83a028d239f9dff76cf7e9d544bca0e65d54d1da8ede27b653fe6fac6045e4e25620c7edb207d408240e4255808f08643424d6ad0d67c859027c3cfc0a077cfa9a6588c5305a41ab7b2f7147ed68fc53f364f61c29f476b9d02b67aaefe97a01a4dcb0462fff295f4fc0ebf99d4cbe7fa393463e6b84e1385e1ac13e94b5e076a6384052bf5fb1881e7aa4cc44b7333b6ecbbf5ece50905cc8f40e948b0b1b9cfb6d053cb1599df024beb4b6a202cfdca9cfb87a0f4ffc50cba4c25b4c1448fb7500094d8f84df410303b6c145797e39f69a0cc6d0bbd360722013965780a8ddc5179e43682bd38cf71e3f8a97b3c932690594278cd61781584f0f1faf1719d2ed0b47cd69c523c5a570cec33b18ff26cb53f8b955393ac67827431f9c51792cc75bbe104bf73e447028c8b1be99f47be3c10c4a6e7603ec8e638898c5ae5984963fe1258767cc45603416ae930b6819d559cddca5e5d7cf0775a6db96cf2933b049ed9867efe4699e9065a863a87cef8aaad9278ecbe2f381937712f68063da2cc77eebdef2a9337bfef2a0aba32c6d342fc2147d44cbfcdd0b63b74a39dddaadd2011133e96c8c0764572dabe0c609d279c7435f9dd936df301f1bd4bf83dd314bd4f37760ce403d56caf6a228babad172ba865a04df0e5c8fd7e04ff8374bf9202beed9051b7fb78fdf3b637a0080bb3f856440bbc496fe47067ff00c73d67aaae953b87cb45cfb55da4d2a4d76daeda28497d035630595b089c4a370a0ef67f3fde68f0e8351500f5012d3e04a4e6ae6418ecbcd2435f29ec14da973d24ec1378160b57d44ba288bf98253f57fe5b1a34decd4f602245effc90e7964936efbfccfeedf840b551abf402c8b8320abb1faf307261ca07396983acff9761a0e337b09af83cd6ac866fa4eb261516f33bf09436910929bea5e1f2eac07ec18b17148ea39fc66951b14d21d48357bcf12a5b864535045c0f2a1fbb4944a33a91f0278c2148708363a95475339994c9260bf289d352f125a19216142cc5c01a7e2d49f5b3d0ea8dd7402ec7f626402273a285c573776edd092b59d14694194dccaf475ffa5c22fb1ffe101acb50f506242f4e589b43049a4b220bf13d92aa0fe690bf873b0865c6d7180dffeb864c2b41d7fb565b93e5f04746cf2235b919e6598f54eaeb7280fef48d913b2037c5930f4c74eb18bb74b92f6db3e73829979d0f067e031866d9a2d640f1070c60bf42ef4dd016b1e1c2143fa6b7edcb6cacd4152a3408ddeead7fb887557b440066644ce5cf34c9e5960fd66e3b513b2153c366039f62af4b10df4dc2678c1498854e837f90796ea9b7e4bc4732ef2072b860701e141b8f0c9eea96e2f4e47c91731ed691d050b14f06269c3e9f2e298216f4de15a5cb8707a409dac4453e23e32ee6aa195787be2b60506b46d92221c6d9a72ee65dc760266ee6eeb0dfd323b2b473d9f4c393650e79b4d0dd44a3fa024e380dcc33670ad749789e4fc6b6ae35ab596291893c10c21f833ea7dfc080299e55da24d8a73698eddc531b6eccf729c6c7288956fdbf98e0b8287c989ea773bb54dfb172a35a2250292d4090ef1b51cf9e008160e3ead99b0dcc712847805d4f896e6a797c5b99199cbb17b4de48c7858447d9b1ce6f26929d6b04348db0a08e7902737df6483395fc9105c9c446f2ba891b9e4439f483bfc1eedf4ec9cb330dd38c4088876c88d740a1c703f52e64dc397178a786c627e17164b1ea44d86818aaa88cd8aeec5d3949f662acbf310a4691bf329a3f9a5cb674b3c577263d97ee97a8318006caabf07aa62733e281dfcc9522fa571a4aca5490357a75db458c4810387e3785a1ddf5c3be8017ed9d4dc89ebb06b63d2c60b816cd5e675ec831bac20ad2333dfb6d33978b543b2e40e84a44fdfd8623a76dca00516af586b2de2d2c2ce6d9c0b815dc51fe8d5499c3935bb4e29a07d80f2070ca21adb1207fec0e8f70babd4ca7f357980858da2fdd5d7b0aa7f498e02a592fa990998814cf2b18fbf46dd224425b86472f50f9b52fae5aa47d7bde8fcae672cf4ac0595f7d956aa11a6f38df8db5f2a2684fb8efdd1aad1e8a9e35b3e5ffb33c3f1661688d59f51fc17af0bb67e0f4277e29f7fbc17d16974724b1a7bbebd82836ae2d309dec168a3c896f796de2c4af7a3466bc69d4121b335ec5bee075da102ced25361417d4939abfcc3a8418b7117bf0b15c4ca51a95ff5efca6227d83d3b77a771b01db442bdf8111781de2544ac1246bfcb1742f8243b900dea4361f99ce35486b0e550e369aa0bc6fc63aa6487dd7f9a35920cccda9602ff832f10206a3455fe31e17c4b42005760a070d3fc0943ffea545c8348e9177569b6f864b430b0387f4bac26fe1afedd3c961a93ee61cdde26e02bbfca3dd11a539e5b9a78e741c6fe24b7d8eccfe95b3b12690d109be7b58e18c9dd4ba45f4830544bc8dfe2f5ab6db5c5dfb716cfc1abf7d962fccc60d102909210eda95ea08c946fd610a97fbfd73b6dc15c81fdaa0dfadd4a4375eefb16c2fafa8666ee4ffd7ea13e65a7b4ce1bb79c02cdd287e19915577e017da8d6a693056156ac355b6ee5709f0def10009d21103e27bcc3f2c72822f22b808ac16c32cd252026dcf91c169f1ec6692fc38fc0dd8a8c76ced341ac89e117acf4535dbc009a66dd9003d142a49bc46a5451c468ec32cc720596f702cca809f22b65c98c601a8a7b826c27a82ab24a1be5bd103577b95f858d2b45a0a26fd459d7a4880b41feee59e8d1ff5f8f8c8505ec96bccc8f31718acd3b5c4eb2a3d4f784c3ae600ed6821e7a165f5a6686a5eb9815900296e4e57aef0764331943cf68212b8d7dc4f93a33f50e5950f79e572cbade6847c548a750625f01d0e7f67fbd83470382746766e09b3ed3a4babb0a3149537625a747a4e55aa43816a1fddf6ee35e5fa3c6f1ec32b1a3320fe0baaac5de707fbb9cd4442e4fe0dbe3c1e56f4b5cfa516071bed5200094469cca55bbc43842d4f957d349cd7ae8f69f31180e4d6edeb5fbc414af8f55a9020a8fd3fc35badfcc34614e53045b92eb76d43224ee7e932eee207cb1f902bed9c27b8b04d9e8fff77a94fd867136f82fb5fe3235e4537edf3c7c2ab86f5af9cbc58a377318ee175bffa01c11e090cde00cf66f161d9b88bd69e0c2689f38ec7998f116b38d190c66d2899179aeb7c399267b655d7b64593d00ea63245679bdcef896e067905b707c16276307f338fe45ba3c4ecc15a31d60974c069cb27d6613649385fac7cb5836d7a47fac936797da905f8ec7acb0e060804b2b22d8aaff7cb1b7c3ea5823babaac02d333fcda342d3cfbed1da0c874843f42db1380e91c88ee0488d012405ad55e93adabfaeeab58069da3adee8aebcd5a1df4f236273ab363322137f7a650aacd7f7a468f41b66196c72eb0b07b9e9900dfc70b6cd6376a282b42bc8cfa16298041c62b2a36b60d0c6c02b70c82c760861c0bc0bb69e734f58dd41cb58629a15a2a95f3e6748b1de176ffa2de6ca927c67f13d105dba77af32b9b1928b8371a2add09c6d267775eb241bf2027694858ef466643f229126d228ca45d48a2e451e3d15b592fbe11432f1dae73e18db20b76c196b580d3b5eb886b8dd0c27c1781aed31d48f2eb4cf6d2cff5990c50d8036ad674441f5fe572d43593fe4b1edc4a1b81d47975a68a1ab61038138c0ff5d7a4808b568fd69267358e7680da87d37622cfdc976a6ac0a6bc0d19d403888715ed4befe88f453c5241cc2d8df0593459ed86a2c8a8f7ab939fe59930fa62da5762da5c37de3ee8da637a671fe2595b0ab5c0262b59ce174bd62e43c759a31c984462a7d99e543298efd7dc5888f4ebcf6d6ed6cc5bb82f0b30711fb7e4380a386f2c77b0aaa367033055740d7eb580595dd2b0137c40e5af01a985aa49702489ae16583b8c45b323f61a34a0d4d0ebb22c05bed63034649428c8b38052817ad26c13183233dd5ac2f982049c2b9b070e8172d59e3128977ec73339a01302021712a7757b85110b3a119b42b2380f17c6a859b4c91ff2bbc68e41313c1a6f7f1ed86b1edbfd083433b2a72209194bb63e744d6489df2f082d95bf4d1657a9fe82c28ffdf079f075df659ee20338115b80e4b6f1f901bd5d0f436fb187ea73dbc4e053da63327a1d86dba9ccee324865b4f35072c7ce3f0ff195849ada05f659a7fe8d4651995cb0730ca80939e478bfe25daa5b1e119db4860e27dd3ecd3ecf576d26dd5c84ce899e74ad4326ac510f93faa929e0c8810fb8eff62de384ba59219e9dd0f8980e8792bb7f182220247633ea02555646daff49117f3cfd5ac029afdd82c9c16adcc37549185c1f8568e16694c399dd035d88226dca9dcacf065a1f9dfc14081dfe66382c272baddc37eb9c36ca8d43c6ffe258bacc86d4132a58685c4423e0a1bd7e41618fcdabd7ce4a958c4c87d0297ca583ddf55600b42cddd277285654e267ed2a10e668cbb410d5b0ee50d337c7d4f5f6b1a3f23692ef758ad0891f9234cb655e83f7823eec040d321c323ce888c8cee4b3acedac78bb9b8686dd6f2c86c077a5b340544a8d5344cfc920e0769e6154915fd985158dbe5c4ec0ad74cd899eacfe0b75ea9e5dcb4b38f7a0571eb96a380dae656d4a88869458cb18a7f108c2b349a3fb1aa11ddeb37100082c8ce1f12e28f4826532de88b40ab64f2a86330f9f9c896e4510d0239d275a059e7644ca728d8cd4a8f0f09d1c98e2177a8468682f54ef1be6b186d4185aea15bcb4c80c5a3c22f425a701800344f84a69e57b373131dc87f8ec004f1905f90dc9d21e91face24ee8955d051efc706567085efe9e814e5e6297f5c96f2a5c693f25c3f7ade40ecc31a9fc314bee0a5bc4bd4ed708f2ecfa4dbb533ce6ac0f0a4575afaaac82756e8618100333e445992ea6916c0deab57e0f5d4c7c35ac5ee105fb24f46899032c5a64511f856818702adda5552ae3e67ec89954163defc1bf95d76c53be2f1aa3839379a14c18ce0c9f26413a39f09d1cf44e6a43f1556f5588e49d6b87be7e90f8ce8211b4ffc26b8f951f8b6dac55cc3b157d00d733e6c785561dfafe8a6ae911f112206c632ed370c24768eda3344b74cdec328a0e27179dad7aa59bb6023f63b4e6daf45fefdbdffebf9b549297ccd64d4750eea7a28ceec4ad0afa9ff41ea7b507558416bd65903606cb9eecd19ddce65b0e143857abf3a01abf1c1f74e0e2740242508cecc2cbe8d37a9a8d9e4f167cfcce7d60e32e8d70f9223af1776775c62cb9ddd443fedbd418c9c7be5afc7671ca50b236f0cb8133a548b0d8e612d0d49202da2346a63357e0a2aad216052834bc059af16b1f39f85b635cf94b7adc61761a50b8255ef0a9b41f1f002a639df0590854a53e43107bdd96fb074c7684da77fb86d899dff36e9b511a5b84857d2519265db2bc95d735af84dbcd252420792a2801217bb9c72f33ab01e7e8e3978eb4c72c1b58736c494e326e1794c14367362f922e2d3dd35b67a780619bca3132d634cc58537d1f588031595129a2a02e3e8821f4c0b7106697314c516de0b41b17a33991e8015d7b963798a7a62c554f97d5fe866c11a16b38eb6ff9a0e1df9183e8ee97efb3d1060827574ae36998261203956832c2c3165b69424d13235639f9068fd30095a79442e71db776f51c6487b9c62ca3f12fd1274660753bbe87765f0eee67d6acfc01ebc9701516e078bb1ec77e0a2dfca8625baea8d164b7b739f18f243a6b2d361b24156d7300ccc86f555a0c4e5fa6421416e616cb4486a180e306b8e4250f2ea2352e3ff306526a5801f4efe223e8694182e0ff21946e3c16d33f3fc2cb78c9440ecb3e53f5c8cc10e309ddf78bceb9a0711bee5f7da4846c5d648f396fc5527b2e803df50a6ec31f6960d16c017dfb00e5a3c728006b1a9becbcb022c09d332593b5062bce82621495003330e68c5320a27d822466d68a07336047b645fe49347c8df4ac27a8fb9d7d847c4d06091cb3e2be9ef4dd06be1571d1c8a6956354b1562362d60a66f1f5d3f85fc02f84f33956c475ba6eaed482bcc61ed2c94cba389c4a1bbedd8833b61ffcceb3f229a3d1fccd09c157bd5ca1d75c53196837ea9d203ae3a31107b5599391248b30c2538c0dbe4deb09f3d358fd66084e77d79efc4f5b105def47d7ed1ccadc52a9c36991224ea8a6ed1f4c2144fea12bc60bca6dd8f4123af7fa0cba869dbffc9de26fee486c9a505befd7dae9442fc41549a040112b9e98fba7260aafa3bf1228ce279e8db6f3cacf60e725637e9d091973bf92e1bb8b3842b73498da152fcb4a56d336a3e4ee086ea12af533287a345431b36a872a1a445bb9826a519088414c85c5432c4da33dbeba71bfb1265a9b11489843d651c7dbd75c9a5cd8628143dc35847e27e0c87570fa117c8a6f26a607e296cc313937fde5663cd5a97e319d09ea0466cbae283018d85eef6b53f84b46c7329e4f4b6647f72b616d8664d2c4245843a7e64ddea9dea0f1a0b0b82f5d16732f4c919f75f322a8758a0bedfe438afebe7f969815fa39fac54ebea970331fe05965b7a0f3448d090819828f4479b8ece6ed4a0d8440a13007c7d2c7aa87cade15005ab7757710bfc2297c74cbd1627ba18f785f0a98bcb6243ec51ce939ed18d1548e3f028af8aebca42108b981a385277fbaf5846216b5bb6006159bb1a7c240a8929fc33871df42e714cbb318c9acbd2dc58a786d8962b8b39bb857b1c789760dbcd6285aa109e26514ac68db57a6fe8f8c9f5a84a3c44458ae1e147d729297dbe1aae0f83c3e575e9b7fd9630e51bf9f9c660e94bbecd27ece29feb1c6af16c0a48ae1f59c7bc67b67294e5d0b3c027844d89bea37220f82a30135a4bf95bac8449f6e0027d853870822318cec5e527c0201426e0824c9097b23a0ff4a6a73dd7075df24f129a50448d26bcb0d4233c1da12c0196b293418a4e3000dfd5690fe4ef7e0aa3f4c84e0b537ec6c325a884e461fc80a0c5ed06df6db91b4b9607b3ae8938ac6b4acfe24524ef9b0cceb59d77c737a331418487d5c9b95a8e2a3e6356f52feb71c9e377ea58a0a90b3d208e54ba90bec50a36e9fe6ebc60776c9e5adac14840264c2eaf4d829e8dc748d3053924431f4cf8177323e16fac1234097aa28e316e67dc4caaaf7258cfe4034becfe7d8bb316bdecff49582458b592383f064ed6757086cd25383c2428cc3c193f0c2e29c862bfb0404b4b536a6f6f360b4ccced9ae02ab6636bf8b95b21a3654070d8e4d48c4a13cf9adf13b647d4e3b8da536c780fb300e66180b3a00c3766f5070b03594142f60f3061a54be305728397e40d251f7f18f94c3d139918d4ebf443e1d5c73cf3f0e5b898ea787a51eb3caa79d1677ea15d0e12e0bcdaa65fd585695429856cdecc3380b1e7593d57aad421467afccc7fa6e6061ef7073d619a0d69eed6f18263092fb26348f98979de2d33a60fe853bdb98c1888f0772c91c4cea37e358850005882e842db0096030b460cc05cdd83e7d51fe730366f563870f73a41a10874722240286411fa6d8c88ab75746fb12b39296ea01d3fe80e12d0ffc1a2feb5336900a5bb09801e6c150244bc4c1ce058ed5a5fa36ce2eb5fbfa9586e6b30a2919d41aa91a663b88865f7629d887a201407012f36b4db792fc1d6691ea69f0aed1667680d561813262d3c302f4c64096c272b77d95a4a010a5dc3e0d108b9c4c21aaa496fd8a1fb3990785eff32fac57f07d70a0292346993c6f24e6dfd70ba5ec6730c423f24d1f6cea631e23a1dda7b707ca0432b01a9e0657a18e3aab8769099e29575b86b2556e797d52b0ef6f6ba17804c36b7fcf03b1983b21b880bc2b4494994115265ed26346e044e326fdf7c99c2cba03cd66215dfcb313713e54ea60caff1a923a341814022fd1385b85ce5e8119eb15d245a226b242902414776672b4f16ff05dcaec140ffc9f6bfc44235f0b2f050f0dc44edc7f3d7b7193804971416fb73e469166ee425d7436ec9846fc8de58d5e5dc0b584cb3a86bc13f140257280c44bfc91ea67e08d90fe0c459dce3415ea5dd630bd3da70ffb3dbebfde4e45bdba2a81f3401d5ee023029f3701cc37dd58fe5969b4a964d5870d34b51adc0baa22f62af18fc3e748db3285936be50a50ae5b9fc280df7feb5c41076e34ebc039766c3640558220a8d97ea3d2787cbc2bb9a4e03dd2b704e60a49005eb7d442a0afabc4d5a0d437218af72ab36e33b51e3048a79d617a49bf285a2942c6049d45d3da01cdfb3cf45bff924f1412ccdc352ede013f27d7d55ba1aca44378736ddc50b4da693828d6e50e28c2395bb07fc1c14f3e922a35f826523e43cb14594d2dad40b65104909acf01f3a997ff7d4d5d0625ef85ac3073d01bcb623b4b15c78fcb824f976043d148133c76cf8ce6a9b835cbe9f0335a73348b906a839e6464384dfb7a2f2f1ab8d55bbe2fc5255b88dab9434220653342441f124fb97554bdfc770f6c8c84091533b17626c0370b161ca36e62b646fee682d89a7510e3f6cabebdd9e74f9c55f75cf16f92473c8e3bfe7e8914460509767c7a2ac6361995d3985608ad46f537b5725ac2f4fe57313c5db92c05d36faf220d10295ef870400d00ddc2cbbc294c631bddc935b6b033a8169d41a4c64c7b4a3f28a9e366794c2ea248b231e85935a8dca6af37be9ed267a29341c4882111f97e8a0277d9f86e7b6a222529826457fcdcaf09d282b862c284451b46eabec40b502ddad8c683558977a5499e763c4c6a26b694d9c07efb877fca1653d76b7eb4af299cb327ed92ca36ddce46c4718ff96c15d585596bf76490a8918197d8130b9e83f91be1ced4c248d802aafb9aa8884ce333e98734995f1c4cb28cb768a73f7056598870d42951b8daff6d9a1d0811a585773ec14b324b81db32affde5bea0d171d1866c31cc1b8e6a64e399605225e0e37909b2d58065b6a6184c42e3fa79aa412133787e6ebe3ca4a03c01cbd91260250ab342322ce8bff263f10a5122b4f5143715cb448e1b7e4190b847beb859e6bc1b30c92ca156c9bdf633a9fbed4718879783905c39901ece61c4119a8e8c6c02217b8e1392f16a0a2cc7e81cd6b5a7cdd9fb9ef0e32c5074290d36d8be2f21b96da94e973d7171e50957d2fe871f774372cc811e263ba00bc7bbd82aaaa48f250943fa5c487f0aec43bba1439dd4b6ebd5bd6ade106c9436d24a503529d451a15601df91e47931fbe0f1ebdf75beb0686295a37d96891b46a15d5dee23b392254fed486df69eceabac974fd9e4781befad94def855ce64f12a1bc7572e2c6f6f6cacf91658255845effeeab8ffc4a84e392ab9089b64e9e59216b5d103265d9031ebef333e11f7224b39ee821619795e53af60c569083f6870719afd368e1a69cf514218c6710386c6cfef5660550ac8fbdb2582411f38f06afa0e81123d96d0f7e883db51be011281c69a11d6b6c7d4d9aea4748ad436530f2fd5c614b4255f329944061e2bb7db8919f3b9bb37a87dc55aa7a73c4c8e64bb3d3344951e3d28b91f929e2fcc296be6a4521ab271b311cff8259b9470c7d18e7e9fec018d831ec3d39462170893fef3762d3a6cb853753790a2266984fdb9931a7d0e176106995d91c838a449b1c3108e49f4878026d44a75803fb2003220bd19df66a7acf4188fd2cd45da3f15e8faa19dafabe6fc7ace38b4ae1414fd12366fd34fc302cce05f738a718327f5ecbbe36a3a536bdbe046d449fa60b8e8b18241c982714942f786fd568e9b0a804971bd4423dac74fb9e8b2850d4e8cda70116f5d4bccb45ad3dd5d8185fcdc406c2286d0f93182b557e425fafb3c950949172a539f07cd41a50c1ddeec64ea1f0e38ae3e62a2b11217d4c187f919c0d09456af02d3f70a88e025bc30eea3dfffe4ce5fa3617b067d09f00036e52dd2f0a7777bb4334727c684fe82e4d1d6e13ca4786d9951d1504dfac79d5a36acf94ba02fb90d76cf5e1a7704e0404d5d905c0f5888eeaa5d8d0116406c85d8a8550fe5cdb1d4182a6b992bc43f1f62e99726f5057872536bb4905ef78189b913228ed4fefb593b8d4beab0059e34724d5eaa1197e19897c22010c656587d99c5f1815c238cdee94ccf9f8d4cd8e7b55fe7346c473fc812a3b1d5d7cc0d9e13c8bce8c3357805ebd034b9aa158e88da2ba042ca04acc0f220dd892c29859a42b662cd6da240c4cc4bfe7af50a8ae1601710b8cd2458abdb7e3fe78f6d55325105aca908b24537ec1b3d690e66ade7d4217e864e5ef1a2a0b22712b5d039879234771fcd0415baadaea72517a113b40bce96fa4f7a0c320b8d9a8842c2bfdd2c86da14be667ce931d38eb99485fcd4304016a22d859b24162402bdc64b2db9c6a490cc6769f7bbd4d4ec5ccda0e5e3c334e156b9578763add6e1815b4f591a82eba19bb4fb6d3a98794587d7c7dfd1f5f62a0594c12858ef3aaa1fc3b21fd69533b62446eef6dabdbc64f36795a7fab3f8ab1823fadb6ef0ddb8cf8555e0744480c36d812dd824542742990dcccf9f62e33df6e81adc6263da9b205aefb17e5d22217acc451711c8c75bb97ae5f471af84238a5bb05d38b684980064fa59ab3d3743de76e483c762f8bad84e64989e12508bc85dca4e40ea3cc79617a1b804a7ba812a501bf0a4b156af8bf8d54a091c3acdff3b5a85ed0e869b1ef6418c1050382b4c740c2a3e5c7a16e3acdb6b5bcc1a10bd1d6949049c72a533b333ed39e3a76779aaa54f713a5f03213dfbcb326714d1c4729ac2905b2f81d0ecff736d97ca16044a06d97e7f5cc3b8e1ef4c8b9c563e2e21f13bce7051edf4f7030e83edd02aa0a3a093a060e24d65ab0e3e88a53b3a27e7b8f4308fdeb0035f3a6f1eca043456371243a8ad492212a7d0e4aee37de96fcf3919918155df9bc556bd96f548352d78a8c147358a591957818e99b606a68d14db5ff5daa39d780f6c52db01099a8b5e99beca552fd6d556e6e04e4e2d0bb5ea6059839c97abdd16e61feeec0fa5395b759077eb79950c7cfbc9ef354b8a21d0132ec4078656027221161d27b030fa809f54eedb9dbabc43bd7ff285b5227995f4169e6fc1e89dedcf7f08141a2d1dc6d3fd12ce24f40c9ba6f3178dbe8c638ee83ddb1dc4897471c2347d3b0923fd2099e42fd19b4a869e92183df8803efda0f40fd089d89fa04dae8888685213399b5e37677d02dcf198be30aecb832873f520ae28783c5b9b9f476d892f1263c47bca9e86c972572433fe11b4eb578674295ce87e9d830ed07900cdf8223511c592c083658dcc7a8ac522bd37dfb7edce5ee4f4c80705344df05474ce18b114be5e101a2dd9ebc562ee75b8f6f1c41252d164c763e768b7f1b43c54fb5b37f729eb49392db4642b9baeb8c74582e9bf9c65e7eac4ec59bf472a9251d0fb64cf1a57319100bfc79e1b39e89e8820236fe878f757c3c49580cb5d00b70a4ae725b2e99a64a30215e309294241733919f53b51d8fd4ac2b91c658c16c047dafa3f5eddf0313dbd48ddeda7c006f9b4dfa071f977189a6d0f3325b82d91baf0f9c3a61243e60b799d278661fe724e153929f0a450fd36ae3b6f76d14e1fa2b256459dd3b16cd6d88075df74bffad30a3d394ed5191cfddf58ccf7534ec6a9cbb3db572ab2c1614add34020b6cf45e2d590d2f7fb8afc7e748705597f26384dda71649f68779e2d727ba97af7016bae09463b6e9e7bbef1f049fe1dd121b52bab73b1d10d6beb3b3be7742d324662f2e90722d59bf5df15052cad59f9e136d294d59bba2c4427df90df50c30d8db1040e1bd1ea72b709b050ae1cee1ee895a4d95d5370a808a18680694051080ba5991dc4037288a34da253f5ea186da6aefc4d20c260412c4ab111201eed551dca376dbd6acc6fca9fb426d3c8d1e37de366f7e20114bd3e39bef2611374c83576bc04e32a65bcaa57ed5447fb78650999eea63bdebe53ad274dbf2c21a179683ef041125ceba560b1795742ddd55b02b97ae0c45ea54d6cf42e8289997331137f2b89ee8c5c09bf4e2676e85c658e7c303aa180354dfa5020e65480a5e5c25bd60ab6383cdf9032b1670e867af803906f20d7ff50806bff53b73ea3bff2d8efa8333eab5f6d0cdfc3f282e5cba209c9eb719b2d4c1f9e92d549b56238eeed859b2838d9de4e459e50e555bdfaf8b8df9e6082adfcc3fa9016d86088b229671c1b2eb8621764d666887e2f7b449f974984b89194e633585228f00567a6eb0ed9d63e35ed5940d3287f67d3c6fb272a311f9cb0e4c6a2e9b740c4b688dfb566b84010e8b26914485dac8b46b604429440f450a29a0bc5c2f9380baee6fddb8bf110275628ff68dc5c6e81a7d25a37a0750c1cde4e55b2db5810717500f469a810c1fb7a2b4aa9221182827bbb89bbc7e6742ed661fd97beb76997397a8a5a2a4f122f416c687d5ebb8ef4716c330a18fabda17f6c8d3b497708a66d44dc1d3b9e87ce3d42eff4cb5a9a81e0e78f550b107c842d36434d2c78596b20b1d96f312de5a8dfef6781b1b392c0794ee584b10dcbf610c1938a12cdea0ca545c67ef7f2bab28467cec0c617c56d003b1343c282c656f016a2b0997ceb477189b8619fe6d27a27031218bad76933f98e0b5d72610f7de75fcc3e87adb541538d1ddf9f20c3ae96439a1ddfb414029dbb5270265f4b0943751e09e3ba40e860683483731363af6898bed34fc144f98df883e95c1ea3bec892dc9ae1f8abe24272900694674c351d664ab30adcd9df10c8ca52693344f0fe00fd68c866b711bd9499040ccc5f051ffcff85d75d962ccd9e6af2daeedffd4493aad435254ca096a80a02193a3eb597f01211fd9aa39e558337b9a8992ae0ce9cc3fd764bf4a3437474c397bfd07f4764ac8f11950e66fbff1d817c785cc5d58f5965d5a15b4df28cbaf334e1c029d30468fd6f9745dcadcf979bb77cb5bd8fd2356006dad5a8dd0c931a9c0afff53940d7301c809dcce84438b00a03f477c0755059261ad8ee07cc0ce31e503eaa6500ffc51f26cf4cf38726ebba60df0df56059e4e9117d64be8a03b28efa75a30672d8d8a9a57525f16f997bee49bfe0bc3832a0ee5c03cc8847ad8c42f35ffa2dcece8f7b5992ca88e861629a4ce18571bb73e955a84c6566cc104f832bfbb757da42766bf007e41f8c5e90ebb416c35f91e65c13ed5f7e7a598a32d8870d1ad7d609a7095d1f66f05805554531339b6395092a9913e4938a3548a4edac9ae7d6dfebc6ae54043a782de21c2f75926a496b8f35cc80ab01f0d6d06b104970d55f5925cf7fb2477605071080cbeb901386d1df0b6a5f2543df497821771a9cfa16dbe8a7305a5c2761d3c3a0b2430d3b187c94a34af4e9c32673ecf4d3fff154a634d7928f251ba8da04bed9493fef94a46efbef27ef3140c7d0acc3e8d5885d01908d6523ff418a35feddbcf2bd9f401fbc9843d5745ab507648b7f17c23aadf76a1594668bed0e3cda8d61ab638a35944aa182109368e2e68231e528cf28b1c71d700b69a9232ff8084f03fa9da1bfe0b9812557e97f580c699a82c36e346fa3e813dce2a24c34d5ba39f03d06d90b70d4570b69956b1b9b82950d4d87855a590e66b4011bb4e02b1874c7bf4aacc44c01741c63dd19d28795470a303307389356462b7baa0b28fbd1bf071bd997be1907e1bc9ba41c875420a5c1bf3be8125d6e83daad9ee577cfaeeaedda7d9ab4c30fd60eab7329f9537ffd8734a71d41e6016349db803a901a555ca3640ebb14fe44440549bf9b7bd6ab6e537a66db77ae161da7c4e1e6eb7dba8ebc88fb83dcdc8d4d46eb24f066e2b26aa2cb4c1ef090215f5534652ab9c94d8e07565c7f851deec6dd4c8749195a533bb25200259b5f90c9423e302962b2277daa079ac001f1c7bcbf4950ee86b18d7c054b80f3660003701541ac6ca57e4c119228675bc858c9ec1ab8a0c85a95519c3a5d32d0923e3c5c25f2075f3e304f14304615eaf1689e497b29e2e6a625e05f322f26c713c2e60a47fefed11de974e389ccad0a65019a79bcbd01f824a6eb5c8662632609dfa6ea65093878328fcdef96e56bdb12d06659cc7196d706c6f77f4f7ba300412f96e2ee16f86bc1f09440ef75568343ae7a753d9458fb56e1ff893c69ceaecc2732c90aa501a64eeb95edc990c2abf2aa7690a9499b03e9ddf28bf8aced4aa51cfbeda8043589155737a54bfd77003c54d622e44aec31da85266fe1530f3260647d8e59b8ee5911b9502925a70ab3d71151d5fc4699eba1817e6f0e44bf8bf38d4260f8e32fbe95fde2cf8114bdfaa6a2180812939d3cb253a59ca69252bc8ada340d2717544477bd5701463be98d500fb04cf3116650a8f75f5887de3dee6a6d53b197fc76dac3cff7bc9d4f7e29eda36ee9937decfb0c07b7dc3ae91493e7e3315c27b8b3550b2ae012db659b11c4c2e25a4d7ce633a5856ab33ee89edc6647a293c8fd16ee3751daa8a401b08817a230ccf12b289252b7e73d9a48a041768b14d03d2b74c0f6dc233874ecd458014739a4ae0ce5d583a1c22dc5de97fdee09abfda3ee4c4bb0cf5e1065ed404e5f4fcef6be752d5f351a870cdebcb2902aafbffdcc43a2f410c59a92523cd6bbcf5c770eeefde1b781868fb43029a73b47c899246ad2d4822139a5b246cac6f5bd3256ebe9815905c2aa71d50c6ffc877ca560b906f7b1a3ef69957b38b936d47ed986bf65b221993e2e7c73ec8382bf7f82eb7bd2006a65c695d68a1a9d95357d58f1710c5495656fb4b165eaba6171f866eb193c6e73bdf697c9913960207a76d9e21a439b7e7eccada8d8880038f004f10fb95b324e03302675d6eae49e09c746ec8bbb4d9f9e2e42b9cbc7582ed5ce3b34722d4db1746830a2b18abef9c9c6b674c6d693c3a5bff6ec918b99ce7a2f5455d19b40ecaf55253012cb9bb5a835362681d7a3f7137bf13552539ba67e64ed0f585a2c2d39751d63f8513cbb17b8c3dba7e4b093903efc8d893f5a7344d447a7fa8f9aebf9a42e6cfba70b148c638effd6dbc714656aa9bf8768c1a373e27b23a15147efbc4b6a72c376bb6cff5ac958e1f3832400123cc6ef841a8077d49ae9a9ec6c49b21148f18e1777406d7ba6204f2fc5c474bc35e338e05b28ffc5574c62015f1eef287f57ae718a8858184ad6da9ed24ff6b4abc22ea1cee87dad2af8916d7e4ceb25906f092996ad46c8c9b1f27392e061ebc5497260c719a9701fcaef06d0d403688141971cab403e4188c638e3c1c1b36409f5f50e6bdcfe08c394a925c068f51190cbe834e7b34e4580dd03d756b68e3b50fe812cba8b3cb8e092defedca76892d7259b826de3079f50d0de717548b83091dc304e5ca6e44c01cc25cf916762bc3163e8acd3c0ff2d1bb60861a3cf9bcbd811f3f4e54abf76de7a04d52cd6d06fd1c717b05edc4e038983f2fe228ac60a16a60cb4921d582b3c9a7a1bba6901d259bc07eca8d8118e85d7c545a0f86aa49c1f94a7ad937d1ada1876147d522b16743aa8828b0eebd7828ed99199a6ddd67e44f63a222ec2326fd95d031398e5d5254e810665c87fd1585e3f342ead1bc92072723fef21fd35e18f8837881fabf969ad2b791785f7ae112d39d53c89407ba7e29b52764acb5d2ac1c316ea975e31ce55d39957a48054350dbca6fb500f4725cad994e1060a109fa7d0f71fb2c1fc1696bc8327b7ccd265caf56674f06ef0a5cfe8af19d15da38581e2cc1418fd432ce8bb9c96708ef35eb9c1328ed7e8457c08572d06f2ab3332dc229e7b4e1e80d085b9d320dc01d6aa0e33288f6f295173460a1d12b4a08618c22ed4b80b9373e4b0352f4710629b9d5712d93fc18617c53bb90b2fda2b00864e9552ba95b272393ebfab3b0d71236ad1588d1af072eaf8b16f49d0eefea6f50d5c62c867313c34107e6abad8f4d86142b45530920a480bd30b809833123930be7c6c99b8ae61c2d4ab2f6a31358eb1a909eead778a4b814ec299b3972050aa75618432782940648814315e657f692d92ed6f2a28d6ad49f69bf716202896d2f56cc25e0485884df69a3493f0a4e1fd142bd52937f2558d49f2f7219aee0942f0726ec7210a58deabb3fe81897aca3fa1414a7d948a79d145f5cc5846785300533cfaf6bee6e0f69be910212b5971e96de3506b286b4d0dc12aa3336692284e4405ccd0a5a7a2e7fc58b8ef7c9fc9861376b2c95e08469581e7173c3d4ac1a752ffd84787c23200c2346ce4b22ca99e2b954273bff6e7fd42d9fff222745cf181ce0f5ebc1095fe6f417c58a0e614d3fb9071c355702858d97e7d38b826d4ff4a0b93b6ae4c24e1f1590f4b42369d0e23e8ae0174d35d69aeaa6730303d42ef56c2c3441a960add81ea7195768aca50f7d59be65223c0c0e60600dde1d7b4f9bbf41be240f3497bb075b4eaeed199ebc2c00da5288579cc968cf9e904d7f2bfc516d9d4b7ca541e12109abcaf02094458c9e02096c34650920dcf6191655036df917691ae68367221e5d65f9495b36783429a3db4cba7df8aab6947b76b93a70840f8406d7d4b8973b19e0963087f09b931db6e9e30a03d645f174e4819e87f60f404d6509d8aa770468380f45b57b702d2a2f412a807c4df74d129bb61e5d1a9f3d0bab9c15cfe6209a39ad494fb5a9013616da48c6a34020ad331257ba1c76775d86139c305dbd27427e3893dda578717d9a7035b7a94e1947f4fa463d67119266a43cae59c09d695130164e1fdd79fb4d3de1b370ef8da5a9973385616a75da70112054b956e2e8e62b94992091ae1f4a1c81666b35a8af8c70bf95910e54a0d722f157cd618d21376a6806645d255986f24e1d2cdea40dd6e32e8b2e4651360e0aca5e5644ebdefd4fed1f5baf0e19065af738a486820cdd392626d89db211e8e5f1f569a55bd7f3905c1dd615e7b849cabf73fd6787460953b50d83e8337b573d45782eefc3835f0fbc03e5c83e74edf62d9c8c1135d8071d9c7071483b80cab2c40844f23da1686b5793f43a8c77e3d4ffe9e4e675fef5b14cb517bc33ffba92c3a550ac3613dc71c338783842ed56dd39f60a9d9fcbfe40820d0eeb6c7ce4c0879574341de560d4e71c4c98b31ca00adbe51680702d8ee1711e5a4c131c6b0b53acdfe03f223de1ba4287fcce808638abed59be1d975b68609b35433ba98ded0b560b990d6fbd9188ee6557118eeaf6e7cf76e4dd3a727a99a9f0a05f3ec445a4f9c90b54e46fa80b0bae5374f0e54cd1de657e7f1d1849072d60ccd33df02da0a70fb7ec802209c9a5e586e9dae7f87e974d8cb942a5d7832c7cf9a098102b22eb212fc8541f2737416264a284a6935b796ce87f83ed36be0a5faac1558e3a8c5996913c2a90cf77d25e2a9c5106065e4e654925b6d33dde4e67b33f6dcb4f7fd4176ea6cf27de3845617947c62aeba4a2433f2566bda2922c6ad8f48f88064652a8aa1496c9612857434053f584c1665b1def0cebe6b44b9c58ac49090a633a9b95a66ed760600c0d188de713b6233ad120bfdb94458ff7b25f208d6f503facfb6191f5e084da699c35008e00b9b613e73ca1edeaab4b1e26dfcad1072f199dacd1d30d52a760a7222d21886fa4647f5213e9f8ff55f3098e95fcb19c30bf5580d1b1e797a291184cc88123cef4dd5970fd7c56dfd79555427e563a999ce1cc66ceb0eb7ab08778b22630ba304f2d60b5226cd14ed2990ca7bebf31317ff0b6f40e76a43005e0695880ecfe40962c745a53d71bec5b436e77cfd9be458e6323ec7cd20afd51a64e5e853cec3fe1bd764e69fb9d4d8e3947385755925e75b6005a03d4eed17260e543084ed319d1f4b6497c1e08c4ebbf07d3532ff2244a0d1c650bd4e1bc6fa64d1cff40325cdf09fe55e197bbaa380ff8fef5ae0be6607730dbf53735b7a96da0f3c8c1ea8306a1ada85c03f2d261185d35b345fec34739b9bb8b0e498dcb7a0716f5ed738965fb03d7aa314167b8817c2e304d2cc0b9ecc966ce57199286d706d66595458881073a8504240221a3741e2370970f83b3542def0ac4dd8b1f24496a201bfd7be6ae5363d01e335cde87a61c97838e78ac35969ba44fc425de12353a73e47d0a91ce82c1a091af1367bb80f529a0519af6b1d6228d5f31725ad4c1d7f370367ea7725c2e6b76d7547c4815e4be83d90d8727b2ff969b1d4977369cc383f3e75d92327ba43a16d33c8c8d18a7545d56e259bf4c9d17a4457541a9b1161d108eeff9f7fc5f81e9ce2d4423606524190092a4ecb84ba03931592bd7ac3919e1b629c96b12c6d710b285c78e988a96cbb917197fdec9e2dd8929a6e6a06e1a85f5322ffbef032704edaa261e0f306bec889f6ee937ece861f0a4b42cfad6d70ad21b7108a6078ca9e43c01f074f37068595a9d30eb87ecbbe9a96af0e6cd62f953ab704721d96d75a3d1fc325bdd074d7daef35fd82463e1022003e6988c22842de30a9d5ac85f73c8200622734a82c3d1422b20500a290ef5323840cdb5b00cb3b65c365ff6583ee1ef77ebd255ae872aa0c9bec83735bbb912c76f6c0cc2b81df4c4271caf64dd87a6abf9909c5aefea670d460fa7d27dd755f048492c86c116e1041952a20b4520d16f2b7e78c978b15817d99f2e7051a088f09c5e8035e876f8fd97aca6b3d97758b657296979f4976696b0e51beb82d15a689efa6b60f867ddcc68cb387cc1556ab8e5a5d908dc337c473d500716b723223408c4e1fb44780bed7ce01c65f0f062fff0f1367e6dcca22876e0656fb968c977e369eff5b36c6303141ef9d6198e622ac8f99cff1e9c7889b080524d0a5be275b09dc1ac32faadf60790e278964ea6384b5d1bece13c8ef66899e7744de940d2f1476ba2cc62caf3f5b17a24afea92857eabbf1977a0ac4e9e115509e22cd176570d42d50105d37a41b5cefd74b30760a38816698c7b867c979429297fc9c2698d2514255daad40e5f6d716b18385aa213e8ddf8f21a3ac7abb3286767a03b101b150d0e0495c492c45ccc0c4f2e0f393c0b66607b5975d3c0bd358b666b33f38594c303147dd0698d60bbb203654d5f2cab1c4b5420eb5e1b28d973f9cb0f03d8f96be4a3ddfdd2ec1677a32cab4a7e0a1ab08f465bce66c8c1fb9307991f5de8f2b5b3403ce67a10a2c9397118316d7fcfd064285cb947975cc9536b2b3146090a7ee4300eff5dcd73d3f7e3590a03d425cd5da9ed55977d5b5e774089a257d15f2de7add1b096e0c38dd95812e1ad0bfba505963823fb4c054cffea31697b7bba49af255dad8b283d66f8f1dca2b551975966c509a4937820052df14dcecf44a8c1159f80fcfdd4d1b1b2e46e2cea467e68b9cbda9ea6a96f9d8b13c8d386d2fe43a7debd80bc89adcabe7677dd4fec1ba551e0dda8b9760a8accaa26aa6e9b1158eeb0719df933cd50d15bd5c60b12bbfdfb8727b0a2e259e7feff449f35289d5899ad836521fece4d5b25166690f20da59b902d636e2126ee69fb69af18f9f732ca754109e46b9e8b6b18ef63bb73624b6f7fa3c549564a3d7ed60d906b423127e2164a1b2f650770dd7ae0cf6289acd2ceb770b9d10e6f030e674f8aa7d434206e98104b10f03d8562dbf8f0dad4a9ab8ce0726bd8f6535308942f5ec24263653990c6e7117b2beb4040d3cc98a674497d5cdd8561bdc17fc526604ee79025175861883fdbb16b26d832323c48ef800ce6f276e7bb356cd1c543f8065536a5f81c9d4f4142601856cc6ba0a6ee1f1b8a49e05a6354aea11fbd9df091a4d252b105dd599a71b82f6be913a618c2d6c4522596c4a028e3c7d6aa73a1b6bd6346fc049d4565ff225601591b2f0b3164b239a00ca7951846b221dbd8e9fca90db69b2e2bb081452ab5448286af3d1ffcb9f1de518e407f45dda2cb253053dfec16b0a120c4a57c7df34d1d369afa521b77403dafc23fc9fc6ae949c28a3dd7f92cc73b5ec5de9258c0b4d19a04842f12c2867da4e917f51649bb489873757a4a69e980a7fc342bb9a740008840b90d64949a9bdc866ceb24842d0863fc16ec69f54ac2b40f40b51e1ee4f49570ccb321ad0eafbc2d89ff92f3f9239f7ec76fb044de39b8e98a4ff38bb43c1699902b7987c938236c79a61337d84083adadb5140e1f7e3a40d9fd0cd560dbf00a9990ab10dce987409dd76605196d3d108985f9526a43e9b704331d0b17cbdc31dc8c1808e0bd446ee7730ff239e3c6fd5c020eb082b5542820a35f27e88b3d07619a450c57d1c696fedda9021d37fe20af92583c8d30be99f23a85a5bcbf4d942ee4200059bc18fc3b7d2d1c352dc66b3fc12f82c635e5fbdf0f1f71903a2ef77f8dfdd3e9f9e6b6ec40e48029a8cc283176d1df1aa4cc8c489c75c3092a9d6a3c1a324380a4f5eaaf27a28a10f4f3df0abb99a04f8d60c0138ae901efe570fc324249b331e73d00b97229d647a7eaf39adec5b41a20a0240fb4d7eae7178ed6ecf3a0c3563045c8afb2ab6e587ba2b872927311c811e94290694e912390564fdc82b944122ba3cb0db9d8f654520056fbbc537a9739f88e13ea517414968661fe51bfca4ed7db88ce8037642aa12342814bfe605d9aca969bea0925bb74b018552d79d5115f0712a46cf13a20d935bfb7b52d8db18456c625632b3a73cfa8c100fe889e532fcd6b60d96f17aaa29f1704906ab3818a263b807f5d952b74f9b2c70fce9bca07d1d3ccc1cd366225e49286cf69368c8a5138588a5823faa3983ea9b9dd9befcf37fcb0131bb8ef8bf8a39e5727cda9856f4e9c4249b2dbbc7aba90d362f3faa47ac39a253a090157fc9824aeec455fd6bb8f45a4ac8d435ccf8291668ef64529e961b97331c764494cd46443c07f3f0ea28ac1c8782f2850cfb1558b464025f3f22863df89fb2b77945121220725a6fd2273d905734e1bd01c6434c7015b17f88522fe45bf7a6807367f2052b98c3fbd7abdf7286c40814298d25bc0c125126f2f5f352962457196843a602ebcb131934c773303f365c63e46a254aac55c00a24fd703f17dfd6dd48479a03696341d17e0b076df04823a9eaf2f1e80933bca819f053d7103f708c0ab82b96f77cd3329cdc6a7efe4182894872135eaf0764be5633423bd25fafcb2d21e85c511fefccb884c4f0454a8dc7f0fe7bc87bafc1cf2b0fbb00f2024da1d7fc3a8b07cfe334561b88006e0938cfdcb826157a7211a20aab24ae487e69fc7916426d40f3928367919f210b691d2ebdde4489076740c3ea6e4842bb7ea82a0c6a4fdfc5a35ed87d74977a91a9ee163a98e93b4a823d01be7cdfacd5698560d381f5d363ba85d1043b8b65173354dfc3979f98f7d046eef43374523568a737bb9459ce9641f4168f2b33f2e7cb2d80f3f5d5de6a92516313f1c98bc36213293ee7ec6a8a9a98cf3231e661e51e6b1a7be89ba20b3009f9547b3025c07cd918de309dd567f43e28f62e9aacbbbb286ea636b54b044ba69061d2a4e09bd442c82c080251f4eecdd44ba4edfbe2fa58616b8dab94485a0ccc114e65f1a295936064970bb27f8280879fb40d2d726f037f0a15230eec9231934342ab8077a42cee8f1b3de32287627bf2e3db56c1d010763fea629bc814d290be98902a5c7037e043f08b614e75e6ec871a0f3bdc85ac96015db4f47495d4b32813c47f4eb30d8be62992bea839e49ca1c12bd8c43c8058d416f6258b0394af0a279385f12a3ea78800e2203d36b2504c0b17da853771d4a78a820c3fdc767be4a5fc33489466d04d8dedbff9ca52503cb7b92615391249495053dd442ddad251a3f3f73f741a07659c246a7effe1ebb9e69e50063cb099634d1da0077d5bb33e4bb6310b77b5309195a0033b2888f19dd1bb250d5dbbca0fcefbb22f13fae12b9a20b64b70479241bdf4fedda037375e6e952150a35086f5ee2240795b5d6383292f4d0d46bf5a3a507fe612f664172a5fc9f98a90d49bfbfafbcfda5ccb05fd817a3590501918cd3be6d99018baed16b6971301a3e17d889e00b7fb7effd612a64f9de3f89c3fa174400779737e9b5c626b09403349bf5212a4a3fa171fa985c62e2b50a024a90c2ecb3605000f05a258872ab257889ec19f9b12847c7e94f152e576921a0212fc73f04186b04a9659c8eef900b8dd76dd20e2d9388cc67372d82c8ce54451986be9d294b2cc48ba2df056ac477a204ad05de49f64f44f665860b46ddd8371426fad88b2441908627d6371ae15b49340f6695e6004a3364eec40be8d1132ee75cba8adb0e9e40683e8d2251d410935237e6294eb1c24bd5444920856974955801dc6fbb77f54dd97eca9624d896270e09930bb6461e3146328b98aa44cd25a1294d62585a488041a9113044309e6b5093f3198c2298c85a906baae47f2de2ebe372548ec9376ee0e2438f13a7a9b2a5577e49852b9013b1af845118416231b7ec3fadf52568e737806c6f72246668f1e4106e632d5a9b6edd4b37f95cf6b542d0d9461303f8864e088171e1c6a3f85e1ffb23fd7aa03fe1d5957df2446856b0cfe4c3294bbf3bfd5d7871cff28d988c17c79c28806c525e958e8e5d40f57f03e1b02039e7061a467f8c86ed68d165b05777c0fe0d9f7c55405ec0eb85941a94b9eea4406aa4299abd44cf660be000f96c405b45012d238a5405206dae20438a11d6db99518d8313b5813778f1d24a182022d179abf4143e802b9d4b6c6bf240ec5b0eba7e0c95eb39c6f50093e7fcf7cd573a6578a67eb6cce0776bc17c90e471eedb8b0f842fc352696b3aaa46de459ba7974727a79ce44345527d14975326569baeab3d7a94be62f0642005b384a946629ffab0dc83cc66848a5ce6c0d8a6385e44ccc7350116f08206ece513608cbb8015d169edfbf0ee868c80037838fa1688296a890b5ff7c7e7a308993a6bd3f395ca3991800747c2a0e69a6dfab0b642954f4085e31f3405dc9a4de46ae49582ad3f1710365cb3269e13877c90b6f5396a90042662742f0f866e92a8124131f292931add417e79772234c19fd7ce7fc5e537bc0cc170f43cbd22c00e915cec35d543eb29846aed68e2861ab733ac54dcc60227edc291bf91fb72ca7282ab0153c8fecb5e858326074bc903c311ac91b09aca290e6091151d1478a9a3055400bd4900344313dbb664c60f6512f00a59efa76b59a7034a190140c40064a7e5140b24e21be48a80c5fc7940b6317d44ef3a89a2656b30812815665354ff906437250ea30d65541128841a2e55fcd83c599370bad12693c17f02765648bb05b550316acfa1406f4255ff7904124cadc8ef46db2352fec8add2e3ac4460552f8bb79e82d2b5414e390284a434e08464684cecfe27ea3a5b938906b0c47bc828a19ff4b1712441fb5bc6814e4705433a67bd145e5a878cb4bcbf0e347c3e1cc1b3ee2130d43eacf35c86c0e1bd4c308fbf80e5663ae4b6f7fd02a41c7c65d926d5214a3b6351d5085d7c342b96e075c745c79ecaf8dcddc6deaa0d3d00048cff016abbcfafb07115085c092cf52444c14f06ab3304a464869f7b7ea4aa673ac038063e4d527a9e1c84ff94a618293bc23cf1dfc69d30dcdf17a5831b3dcdfe15ec4184f6c0457555fb9cbc7243b815e03e133376f4d11bcdb97701af50444e9dce54e633958387a6c1ad32904a78d8c23082ffdb92c533a81f2705df1bc8b0005d8a9c97e00c7df4fce761a5023adf42c35faa6a915692f1913cd82c8b654fc03ab750d55d855edb67b5aa0d6dcc912a44560762d76fd838d0981803a9571eafc8c1fcdff87848808b5eb0350533f7b9174914e5b5e4cb06a258866add46984db06b97a670bcd0b2f13d7c3d3725ef5f8aaa1fcf432a89ac28d641bad68f67112957aacdaf8fb8c3a3a92994756eb65292d07101e73f7c51c669a28f61d875bef9f3cfd78d3ae7395ecd38dfa719f892ff92243c4f031d0cdf633975d01bfcc6ee2fd7ce9feda5fc56e151dded9a67ff5a9af737c89e54050003cdc2704f4e6b9815eb13e3c4560f202176c0f932124d9d9542bd6a3b5a778fa46d5a0d6b1e3b77227262af20c79ae305c8ca264a53ac3ce19b86773d50a9f81e6d4067a34675d4ec18de1e41e7ab7e2acd0e167ba6809b1ead4144c90a5179d36bf1a5bf1a2a360ba71576cbf25ad21913173ac5e00a5ad16e2511bc3f694db5336e7de0d4851914573a9a907a7f557dbc115a17f9a882bd3ad6574601f4d7bddd6902be349ffc0b71be6b661584a981d33e99584811eabb7cceb7aed90fcf47a84419f837003580b3d8c4e0cdaa6552d6a7199c5eb39fe4082195d020c204a52c680d72ebb2a105fe265f29d789458a823c8250e0605464e3987fdcfeeff6794338b63db926d7575d0a02b7be30b836ba8ae8f21ceb58ba48c80e293d366caf0ef7a3cb5cc073533964618623367211b9271ed30bd13d86adf1c7f267f1d4a1c3853e69b7c9e15232d761ce1401e1c9f9fd03767421865ae85a270701f86ac50fe40ad9f975d5ef4290138dc4a475ba0995cbb600d34e4de7cf658efd9f01ac996a2ca88663786f77c4e330aed15121047fd8cac2803db4fb41a9536aa83214974a85d50c8af3fa2c2a4907866d275a071919851e479c7bae5fc2c6890cf3695f4a00ba644dd5811243ef10430039948fada0c4790d413b9fd1700c10f6d16f63c41cc6cee897280c7c767e3e2c2c0c3fd285cfa06d4e14f14aa600dfd396ab9267dde3a169e44d2d02e041b6f7c8f100972c5cb07115c5df580052f9d1257af3bc022d9cc3e010df2dcbe09a287cdf25dd05d8f61708b2e399f789c132083aec2991fca354a6bdc8bc6cbddfc5f6d714792de0eed01a6ecec3befdd2016150ddc2dc6bda91b600c5eec471adb6fa6577e50108557d46b28cbdec51131fafc2caefda31aa9c1e70fc8322617e9a9a6b49dbc8725bd876d56049bb168ba4d90eaa1d88f7b9e0f5a7c99b03a80ea6a685ba7198d9213a9074e97aa15a9ebb5242daf0808307135ca8c72d745d5d05daf6398907364642075cd39ecd2adaa7add5eb90b68b9e7c6b59fb417ebe9215c0ca60dd359a9d73b9af2d00555e1e27651eafb6bd450da5ce7b502e29064ae840d45f8bd49618e2111c1792d97c8235408c286893b5d353a98f65128424298026d2262eef0014db28fa321db58778f2b0fdcca6612ffcb9e1f2b8ac0cb52a93e2664427ab8f1f526f6e4aee4c01819e60daf36ca848afab1e942b64a7ab8f974d6f7613fd60ed4f23434b9c63b6713f657bbe246a803192f03cdc47c0f6ce6e8b209507a0e28b5b4bf27d21b78380f446cff4dfee1a6bd3d4283d8f5bfb95731ac8e66974085d44072b55d8402ef096939812a2d1761cf1ff8be28fb4cbe57570bbdde6f865c950da4bafa802806ec8f44ca39d57056689f22fc71c097f0f8c0f292f564a15b0d8da5777b01788b579882673916735d8866475ff61d5e968c22b24a200bad225f3d0a0f6eaa679fc6544c69df07e97f97aa7e2d0ed62a4c29f0c9e9505793c6e479f10b351372f8f9fd57e6e626ae7cb8d5b55048cce5818109d0b74a808a4f2a57f7d7af7e345fb7350b762ca7ae46eb17ded61ad0daf9099a24be0606f93ab813964273d0ef33d832cab6da3c9084ff098e7223dd7f4b70678b4e289178f7ade7870292c020e5a7f021c8a62e551057cc98f9f7f2bd6361084b7a819c546c2c3d282ef58fc27a927bccd11cc3f379784b40e786e60102a0bd6899a71ace70909beac244bf350c23b5977afc5a4c827194f3d807cfe4396be7f4c59789a1517dcfbc1e608ab24952292870a5ca20a10963fd0f7cf0cdf23451215f6b26c94fb7199187662aa5f224cf6ae593c4af81c45322f2be458a67a8cd07891c33391ece6e5ca54f20505e9ace490ced665f80b463f18b260c78a900dd9caa5a80606897118c04140a97f4eaa5e1a01b3e72321da87409b4f28e1aaf5747ee6ead8b497912559365c4e2b41648055e7c4e1adce567033dd4ce3ff35157fbc430d0e5d1847d4727f8daa5afc30e59861af75dfadb00321eb0f8e0bffad7835e0a77b58660413ef95989268a6c99ad5e7cbf2ab9c5423f450db11edec4cbdb340d37a1df02191c245cced900dff38caf7f62a27d442aea71be32b103bc9a148116383bca3c02c5280b54c95d0a9e1d4646a8265d87254e05a4516cb712494dc50371cf26140b6b6a372e34031e4f987432e1e8a2cd4c3f853cc996d3a8bdaa41f785744aee7f94b18db1d8e9f78d8e8ff313ca4cb938a31dc59c590ae2e2167eac7aab7d8ac438ab3fe51b17e378296fa230298d71606e1aea2a025dcc9bc9c6dcaa6e7bcc61f0663c51912ba65659f1eddad44b9529416e07d7c3a496c743a9b3ba6cf656127cd7f82a3cd7e1caa778579db6a2fd131b30f969a00c57cd717a53715013f6d7d556c8224160bfc7ff677f97f56050ab3d1a7c8c2094216a20d069af62e9d9639620c553a621ef9647a881f93e88622824091efd8f8f6ebbcb06d4f38706a15bb8d448182d06f7931a2862de9ad06aec6781be638325d1bf15ba532097e93245aaf7f4c44711d17149588957b422044085279fe487d1fdc7de5153704b896a232f704b8cabd9d92509ae50bbc31e0000b74504d5fee420920f72bad39fcc900dc50cd70c05060fd2e1229f885a9709228b49653352018509a287d21f3611a74b28fbe34be4d69d4880b195712812173b65462dc5e09f89e45a3f3bc888a781c11051c81e0b9cc10286de4b5f714aad71df710bfc3a8ae99af30fef110a53b2b27686bc44e73bf2a4542c9c8054aef185dda5d3048c63516e29ac57f2ffce422983f743b327af626a9ba500b634e18cd930e0eea1541b467f4392720ce0453f89cf6225d70a84cf111e567fec8a3a5da1dfc45bb3ef7c5b282780057e2bd7101bbf6ef3cb395d9d25c6d0a6392b42f9aa7c173f8eb2a0ba19fffae26e4e1ba8968c2125ccc26f47b990a99aca8d38026080430486dd77f8eecec63e94d39777f91c68be5b0b031ef653f92ff8fc9046f13ed60d49fb463c390a6f121a504a3e31c89288ef60f75411a9de1912a26fb4d3ea03f6f2bf23b6376bc591a4c823a2650fd5da12253e628580f825b34a311cf90d205ff6f27a65b8d25baf5bf4879aeaa7c8f24d705baca807ed88b068e4c26e07265440718b2fdbef08b132afcfaa2559e7dc4e6dea88fb8fe63af63ecac44b66343937dfe945b1cb67d4dd20c7ecefea13ed169c188bc7191064e265db47196e0c4c7734fcdcf2195703289a31eb3ec90fc72ea32622eafc1dd9f1d1407e1eac95b46ee58032455d7b989aee404c1e1104f72253e987e4ebc0a7cf4fd9783a4857253b1104e4ca82ba33a91c1d4c4472fe19cb064dff8a3635b319f9b8c935a7020c6bc46c95f895c09ed36a6607cbaf6c961d99cd62edc2b6606c7da4f6b507e751841370e7e7927cc16fbcaa95d891a4e491bffd0253ac0754873b97bb3cfe7dac728a1bbea22aa0a60dc5eef33fc95c6753b601d563d9eb3b9d0e411ddd17de7d3f304cd720822490a7ed2bbf649d32d4c6d113f1d823acae2064824b90764745b52838b1f191c41b1033cdbeeb3b262c24b700614edd9716a0a6f45354ed9b9fd0cfe6c0ccd17d775001363f02291b4427b4ab3e10967dae824a5ea47efde1d72005b1f8814814a6e813393c469e59dd18ec8922fd0b348eb8d1425dec19c2077a3fca9e04ee8fd323af9f5b999d0e4eb2d624e2d71014c7afeb5074fabf0cc89aebb4738a8ab9b9f21aa5b7681cc8b99ddb999ea2087b90206ff1a66f493171a78ac628f9e2a214fe237c7933f3fcd994ab1923dd594984824bbd3cc2d5c8eb9cffad261c101d27e38bc8435a2c835660566b93c0ca65eb3cb8059a634c0b111d7de7a35c53414fccbd226d81e418ae3903696ad24f361ae4d74aa2fb44589b368f480bf698e9aad7d0960a102edbae519f7ec868412add15bec3eabd95b1a60acf79b2e01c34ccfddd25ca9550d17e7dcc2f12fa4632f5c646dcbc42633bc171e874e0ec54057e6af3449d214c61344cd88fde518844a41376b1e88dd0c38b02bc0a24c8578498797b5c33d3bfd8fe068ac93505ded849e52797672dd934ea1b925dfae6759be5c9c148443132e6de5d24991fc0cb6c0fffae94ce997222c4d48566c58c9e7915185ddaaa0e411fefc20dd381460800a543a3e29042cd4255f38870126c7813c3d3a8564f086e7c3cb630db4e42389b0d46877012f070ef0d4ea49b3099ca9e88a025f4e1e513d72e844b2c04394ac47b771d618a65fba70338625a17c9ef1624358c6265679072813815d2784293093e843d8ab56c7b8cf1daa2f59c1693bbab4a9a418949d571cbc753effb5a657cbbb1baf858b5bde4c8a07408fdc7e28fd28d25a3d48a625b98b1bd39c36a5930804f74689432649ad10eed02afcebad93cc4cc94a03b239d109dc77aa95cf0c0598df9ce04e04bba5ab468a78cfb05a4e497b38ee262c50732b24c0f3dde7fc9834842cfb61898f8fa0e0b8f4821ba418eba1102c7c1ec4c98b1566a1ea3986ce4a1de46e9e9b52a6186d1a4349f60c14c5302d61b800f87d50c2991ae4535cd586fa7c29f0bcdcab4e10ea8580e127b16e8ece28a0be3b15a81189c0d1f0a9611da690705695f7586d8aeadd80a298c69351572a4b3fe2cf0f7a63a69b03f74ffbb40899d624b6e50a1339bab53b55200a65bc7b9ccc03bbeaad12d9b77ba994f8e4ef44934001b879ff9ada1bae5f0955cfc9687639287e5d37dc91749d439f8fa52a6192977873993eff4ba9e34ceba012787b0cb5c9ca76676792c89140d55632733ed866fdf41ec9bd3cdea1f16b9c79db7079363691f957b3a8ede05d3d3ed85e1e85b7ef50523fa817dd29a3889562636aba3bcc489247b1f3838c1cbb0ec9e29b797bec0c738758a80c73466d6917b8aee9e4fe41dd226cb9940273cc3afe83c9b1a51da85c1064e37612619136b9b2b15bb9ce5754ac4fa15980aed5913467c53a467016c194c819230128a9d31c005ddad8a47877540f8d070beac275e18fecbcc4896d3a735a949ba4c3af41211f9d86d226f2b194628cf6596cb3f40110fa1709621d451cea36111ec6bfabc195b9bd46e253814567e63a7e59d2205a2ac713b23cb637e7b1f03ccb4a44c546575b06bad1528b4a505eed6f4fed94ab508023acfe2074e001fa14c3730a5302d0369ee9c4e53e7d55bea2b37c925d0f80a2e9e365815e8807c6f9915a05011e8b6744afdc6b74d6c304ebcc05fb82b5dccab1e794581ffbc61293c7745b2bbaab14c2944d3516a55cb3702a126837e991d9e9c036141f1bace27e425b7d835b70f6e4c20b304779d85ecf84d0813737100f7f8108b3c1e2716c8c4aa50220f4c5b6446a505139dfb4b276bbdf0fc1b342baa438a44cb62bd6122ea09b2f168c116a45ae04e2794c120b74c23515a836a89afae0934c8cd2c2b10fe653d72b14534358e2676ece93f996d398d9e55fdf4b9d5ee06a430f907c38237ec996c42189f2d33a3483674b55f5d73bcd3c68fe68c64f49e0355a67ecd296ee5332b56f4b3fb788df5b9af6eb068fab0ebdf00468a8f25fc62997fcfc1698caeb76e047bc080cbf292560dd0df1f975167e95e7c6eb8841ff7c5219b69a2d9a79b7239cf8cfb0a3395ed6fa4d0eff27dcd0e13a1a6816cae9ee7ce8367c5d6df9501d942cec2e6225ccb5c2f7da017a1273bd103d4b0cb7353c06ccfad3ca75b1f9a7fe8b4cfe447a69fa0307323f08b8005f74c4fe10a77b9c7b01302214064faa293195e18e0c817066b3d55b2a4f86bad2b4da30606e696bb9b4e84c0d72ca7af988460ba077db99e2291583dec987c27531d0cbfc39255c0a62f21754f7cf0d4f62344f5a8fa76b673bec629adfacc392680ff75d470821144c01ccb91c05d0da89106f54da69f7f5b777b6087df79a8018fa25683515242950c8ff71e18c0ad7d88966cfb8968da31f96bac21dd2372be9b4f0e4319180e79e7e39e416baae9ef075900972b69adf3871a69543d711ca5d9155f00ccd1b8526759eba5a3f367a97ba980c0b01ece4fca22818b1e8a10cecf3782441999ff2c4384f34c6e36e7407c50f4a75d687938c6b1bcf9c59d0781cfab5a518434991acacd7016b952f2d23a1173aae30b5cfbb3086077ee598765a99b77a1df8646f6c8eec55a4aeaa46d6d20d0bf032e2b22dcfd1926a0756e96a4e8e675961b73e45477554f66e67f809ea41403c0decc9d3a7a4234c62991ea4aa2d75b2aa065381b4d282f73f04dc2153ccadfb68a10feaffc685949b36504f6910cd45d67009f8926e461f2f8173f2d7715ae4988f7b71f1433e6eed4c0fa28af1e86c0e811b1db0e121bf2b12980c95aa0c7df8f8dbcf8e98a819d50a2acaaac8fa9b43a354fdde4b42b50112600d8df545fee3b51e24bb51dedf17211120569ac8e909e4269fbdd2f00c4d52b5d2b70e7a56c8d2ce3c1442a6b3f85091c867d3f7132f54f130f017d04abfa09fbc87031050368fdf078789f9f305c76f51eb313ff3745410a29a9b3e8ff458407c0b25b7c9315ae2210c78a063fcb40b7868a463c0e38e9098608df7fbabda712ee00d966b0c0dd3bedbbd659202b6be1d5b46aaf399cb996be9e2433132b2fada995303836f81891177159801ada3d2b68977aff89e4031d37c63a43caa1f7be27a398f68333a20ad91b33e0e7b9837c32cf9a6d67a5c987cf2a2f52a0f0848794cf607ebd48ca87b47e7c2e77acd621e3aaacd40b1a1db1d3497defa230967a45b02cd819f3d824277445165b1efc8d12aab01844a1114e38f87420cd3cca6a1d53fe2b7f548525186ce3502f7a513a8307f92f4a3c3c3842def01ec13354b438ba101b9570fe85d1e58f0992b34878ef2b2c306311c436d0f65cc80df9bf593b58828a05af8374d9f656a691bdf4452c34170930ea02a9a1110d7787b3c6442796cf28db5c8d350bc955509811a677787550d70ab6b5b41e51e33fb94f331d75521d38cc288534dc1c49d5ef2d97c8a7f5a9f85d06bdd958b046ce58fad667465b5367c7a80e43d0bf6bbcf112a9a479ee734c9d916d0d35776958f4a78ddfebf606c447d2df48be18a5a8d8c42fbdd34f8f496db070bff2c7df65c78f523aa75c350fcc8429e6aca20762f1cecebac83b50d48ee2346a8d138662c0ab64cc8339cefe4cfd8fcabc699f209bbb659e4b8830fe594aee90c8a4b737463c7bbb5d2f0e36b562b3c2cc8f90f21f9e0ca330415219ed2c274d1014d5653a440ab4b351a9fe2c3b48be41cb8557ef8b20b3a424d2c9b874e0cdbad31a58090a9752aba8c50c83f3bf50a3e56e6b2e1b60c29425eafebc2e426aa1a6c7ec579a1cb3c4b2c5d71a5b816e8b3b8f3ae2ec179f96e9e3fd9d0fdc297a22936b024380d815dee2519027b168be40dc18321f5f0cc54cdbe8b3eb75e4d4357464ae1716de8a119808b06f8a691ef7ab1661d96337a9dc506b5a3c705b219eb730266e35259424dcf7793047f3937cb3ec26c2c7d9bb5b8c7611a2b4eead7066101263ddf0df632c9aa4c99823f37b55f901890c9b4040cf37735d36cf2093fc1d68556921390f67ff7c7eb0bd5bb74029f55d10833e3757b29ed502d187436f921968550269dd75f40ff9d73c90855ee5e5759a8f209d30a77913e23ebd8f83131902135c0676c8853fc49971464e1160778af7be1905d9b4174587518adb7406d34eed3b70f59e66c7d0796b8211c3dbd158a621c0b5b1d8a73e512ca25eeb2096bcfd23cf6c24073af03db1a2dec3c43d2114c5722942bc84f39a814aa542b2377f3095db5f5963af5fc609e35c78b8db296989068f4a7867ff9b40490e205f740e59255546ced435367657c144d8d30b3b6534e64aa6b74720d9ee784e59492ba3569389102e7114f72b48dee33ce347224c534a70fe6327b7248f37ec2ade96bf488b079b98bf9505cadba99c7f4d4d31e2bd6d4c4c46e34c2f38bec3bec413a4d30c9d3dcdce60294f036a6468d474bc78ccabdc2b452508a8bc518402a7dbd974a50d3e25f65d0f7fdaef446c84ff9d0a934553fe2ac03aed062732cece56aae7f0805b5ecbc3c61a3e9edbbb6df1b068b32399dc1d10d34c0ac8a45ad14ca69911864535173044a9c03ad9a100cc5676984ac6593376e9d46bc00608c3491f2f13ad20d0d29fe0cf3f608531271bf6752cdfaa15caa838b4445b5874ad02ad17692305f977b0d63a9c503845765177655c8a491f935d9c2aa045eee14161ab32fb7480282ed4c7155651e0cc0fe1c86afac1572775c5980997fbeeed0124b983728f12609e09361410b57e6c10d85950d1fdc082b36dd2c42a00d59658ad38f7b769b0d477370c0a3b32a86aa2889f11fb2723fd025d94124202f2925db1d136d73d4269ef8214146c2d08ef0fee9377cca7517b76ae4d1c693beaf63cefef8db71725f9c3d780501f2cdb906e9134c5e73ed36d6d7f0e3f3787322c7d0840de5c97aa72915f6227e9ba7235bea9007448906213eec00e5c7cec98379dd538a4a22994c17cd5342358b0b43e95147f005a916d8b156027b99793bb4fc54c8d0ba3a69e89827a515909dc9a09b48db9c66b5596b8874456719a6f8ec3832cbc133c39f3b0f2ed7962b1dfb2bc193dbaee139c35f308d73e751732d930d02183c464afb4bae25451e31b269e66744c1569534bd38bab1da61bc37bb80acf0d657244d776f1a00ae0a463aa926c06b65efb8c86b11d3bfef641449e1a700f6529133e59c161728b143de587d9485be8af5ca1633fb1cfe2befcd8ad43523d5f5ba2619392a4256a3b47ae194f99652b2462c257a5aeb2cf30d8b0134aeb81d3ca95f556d97944e88a2c6bc6e87de0ec5f3bcf553da0fff4a1a81e4c9c840c2b08620a0dfc32ee379a7c157429470e3b04bf4b4016c5aae90ceb03fe778d7dc8ed0e4ab353eda485e5d5e404aa742ebe3ec744c17373d7088fb5172370012ea015dc7556a99c97a74e5e442d9a034572399eb3d24a4ba8ddeb37eb8aee1e8d825df5432cc1e532b796cbce683a0d0d3ca57a3242c638f32408cccd9cac6d0ec76a378f32cfc802e9e8fb92d261b530353a0c1443c64683af6f96f2de3b15629b22040b46fb1b2fae4e1e232bf66820352e1830b25b41d25aceb679737d83f1373027edf6e2d51c8a396b30657a6c23adc025f7064e28646978083f6e2b63da9f84ca3ffe1428fe0dfd6cb5a77cba7b67b13a9f0d97712ebfe27290a051e2e15fd17acd1a97695b1b52d8f0d22232b71ac6a77b6ee71bb5e927ca2d3acd481af50e3cd94862d1dee5d4f4b23ba615880b48e5d1b61f91c766853f3469d3de3421f6ad14bce300ed2b8495bf7ed7d8be28837fbd0fd7580750de61d7f46b23ba77884086bf7a5ed4c446a14604943f3f264a13f50fd89aa26c38ae09a9f152a2dc877851a3fc3f9df2612bb50955cc0a1d715a821da271ecad3db0764b953d0882fa43932c31e5c4dc021c61cea27eba1f5248bfa4e70cbd46bcb270bcd96df02fad5ec48f55426d38b968d1cd99e1010449362a020ea4c3e02600b4d8169f5e46afc653da9b9acb69f9dca4c86c6a155b3820e35cb13834ee12b321abf78333f7d2406bd4a5eadf599c7259bbe1260e3404f1e3d71f297bb9b916d47ffbdc8caa2fed0fd00d23bfd25e3a76c8d0bba4cc15df4570db92e09f7a972e74f7dea15d9feb108fe032fa735dd4d989a9b3ddc5dad825971b8545d75743cb3192e95511421076651ee2eb37a0782f33eb1c81cb4301fa154903653a77da679cc88dfb0afca760023b7cf7118c8fcc6dae7877de453c6a1486d742daa26f345d68e401bff34c90d5a14a026570da78dc1af9659b5acbfd262235b27ec8982560963486ba13cbfddb2cdfb1f0fb2be3c3ee0f24e8fef92f26252b1ed37a2f2c8a49ea03c68a143ebaac2e395bc0b0c46afa4ce46b33dabd2dcae6fdb76fa17429cedc9cc84c469f6f677bbeacb9c7f462a58c2a17fe103f8cfec59e1401d10fe343e5b3a890bc3199940fa4e8c16aef126776e9c9e7b25b9be5392e90ae85a72b1dc5d38350372f30e0c73d7019279085504b61d77d30317156792dcc976c20aba35c1c1dcd0e72ebed6dbd01e4a503bc80049ac0d052b81b5a03b91504a94b457031b04e0a78eeefcfe25ff5aac4317cadc192fff5473e9fb1f14d06ddf409e4f544dc9f4cd95f488d1925d2d93d22dca087802686bf42414c1bf73733518289f43af4dea02e9cb0a6e19c723fe11d99b84eb6e71f174dc1d4060c04b5464ea306d01cc3557e3f9d898192b9925c8e9f656110d4e2968a0122adeb3e3bb6bd1116a07e277d5da10c1b21a145e893de06ae0afdbef4bd4749c50b5d063f3994d8e43c9b667184469a3504fba93f8add2aac947cc0fe9e96bb7f877f035f12e2f88494beb94a7340822495ed71f6d9657a0c97b2bead4cb4d4c81e75f4ddadda85dca7c64569262dbe283fec9b52bd4b099195e2413256ffc04a6c30ed1fb2caa02991bbabefdac829f5182c48faa6d3f190eef116c2539128637910822fa08faa7adaf18845e343a9345f1a0e7c6a33187319c9ed1783cd9aa66034fa3b0433a33424b14f968bce9f03bf37cbc6f4dff037352eb2ed73761d5ac0b0ba16368b628c1b9946500e0da1717022c5ad94a980ede6f2d686c41c83f9fc1057b6a9030f10c13e4aa798652c0321d9a769d3e8eb3c888f2c27eef814b353ce16b4cfeef91fffbb21125f5a6d32ae804017dd3d90dda58525982182cce9bca64acf8503d36c902bd4ccc16170ebaf36d43679a4d21ece239ca83354d1b29efd7f6917e8f67f364b725481eef70dd3ba992b93de8160af8446a7993630594fb07b08ceec763340be34ed029a21ee03f92bbd5277eb9986e306eb7556c8dc6708e9d611585a3960aaa41fa5a257d768bec299c4e087d3dd9f2f4bb4053c6fe6b5d4221ae68394d3a8c13424309f832c239ecf126e70ab9a8aa3a736914af720fb5edd2d5e981cd845bdbc0e1030a4187358534d2ca8d35c56745e1100599087748b1e745f3dfb7288f10a4adc665e009fc17dc2db9e902c4542d6d7cd17083726b89f54c70932873c2db4a6bd8ecafa807e89e78b3b3c796d29f7f8f123615bb9103d68538b153cf94d16853cb4756d7de518d19b2ac567307ab02bc639526213467bf960d7cbb31d65d089d1aedee64a79ad3b21212d71bbd371eb9d10c7fdb0ed0015ec36cf113e52446228d3fbc8eaf13124f8450851af78b004213e5df66e2f142a30a072d1bad21999464cb5c31cfb70f0fcf6014446df6431bb90e7cdaad3273b8cda566c7c83bb7a08ee671fd7ae9a2733af5596938f8dc5b228ec31e5564b9e8c5bf4500d37d809a66f63d78d59c3badae85682b65baa6bb837263e5063b54d3c2617298c4fe40332616d84524562ddb574e95e1adf41f101b7048ff3f33fc81f385f70308196f544db3e171720d66d01e6081e1efb5a23429429245070e9817b59c05a5b7f695f46dc02639d10dcede160411bbdb804bb0fceeb12ae4254e4c778000b6f9b1cea0583da77ddb41a0b474f363a1390cf598db8d4087ec645df74133c19e64932ab96da5bcdec1398b7873e1b55d6975196642b259e78ef28095f90636965d773799eb70e3db00442d2386e3bfb1e88a21866aa4e7865b58b4f98dab951c25469518b3beecce2a9675f0067747f1acddfc660b2773cb4239e3766fd561137e6e4c960eeed493b6b3d516746b656801faa010234fb5151e7baae97fca5963f2f77980f046fb2ba425ae0eb4a87c6e439a8f0d0eee7746058d76a94a6e595d889f825894ecc706d4c3c74d69182e3424b02df96ce8018c748","0xde2827416d22656a3cf87666640aae36792fecaf4370a1010edc714eaa24372ef203e6232ae2fa13f3db5aa7f65008e269d6810a57512906736b93fc932827fc5ad17e4030f83235d352dc29b62d233f325abd9356e91f50cff35cf92ac4d7c426476930e572bf3686a58dcf2e90c21c401e3157faf149b0b55bd7479d5f26a8bab1c91a75da4d50270d2ac34f01117b8fad1eb7fff6fe116c8dee37bfaedcf94eea85814e25ebedc8cdcfd7edeb2bb9309954b0a71288d46eb922578601e94c6d9d92d6a4bc41adee20733a4175c08fcaa6790f37bae9e5ea682fac8b24200276e1326aee90e3ad10f0d31565e53a94164f1f3a789a6a9b436092588dd12a0c1e1d24e0673e12641b5f80c8db9f4a2e628c355f368469bc90511c6eeeaf806cec3df05e8e41b7d9f126aef307acbc9f0ff9b9fbc46854741a5ff5a134e592c7bc3ee019a63b7e4de5bff4f8b0cdeefc143ebca17766d2e8e06dbf89893588503dfe48ab3747436c405c3274832d5dba78d111d927e572011648c03a3cb42c3773652cf28b5c47f5c0b225de90910cc2db0453c2b1c27386a18f3d763e04f7b435de68cb32c6dc75149986cbcb25d3d9ee63fbdd778f701c9d807409a62845d0b019f10b7ea57ded616ea72b8cd4c181f662c7fadeda0ccbd783d7aa2de532efe226c69608877e97c055e980102f30584d5da9d7cf8febac54917b69b3aeab5d20d9ad462907cfcbbd408d4cf5e52c7fe1e3ef763661851fc862dfb6b82b9afc907edfa882867ae4d9c9abcec2d15475b45b77798646b0492076762ee444576ab0e6203374eeda590adf5dca5d8b52525aec001a3408ea17f9dfa1817fc5ba16cdb80db5a985afd3363c60359394885d7ebe088a38dcb7dd26ee005af98b6fdf8d1a834189d489c1bbb5bb999310a8835d8ab5828f12ada32d9a4397644a6d0366a669b2c6994229ea523c77730c36464976bbd5bbd1eac9cbcbe808f5d6b97624b25736d1ce68be8738c121aa57415e27454cab41e32a0f6c6b92a7860cd4e167e5c50c62bcdc014b63af7094ecca71f1d46ee32a90a73cb344d95c173b6ae22220d0b2f41e091d62321113f2e8a2e936ed57c2828d72aff8b8ca9b49283e751eb1bc47465b6c68eec12c836716bc2b955d9ea8ddf80cb8c5417a67e19f58cb74dcc3dddbc2631182159b63fc02383c456c6419cd64525159c350e84b94ee2e17b2d1c07be86a1fa7122a879ca9ad0d9094d0b96df5080025af035c15cf990648343c7ab004412d5a4b130499aa6c9754956f46d98ca8c451f65dff6f31c77720cea77d4a6aa6868c98517a2514bf1e81cf59b4b3072be938cc448cb2847ca3091a94f8db0aa4ffa1841f24daba6bfe9df435d5a089f71ce738a209d6dad7fa45f84a333c0b8fc6f285e85e3322fb9d430573dec9d93ac1a27b1ff6fa7eeab266f52b4f06683aa64b09371a10f580389f9f53255bcf3e80603dd949df75d1f1d4008ef31aaa08aa6d4c6901360eec88f49da43a07ce924a4a9377666d8e2de94b73b1a71aabcfd98d083744cd101f70160539528252179d40d153d02e025db45a657c2ded69c0cfd3ec5199e090532ff042858b068f3e83542c9676cea5187be455fb2d3fefc43edce8aa75e0cb89df00bdf341d01714214c2d7563a1a9560ba320e0edfe41061c3947267a22f10665d6aecc04a09fddb024f300e91dea94d7a044798dde6b8c02ee97c8c85cfa233c9958cb373cc466c88a4524c25bd0ec08bc7adb8cd68d2a94f46aa6492c14ef22817dbaa6edea496a6177c52a9ac2921e2b9c37eaa104290fb7da20e7919661d15e32b287002dc8df3e1931a5bd24a366f4d896353e9d5f0c98e63870708d2ca810fcd08046540298ee7b38fec8e85e76723e776271d9b15d6c9d031f10d15d7c0c3e8a4794e7a451ee02ae2466af39aad68d02f540441f51fb1789b89eae5f6cdef8e358444759a4f146299466785ba0a262d96576d6d930824398daaa2015f9a1d83d1bb2d98d975e083d6639e377922fa9ac3c236b14b5317c5953a897147489937b73c1476754d0f1889876ff1c6e28651f6f19447b40aef7a94d715243325998ac7854c9ec28942a51b957addb8c0dc8aa06719e882c91ff89b4bf4653d8ed0c8b7ad481c1022eb985f7398eebf7b1977eed0557271fe6fb276fb499e584b21c6c00b0ecfc3fd4b5638620852c35bde49707f39e555fae4d52fe54a8539229977bbfd959f434f14c1a6030c7a7b52b11886a20c0b9625df9599306a0602868de5cc8427a070ea4911db6357cf21fcb26a69bb2f15a5a5d21b0ef17a755dc98258b5566fcc1c43e2011c91cef13dbc17da439953cf0588a50c83b36954a2c78ff12b5c138e4b7c8907475784ed17005b3c45bf86f77c9b594098bf93b9db8d83c7fc156ee24167bb97b7dfefc8098e1ee233ecd2506512169d8f1573fd9bc1f1b133f280fbeb345c1bc7b0972c7fd777bd9874cd706e3097103ea2d7e926fc6c2ab3bc19c16134b029e7517aadf2039abe194700154c318021d41b90af49edb15e340ae941b6161bcad37492751006f0f0fb37859e36def7e8bf322b305167fce4dc100ed19e694974d01e7813596b4fe7b2e6811f3dfa2815a26ee19ea1468edeeaacca02bf4a1f65b88da886f95796b365f862ceb8a3bd616e9895e976c60783a72accc89ac7b19c50831f2753d810a20a7dde3644d36453f88c555cc9cc488413a3ddb550dc90f26e79c9b938d63c93c74bd1a0a2b042e1400545244a905aae1f79fc1ab99657beb68f45abf9a0c95e9e839e33e2088f1cf8e507d6b392b88118ff84b0f96de229627b15e6d4d3a70b5c0a712c75a9d2c927cb4be15fc4deea4f2f1798643f7530f30721efc5602dad66524e6d9548dc287eee135fa361a15224037ebf2bb2616373279db9ec04a8477f00eb755cc9bb719021a42f5df534182aafb5b7c12152d16c02cb1e70f505a50f688bf05547bdb2c7c081b539c4eb2f7c773311e2325eb5d5f9635539f21d74fa6f9ee2b49d543bc0f36f9a48875e012805716aa0ce6d31f1be3a9777323ac44a98dcec04bc596589e95cee2b9c50be16864a7d1a8a00ee756e7bab9878cb7c6a5b2751bf45e2556dcfa8edbfe501adedadc2f0976cea89ddf98d1a847e84ad8129d8ef36422d6b3719c083687bbc6d3640476554f9ea8e79990f364ac97c1f939c36aef848bdf0408964c84bbc01e0092a59756fe72efddb88c33b03f4c67a9e66027301f8652e7b46ba72bc0ad511d268df6212c61ec521befb18d188fae8946f3e18de6f374d1d77c2923d3e4ce240c0b35bd75b61e0b49402d90bf3cb7a7eac0fc755b26ec3fd194bfcaf259b4e73575424c717068938ca7223acb02259caf4c739de069b78ef05ddb5c02178dd693a5774441c1309fd1747e11efe545808fe53ef9de78fa0695e8abbceebb5f921d78c336eb9056184399dc482abd88f10518cbaeb50398ebc52ae6a22cf70c219d4ee70208f7f24a1f2b5920bdb5439dbb397aee562f9c8e74791d6b4711fede8f8dc3858bc5ebd6dbe4f05f8eccca0ea4de39eade1677b93213ac031e612388f91eb41452fc55517ea4718db457d6b99564275a264207f266972bc2d9d642f276eefc57da54a8bf01ecfabfe87d397ba96944414e367466ef897d1ea1729652376f465612b7bd56bc610e33c3bae8095c503be486e5d889b3d3639686c9ba28c4142ac366c3a727fc4f30fbc35c501281c03324e039beabdb3fa93b3a87ed28a9d8cee2534b51a47c23187e5409fbf59425b7afc58d651445fed9ae92e79bbf1eb895948fd6f025eaf0709020378869df2ea5bbe533e36f50248c88183c4108d334d656841f4bdf63eb295b60b3f26a264d3836a5f00892572938735d6c9551ced2aa553d93c5e4cfef566aee1b274a663a1aa6f07b20ddee69a1770e6f0b89399bcdd613d64834e0faa0874f64eabd207188272d0176d595a5ce3d5fd59f277e9d8494663ef3e6daae46422454103f5aaf883949a4b2aba1809244a23df779d0679ed963569bf692d1ac46d60802244a782d4d71f706c8fb03a451a0dfcc5db6907e70775d224182cd7ee7b57094361794ea48b88425a0c51a479c8703b1ce85303ab73dc496b3a0315f7a26ce2ebeb78ef97118460d6212158574198817dc2764c46af5b426c7672e286aea3b41383134ce6d8d2fa43dae28f568135eb6e2a5cf2afb4986e2b96c1abc201a220b39011a9b4daca321575aeb142daf0c34232814e27af8484f2d1ff1652ad3c3b12e6e17e6d50afb48acfbda30c27a2b2610165d227b413fc91485cfc93437b1a600dcc52c74fb047476fad6dca4b8e85fedf9d75367525fb61d407765cbe4555ec5050599949f7d6dd376a2fc00ee86909aa2338b31d1d588fbc3cfa0f3ad6956082b79c549169d1bbf06665c2e43da31a3bf2716627d7e914168626e0af37d6c5ad4099278998afaa76830d9f7e0cdc6eddceb17dc696da2b4ebdc7b85b2758426d05f08faafe26628f56f196f83de69a434bd35e9aa69813e1292f27910087f046e6a50653736b8aea176d54e119dab30475d535e746c2aed41f7c70ad3438004ccdfb473ec8a2b1e15ffaf2d2531c1648a915feb8b25e2a766289e20130586ac3eb3b73045d79db2a6a59e031cd3022ccaa230e8a3771884ef62340d2b814ea279a23668e48a16b11e68e2c36a51ba5c06de7030abf81ae46b8667fcbebe8e3b24ac9f04aa0222afb72d900db38c4185b2149be05e7397c9299bc254951a796ef292354f6815fc7a2e3ca9344dfa70ce3d1b07067366ee053a4c9f69d7c393dc2f5d6444928b8aeea5c0c325c4255e4db24b0694778555326a66d6203d3ddff1e7d8256ecba22737c34b43e60d296a5e0c0110303735ca99175c006e98df4c527b1176f61d893c5cae4f21b3213aa0e3a320fea6ad12d47e39c345c52f55850f80dcd7e3bfe5ab0e92a54500eb1c6480a28b591ddc691767a4d6bfe7581fff603f50e9b1bc4e0c0bcd0a6fda7ea9702c0bacb64cb1beb75653f368abcf1165d8004708b88cd3b298469dab6de74f26b848f02160f4488a25771b9da056d14c4c98e20610a0728def3e6361b2cf8f291c92499c41a170185827762ab7495d3df35804f0ee26a1cd8e83c3e682d77f51aeb4aba76a6d567226d3cdf144ed820569a5d2dba1edc862ce260bab9b5e49e695fd8fe3893c4cd19afc0e17106e656298d139d4a0f6c44435b9e9ee4723aa2f190d72b98f24580af25accc13eb9fef5c2f068c96999668e5e8af20cbd2774539e575719f440b6d0a73cbd6d4e8d988d217751961551f26777a61e5266ef0c8479f9414647fd54c8d8e43acb746753b8d64b2d15d911a1c19f1c4a66a34bec7ee24bbab25faf5568e4aac22f0c3ba2ad2d05f543e4f01fdeb799b544bb9d225bab5a50edb6518fad78d67796ed32ce835f8ee97851260dad648a83c405aad0a792f3129dc7ee0d9d718d9f30da1670b658630da716f580f554c63db18dd6aada4a89178e7ed68d9636ac24865651fbfc38e4a7b22ad18050113e4f73adf57b0e918df7cd100702777f0c152699fc307187ffc3d550308af3e0438948688ba011ed3650d8c84a138bab9123e76ecfa7055597af143cce0f4b8dcf9f0c82d28bc4f4df42d39c6c9b51144d25b8363d4d4cc7927fc2fcd0f3640e8ca99197a441c82c4ff3db9f8d270e06309400579ee08cc5828a15ea260e2b6a95a388b9cedd7ce03f0d63a0157b2b480c0f91cec0752bc21debcb79abae6d508ceef94b4a0b508b753b9985f10d6e5f17d923c3d2f35eda9b01e0b71c14be3d0ccd4d2c488d5bad7cafeb3dd288f6f3bf566d2f339637c629ef4c50aff2182fa0e9e45ffaba5325fd9f9050e74b14056891bc12576cf5523e1de35c1bf3603009ce40cd8efac5692bc3236420e8e7d481b92bc49bdbae619bb52c695033a94f096cbcc1db4ba73b77d9524c59a5d5b5a0a0604b911947a42826625e9bc34fbfec96d445d5bf32ac0f229a70f4fec7234a3bdde001b3b492a3a56e49ed27e830ba86008200fc179035e2109e4f2142d3b625e0f7bd07985255a7042d1ed7f5b24a571ab6c0baf9b99ee345962f7042a03e1bd573fec9218b9c69c2517bc1769b888a578587493c965df32bedb603f586dd8155eb0082495f1e6866c6acc53d49bc82d198a8082402da611608bd2336101bdbb81a5c0c5408dfe9130202381dc61c089edaceef43786984c54f62d0e4dba05535e86c17aaca5772a7da8162e193d1c777cbd7063cecfd3483e78e3c0533f83c909b26a8fe3edd4f1b41ae001b2e32d1f6b427ebc7ef334f4f7ba14eb3cb39835e2b8395c5294e0efc75c8c2b90a571859ba5f511dfbc1361cf7f321e528f63fd5c1ca0afa5a1003eda786ce7623fff6da09bd7b9af2eb4d1b390482fd3f402b2adbeb063550d6c53c2e983b0f5264a69fb04ec5c443277e1911ff752e4e8f25849f27df139385af89d109974e09d8c829d39d1c97338db6eb1e24b0ab0720af7b5a68baee0ddb6076d49762e934e9e1634c5a8020381267eb61f91ead8a986f2b32b214dfd0173b6eae3f91274a63d838f51b897277ba05838b8d5d41c0f5afd07f403c160116e879aaef0c5fd3ad7bbc164fdfdfc52c89ca661841f01434ddfb6b0cd683eca2d123dc062f3e00f3f8e3c8f0c18abd16f133e7204f2db3b80c51b0ab59cc7be0a66c1c744de604fb64ce3fd67f43c9cdbe47b0f0429aa93b72d8a0b0928f7cbddacf00a728d651d9379d49d800b73c7a747128858a19a7d6e8dedef61fff7d492358fe317ba4ee5fcedc7e623dedd3e21ddc685ec8b036e76d665792f5c439c6fdc32af1708a81cce87ce793c41734a0c75e9c84543ba5a6a3266f68db39c3dd28ba4dc52abecd492a59165536b0471901799fdcb7ecfab4511d7ed5ecebf651260e34773d9db2af9b51fd85c9e68f2a625ff36c31a3b257dfaefc221975e4cebe14772331a7f56228f3fbabc660c5c302185684340a19d8dd5604fda5a12c477c082aa30248615521b0ea560c476218ffa691cbe78c7d407ce80588a7e68d662e7e1fc2a37202eebaa824ca8ba4ef38fa4a542ca4189fb2135fd7318b28193468e9b8b7760b8f108b03d254ca14300c97c9231f7aaffeb49b3e7bdb91d1c8f201317243d5223f9211d6f42a63c281a801ccbd62abca0120348443834d3874ac94539b028dffa2292b9dfa7e81b511ff634b77572f8d0ec79e26322925565998001cf2105ef93f4e9d45d4116872baa3700d6d3930c60b95c126b941d7cdb6bbe9e50dca7c23fe8b90c4d66dede7a73ddc30973c8cc3ee9625303cadf04130b83472cf8d11c7b0851040e686faad1ad6e0c9111e56f4405a7ac9f5d455fbf6b88fcb46beb8ad43418dc7ad880c94e4cbba8e2c16555bc71812152240bedff0d4e73ac1c9043c575f5b9ce531b57f78ab2a94133784d1b565622191ae36b85709ef4bf1f7f75b558f320aea391a9ba78cf719232c9018187485c902889dc78a25a0e162dd8b3749e8aecf24e475d365e0686738fe5dbd5a99b925ee3926b10c9eb35aadcc2dc303ced0160b1394a72fcaccb791f982ca1d62df8b96efd9aee74129c615593c1857b17bfdb1e25846eb39f2360f12c84abb6aff1f90ea1218f23ac42793028f9f7249f8ad3b1138fd5efcb10de72aae9b2b30c2f415a40038ad968bcc666fec0baadb72079f41bbf696e1627e1e77e88f3139f6bbd3c92774b8d24aafba2b922431c67bd68b730149d4e961115ac0dc61f84a0e07051bdec2128d0fedf7c3b05f96c9859d6de13b54df228fd1d6c124768dd7a0170c25d271a485ae49c732eb4c067c6aedced32f71d780d3989eed0eeedf77e53fe2e0813ccb933ec4ed07dccfa1e8e54d9fdfb5efd986946c49e21f0a300e3cd3033dc0b2e20f9a3b7761902a719c17b61d733c651a0541eadd8f8b5c1821a7a6b9b136ef0276e222e1cc06d8bd490395698b454b468bc9c3a28abfcb9a1a4c21e217df9d6fb4f745376ab436ddb9116afceae7ed4d6f234cf352d864db59f5dc9b1c895271ad029e54370303b6fc8fed8d2f4aaaec71c813696929fe727191d673d65a2f93f1ed35ba3dcea18aa42bc924b42c7d8a1b3495ecdeb9a9837fb12ef93248db7e9ce81eae79e73ad2637ae055425fe150e568f17eb0c2f7f8470b7ad82d9fc8f08be67494b593a32a927529f43349fe8c236bbccea634099e9cfba1e0047fcb7edd1d73b4400919adf4f714b3cc6226b96fc4d9e9a12f72f3a9fd5493e65c002bbd8a98db5cf6ca2fa540877d41b9a5c70102699e318deceef2340723eb7c02fd136bdc907afb353e1b7560831970e0880134c25c1b56da0e21e68200827c721b93c3f2c9df2e4ed2281d953f533bc1465a7182acf649986b51fe085ae0cccc1fedd9a0542195791834a5d6f8fde677ef0352a886595aa0b03289012e672e909eccb92166ebae5ac816ddcb7ec6cc87690673c026262e7c271b393cd3693bb41d3cb19a02f2ea6adcd279d9254c6026ebb4f621256ca1873e39e5c01838de6ff3c0d59f2e862d65b2bb927a43f1a8e96ec0ff3aecf64083352407305fd1d74fc75f7dc81c395080dec92229ad40bcc69fc939edda13cff6d95d3cb34d30399daf1ec253c92a9f793b8f8bd6fbef4c60d60b4ffaa552c5637dc9c87d04b1ec13255e04ddb51d5c6f01a811f495fc18a501947480ca02cb478fb710ddeead2ce9468f6620b72d3a8bed945b0bc25f75be9437dc8623f941ef84e81dd996110822296350cf0614e27cc4a1f6e6435ccf4ed428409af4b8b917f9d420651c46e439278ced740b3771c4b9dad24a467621001d975e79fb737a622d9ce81f329df718758fab6a8860f8cc851ac33c93bc7e62fd4277d15a75fe9657356da4c105ce5a68f2bddc27d9fbd634ea01dd5c288c1a3d4fd00a707374b564e3496a99a6c7e433449cde90d4f3b2f842a8ce187f04602be13125e6f43fd91b763a36c6bf97cc0f22828f7b8dcc4b87413930305034ccce5369a4976d74ebca96a21a7a06c58e0759ed4e752d6701d0811824865e7d78d0dac78c11f86025e7390708a3892c95e8b5220147a019d311290be91604d46c3c73a36fc99801634b5293ed920f7d0d0be9a8632fbf2b5a9c9bc18231e4455e71e9d38758c88845fe0d94695bc7ba0fef20c97163d45894c57a49f20d8f6bbdd98c36e64d9add25aa37fd09bf1abc14e8a314d98653527edece97fec18ffa1448e32dae69361ad730d3e41cda67afbc50bede7d0ec83c7e3030028dd6bb36b8104f1c6253820f3905af0211e56b91e500d0bc0fbf66319e4d59c989ff8179c13a24ee3f1dcabd9a346f3666b232b61100210aaeb963bc963dcc8960b863b160621a8fad26eedbec498a022eca70461eae6a679cc82a5ea9511bc804c7bbdf7926626fb52f1d55c474cb0c1c4548be49dcaf3707dedb0c4ba9f56e8ae77199933be60593af69c72c9e309f26c99a6b8a901320d8f6f9ae9eb14e474e224288157e694a4fc099064b556628b74935f2346ddb8d9ad30ea1bf29d782ae8eccebd69d443a6d316997cfbdb6b96a6d54cbf2f6af2e70392236dc7585355384b813fd3c7f57a693153639eafb8962bb3ac108337a7615ad4731204b1ad6067dd4e5299ce126b74f7451e74b097124f2d374ed57f31cf6d9314a6ffe65c31e29425bfa2d65af3b1a038bfc220b700b2aabd72f8a189a5117afafefff91bb73786c03d5ab1b019c565b3911188e2948a5cbb2addf39b16fbd2e81644dd963ea99bb7e08863dac083b31e8b5693c60716f7e201a2349e3e43cf35258e5e9c38b33b9023ac9914587c78fd5c557e181a42e3912d41162f8e62961ad1785a0c62d559dd923d3cae50ee2527389b6ee1936c87ccb0c0e437ab1adef8e7b3befbdabe70ae0a370c4aab429f9e9a8d3935af2bad4633e7f9238fa0f9be6839a86ddae9a18091a3fc935e852c791ee16709ced50a244f25a5ac7754a5e19c29148bc0693a1d9166b262bc01edf7bd9080fd6e40ae1b0d78f18fc6bd8247f933ba3d8915b3fea59265eb4563f5ce8ffa8a6292ee79b382a4d20735c34dae62b27b110b6da3c6a206f3aff35bbc1cf413e045639665d466860cd0cb3b5b80e70e96329694e489bc302dbbcd4a1025bcb0a2f42a06e7e95592338688cc0bd2ca0fe0c4ac990a851a7959aa02223396ae78d0b79c772dab365b4ad708a793568d24650804527407778aa50e5245caf10a23d35a90e7a19863a732dcfb86fa45e3da7ea3b56deb68abba206c7a26a35123be2572291ef72c4c2fb917381eef1a1529abecfc5f3b81cabe2af08e299e06c68fd8c5d765886796c19d910b1e99933a9b847f4881159526622d66c73ba37cc68e7a472d253a985bf6c339c8bb5d90fb884ed47279b48676f46743e0ff78014e68bcea01686d41b44bd21d4f0fc3ea06003adc7b30fc8277a596bca81b85ddba7f0c3c911522b44b931ebb690a7bc4749eb37585582e7758a8116e1a76a21ed15f07cf38081f45a9be8240530a26dd80775f7435f6bedc5620a84587b3458c78ad16cc82f3fbc8dfaaad8c9fcba791d1c1658b4f95380a242dfc2882ff5c21c76a97a16a9044261a1b315ccf2032552bab0623b5a1e7e2af50d85d05d18a6524fab3751775dc72e8bc45b1d506deea76502ef4c8cec418d2f2d110f4213a90558d7510cf5a14e9faa045e91198ac6c25a5baabfc9e777913fc01d2a079dac842eaee484ad0b5b340060103dafb0d33acae8c450636a84110af2ba8c23f4ed4f30d414154dc5cdc00064cffd70e9ec1de31b5720394ef2795941e0a1c5c2859facce56c221d88deb4bd2b7f1725df16badf77732c361524c7cf88c5a5074d1dee644dcb12b19c3b1b263f0fa41376698b44f8cefa945749e338aabace70d9e2ae96ee6adbd778ee18d75bd42b827510e82418ea3ee34e79bf12d11100ddcfbd7269c27a3ad4f549e649bb3b1e1001aa8e772ebfda3793ffe675504054c877aeaeefbf02515a1dfe2e24e4378f0185a391adf1f933690b39a66d5ad8af6612d9eae11a5eaa14fdff61f38258a4167e4039c3c625c453b42d509dcb9a4f10040e3037fca4c76cb80fc34c8e31e718dfd40f7458e340a0ab928390e37de917113da9f0d26c899c535652320dc2f7f3eaf9d486ff85b5213a1a0756673c4879ed45af9227682f9d1047b0b7e95fc03b65eba99d2fdf606e74d60f8ae2f67dcb53c6ecf07c5bef8181fd8b2aa3b88743152ab0b5df28d45f8081d21b103dcfda1b4d57badca68917ad7ed5a215e1b779d9d7ad186868b08029aa0347436bddbe7648115db818d04c5817de7ebd2bf47e902a2fa1fddc25f283b486b259fa6137cd31963ece7e01ae1321f556dc1c32cb2ae950dfc50e438d85d854f835cb92d6191769d8cd936f515d5bf7f94663cfb385e387286590bb8a7835e6ce6511be92022290217220a6dc6851d3931197fabd36762ac89143120b9d4ec4b4162749872b7f435871efa044abc4caf6572a9fd4c536066b3bab94b620bddc5b152738ea0934d7723c84769c34b35236e43172ee42b744b9fabe4b41ac3f29d7d17489a7ee5dea04a96598016ec12ef382feeb9de7951b272cd5d0ef2368172940f289403b907fc0f6a390b6098f3dc88539433dedda11753b3ae6b1102f3faf05bcdf9d7275be38ea116d690bb3ccd9b6a35276c9c7f68415dc8f9088347967311f39a6b4d1e352084c1780ed625ad8f333f0f6e04fe1c246b7f47ec188e29107ddc5564a2ce95db7330a77b0139bdffbce658737fa0220a4c0c1e7f8f6d4e6913cde3774b97820eb4fe110a7ad7279462a1780f47df96f04989e9f4b59ed132ee8fb50298dd33931fe9d6f71eb4e77debab0eb22ea751955178b1044a6f81f5eaefdfed64b64f31dd53811aae57fe183d831bafadda3f20fd3da15317414c0e983e18b6586d6c72fbc39a99589cfa2e826c446959cb8dadb79f2078690aaf021ed1c0a5f5016394e061cb3a3132cf937cee27d04bc3a102c74b7c17b5d473843c8205944fa37183a67d8ae1de1df916f453c2a48680e6f7a7511ee0aa3cc0be432d08bbca3c2e06ce085f24f034f602db2ae23540b1656171a55662bdf373c6ab341d77a9e448852f19b01927a40ba5cdc8ac862a7b3280e09fb3098140d7478dfc1701576c1e19b36925be166b5c9707c2321132f7a346497ceab8337d99cbc76ea7754dd51ac724d7979675de4fc7c6751497e5b15ff9e3da4fca27961f2d1377c2bb2dd7eb186de6916f9b234872597e36b2b42b34f7d840e076c48d221c9388241c205b8377913a605cbab8a892c14286177758c3b1fa94d8e54bf89dc3c416da9bdc01eaf3221fb25785ea2c77ed67cebd3a21fd2055a3de669d810ef0c71bdd6bca9be1672d02ca27215f8e7e02599b5a2be3693b999cb998b865004f1693c1ff7db057184cf53d686e8d7b311cc62fe781ef443df2959a7edcf6eda1b400729d7dfaa743442f48dbf540de342f22891fcf0967c80105f5c5bb196efecae7fb779ce8ae45698aa764021872ce7424742da0ea00279748e7be65c9b2755a516ca76c1dac3b19041050be08abf5e047b1f170be253b351f33c9319257bd774bbc8f40eff451d43e2bc57bd8eb2aaa4a299d1ba725e5a12dc637311680b30b66cd88f4e97625cc00298baaa40c8fd7b1318c7938b8ea887bd2ec698c46d89b3776d941490ae34bb3df7d5f5c31f51e41e195b5faf6a8b3f47630623c7a1606d89e57ac83940e05e57c56bc42f0e819887441e4ea271b76eaf7835404ae756c656d3f34bcfa1b20c9c5a9f86ec8e2fd53c293d2f95700b98f8773b8d6968fc6818213d64fe443ebb4e73d474de8249135c066a08bc529787d9ea8ab38bae2afce45988a4c4d3e25a1d819551e44fa0a07177da7f91a3ca32fe4be2cc26c5c1378423ce9f4e8cc2cae47f0a974fc51eb62c7c3640f796d0e85a79d07a320b445ab937fc73e543fd19d1dc8c3b714f19a27de73958e510a8844e757c91d0472f918612c3056185b1b548d0462a379b4d2f838067eab11cb3694ead40f05f0ba38756b72d5187d51295db42e16c6579d5dbc5a2b5d8e9c441c8069c30f44a63b3afaa837b1992bad2367cb22db06c2a1c61f503961d495fdf5794599395860cd77e4f12fdf9fa06164c3e682251aa87be80fa4c1120e7cf2bed4d1a17cd3adade6f5bc8ddc246f6624e746919a0c1df0224064410b409392c247ac5cc5dba26392f2eb5d41d7528867ef38d45cbded482ea9351dfb639f7f897025affaa956ac2424a8e0b79a125a69178725a86187fa2205795b76e36e9ae74e39aaeb0ab20816d84e716e05336f8142790f49ff2db050537fcebb2135261ac617e755e18dc65bdf6dcf80e2784a5076659955f28238ed57cea0aef2b40c341f3dbc6aa3219f690ed0daddaf3baa095ed0e56f6a5b0c79167575861e3b8d0d861acdffd9c14446bf9914ff1f881cdcc71d44c5ece669f45e708e4ee268e08b8b4eb6e5a2ee79434f75775f991205c923d197eece253ea53c1fab286ece9a5ca26dd8b52eb28ba39ecf78ffb7f97ebb095e37d1bcaf563b83c7906c6855d82fa45a83c6444f49179224456cc958c44fb288a4a136ca95954c2a6f29687b7a134b2f04dbbd3fd388945b89b050aa05887e56c0330fcf2248702893427a5c03d1b9b457930683db1811345b4d83fada9e6d8e5cd6f0e2d0481077195d9f8dd716f26b8395a23c318dfac112dacde9b842a7c94c12d3fb35d40e6493184cc7f0c45d26d7e8a6a2b1b044fa26324c7f13c9f51359c788372cb391aacf98e95f4bec472e99013c3cf84d694f5f2854cb1853451cb44b3382b7858557393b529698ef5fffde892070687fe65dec8eea592d7129f5d664cc8c899d18be7e6e5aacffba3b8f940a26636a08bba839044a75948d5965e42a9d139e3f3f7c720744d1f737d225f435b226f54cee658a5c6238ee492cc8f529c874a9d92f8f581a48dc84f901c6f0ba3e37c045085bb3b054717506237ef35ebf9653d5e477115414d7609c06265537d84ab68096c98312f85327fd5feddaffca1e79389d6fd50a4858dc7d08389914a16f83d6a96bafa757fc3eb62612945984f7ec2e5dac71c2f9ad7f3b8d42b0d94f7a58ff57804b7431c1ae1e629fb86f5e2774ef6cb539ac2fd8b6e4018951c2abc43d9a37e8306e7856813421afee741bf7f52ad5c71ebf665312e736847d3a143c8f8be951d58ad3bf4c101603b9a452681a5ae64a9f6011b3e684dfaa4133fbbf7bd08c766d591c13210e16d5de81f0fbd0cab254803b120c551513cafc2365d0a10646666a63e20a9dfb9d37cdfeb3c87944923e83cd442f0664d0eda5f16c8c23f0c0e4a3fde868127c4706949fb0e1aa76a0498969f2b3224676d9d7e3be00cc2da7d5c16ca428e44657666037cd0ccc2b3a53d7e75cd251e14810a3451cc61f422cb9b63fc181887f33d7967d48c9026685996b02390ec930ef6d684edcef72b185385d8b9dbad829e5d2ee582ea62822e88f976730b1b60bf64e3e75684a5ebc6ac28a42ccca98a89cfe3169e4e8c5798dc34fe15d15914f4f49a25fbde92dc39837293e93cd604729eb800aa2834a42e3cfe38bd3bf14d8cc747932ed0723da52a729a96e44520c83653fc7f45bff141e6310fb144bfb9da743a12a9d33a377db1b626c8a29437143570af037e169e0b5529d589b21b5acfaea920888ceae6efd76a8e64bd2482f283759db22f31bc1e4bff9fb5208b09e485bdb08fadb3899855ebcb5c8a1ccfa9509ad203a182ae5284c88b61c79f4f6f3c869d9551f3aafc438d2944a8b94e90ef16efd5c04c2aac55d8d144ccd23fec3c4f639908a99c81039d15dd164693fbf4ee0f3376f9bff4374f11e2c3821c065e405bcb3fc43f1417c960a8f094589f572be9ecf3a3d44ed8c49be7891ef66e9fa43655c500d083435cf2b6a4c3242a2dd7ba6fce9509b5197d7bbe0ef8d7644e143cd3dba34da4ce93bab7eba48b8dd0e31bfe9667e9ac2f974818913dd5ccccedbad9b77a60eecbc6ed7992d6e8b60e7a69895198ac7d92599258da0d75fbf41a5c3d1cc54fdfecf56a7be2ec8ea08991cb4554eaf29f03cee047629a733bc77614acb5342006faa2adca32807a95927ab2550ec707ca6c42527c7096069477644578b9a1725b0b88328326af55af8776861027c5a3e5e6bfff073106e764aa368d1a8115e6737d8bccb0bf9f3f62ff3ab56b97d6bebbfdcb33164fafb4b8fedecceca4abac4fcf3e52001344fd76cb416461b10273035a6356ef5bcdf5447857fab2f87bf8dfcb0de7899459594c24a41f19ee681c76f2bfa6879a0d537862457432996364fba2a6a378e6658e8660931fd0f902803dfaa3c49b22a14ebc4e7dc5b9e03f290b9bf4d9350f006ac33e8f6a20c0dd8b23d52ed705ad9827d5db688698796897003eecc89803fc31227f419148ab6c23c0463be049eac1d79123bb290711f97cfa53f148e75666570e44faf4a4f743b3ef46601669d814fe9866c58f5ff964e663047653dbaaf103b30c44157784321f98e744662ed3c4ddfc2a5212c658c8b8c3aac15e98df0067875e0eaa0f50cc5279b83efa817c5bbc5b24e16e5efe3adf4db47b31762aba36f41ab3ff608cc438ea6d1292b16cd2af4a614d8b678e5dfe43151df365f02a957c7e2f2bfca048fc57d56ff66dc6e2ebad2b25747f39a9fb4dc99b297dd79489e5dcd4e20a68a9d49b4487242ce952189ce4a4bede67ea57582dabc77ed8a91a3fc99e84cdc967ec921b2d4074339dbfa164de74bea7a82b84c0ff148d00530fac3502ffaff2e49156114ce480867e0328bf19b582d368f4e67e410a37e44a4bb7453b84278db21cb521dc1e9df1ddd7ef03fd9b0efb306c485326949212d5f17f66d6b0e01aed011976b835bfbf76d2a6ea81b240842660e598bf17797046c07d176f09f2a98a1441863991585a58431102cc3dc61b76c941ed6203bf3109200b57f0099d71189717f1168b71b37f1db481577a07602534611fc08eefb0b1887f3957c78bf0fc7efbb220fd34b38da5368401837db0718f6f2a48b1aac6593cc02136c4fef49822281e5dc481d9a2df82af48325b4d0f9808803badfb2566ddefc7c442c46d215b0c65f8e46866d5483294aa98f3b0976a41ab2b1b2f1c207781ef71c86f6fa0a144673cd71f66db31d6527b3433dcf6b8af69e88c6ffebab3e4b42afc3098df97600129f81cb5bc21ac3c448a69dd03831ed081aa8a7f3ac088a4d48d4da4ec824b882b38edb26c46e8bc72676d0b9ebd5d58bc6a2672e68d484560ab398da07cf74b5caaeefe869e2e8e9922360f4e56f063d7953f2450d3f079a2297c6c4610600a89e0342b0eaa86c336cc66ab5861eded78367b02b589e8594d485be1256fb6ada0542061a5a4489c1d9931f5cb9223dfa23e539dba65b95d27cf52901c1a386d33fdadfbc47931820da124620819f0971c383deec14d27151ed32881ae3d76ab59d566663f179d6313535b910dce5ac94458a20f886bc776fc544af9de5d6f3e1f839a71692d8e2a892e8e361978191b5f36136c6cf33a96ca71a443415e83fafa9a3a3f89767410fb7a9490cef95aa98da8c8c5459582d7b6df044f94c7b312cf042cfeecaa15a5f2922feba125aed03abf63fa7fb05cd5fc0ef7fcf4b40f1f400b0931cae1b7b2cc3de2c0aeaaed25a6fa7a16beb5b77a2b1441708ee2969172815cd2da686525bedbb9215b2d8d74aa8b7bb6aea75be4e3b0c05598537c907b42e4b71ae25746a8bba01574e60fc87a205c6cb236f5744c7bd9423c92b03a59a0c54c3a6a8d94ad78eded4660147991794b1265eaa9d1720f3e40a190e4f4d0aad2048843a5ec8af60a4a0ad5342b56baa9f666d660e826dbc68da0b70a902597b5bf6ee0d42821be35fa5e58e78ae1ac7667c0ed9cc621e0069bc84f64e9f03fba6f115dba973021f31c68462fbef253510e196651d4fe2c080c0c892431aaf6a16b5c4901c0c600bd04c0b5be189959d5de82108ecb2148620b24e2dcedb5df7b30d03f207d86f5306a86ab8c24d5e3e40e325b945813b8d0a3f4341fb214b2cfae4c9f83ab99679307a15ea3cfb3e885e129b60dc747bddd66dfb0c18751ea85cdb969d69af0adcc0f7921e6f7c66c592681eeb87e023d07164cf88392c69c4bdb2e812ac307bfebc95af758232f5126c1dba36c00fb423997e174bb501a0c8f688ee9aff113ce23c08a56d1c52ab67ad0f3ed82437fc3863b445dda7b7027653e2f3dcc3ffb424ef90d314eb4f569accf7512df6f43bacb65055cb6dcc2336158453073b861712979de662215dcf5a746aabca9c616016cdf70ae42d3f02faf01efb1910aa9dd888b67eddf2f39cd804ab111921b0c364b9fdbeb54b64e7e38fc9c96cce10d38e2dbdba55177489620d4e19dfb25e10e0dd0cac50f4c97a2a0c4b53e465b9aacd837a3a1d4ecfc0a2d8abc18821f729ad701cfcbc972cbe3f7835d24ea84463359c7d748ad745473bde225e39655f3d4dffb60d60677be32a59d05212d1cd7b521e4a7905f832db6e4d2a751718ba5a64e890abc1daec2e3026d360acfb523bfec2d67d1f7be993328a347fcdf8d63ad8df6e30bf2d235756cdefc182a7ad8c84d55bc7682ba0cf7ca961afed6633122b6d5cd13fdd6015152e2d072c381b318fba71d2d15a6e42e6f70130c2ea8a93d06c3288ea2fcdd77c9e11ffa577558b14657fcf655e5838ba71e87c16adf7831d8c6d5833a9c8c773659d52e70f3b854ee178ce150f6c5c634a8dc5b1fda2825cd254e852975196384015d7d81208520ccced331d4bed96ba6900818dcd05e6841470a8608778c037df9c5e0a721e0f88d66751417392d41de8ab9d4e32b9d00a7e4e03d5cde43d6468a11166895e854746193bcfebd0c85c96e6f62ae7d6df410f9d4f2e0069cbb0f2139bbc920f8d7574db34c49d39fd797656fa2fcae914d61c8d7e507ba743fb0b74b1432d58d68f507d50fe77e5bff63e23a7d25c6eddf0cd8ac07cddc6b7b733aac607df46183bda6117ace8f1c2e0d9f850cc45e359ba9b439a6b89363aed5fb2e8e08f6ae88da76e5973670d7f20e6d9d08ace694609d9fc232927838da277161db20bfbbc28634a7bf0ec13ab9736c20de666f6263e283a6dd0213b29add838c1998191dbff49ebdd4075bb5a77511907601e11997800b43e2827e346f1d26eb940067136d2ad753944835096d5f2f4caef2c26ed8f2e9eb470abfae8852aeb78a5125f7ae6736a05229a827df2872b023b6c66697a503e6b6666100edc9a782c97020ccfcd95254869c54047cdb236fc2cda37d8185bd2379dd1e681fc4d049294950b0c9aa0ac67d20fa6b0a1cae5640d193644ae16c7a7d9ada8fe2b63f846e9705d642b37cdd5430af6aca65be00d94efc203a69c3ec0fd7c2d90c9736697b39a85caf78f8bb13b4ca968efa2f875fac72676acdee6b772054e6b07040218f400358ff4f2e0b729ee89f19f114ef8b62d4114891f20a5b6d448ab3212eb94e7f281fc183cca1d960ce015bfd13fcd6056c9b69a76735adab736a79a90481fb3a4b0ef4661a62d2b47ae4ccf3b2f4737951e34514f59eb48428fd1cce0b243d9ae8bb344e5e4c1d846feb8005e12f5e9e7a8b781ea4f93274cbd53c65c6eb3068d4c9dd96204f751303d9a3e7dd6948a68c974fddb41f9f970c3d7f0d175ea8320142e0d06e1ae1633a8bb928e92ff04dc5a9764883c09fc8df0313cff549c002bf3bafe1253bf1301c9b459bb89791427c293c9ae76decf46a4dc6a1ad3f17cd782ddc95c1de6a54e020d8be5fd0f71d58e385bdda677b3832378b34c6de1cb300d2f72ce2bdc74532c94e81cd2eeda93ab23f6f7a92f6fee7cc02d31a12c89a64510dfe421222d4e41824028fdd7bcb41e7d30b5793493777dab1d5907cc648d917f8632396098001a300e04cc265aad5a192d22737667d1b4a013c93dd4549de03f84dc6f73f52d3a7bad5df408e0981e65451eedc926f289fd047aa21d967215d2801d0782ba1fad26051777d73cb5e6bc63dc1e35bb975f57f4d9dd38630dfe02fac051b11c24f2ca04c852b953e5ec11ba6456a436bec82d7d453d7af051dc0f35aad256d20cf926933b06fafd2eb9fe5a127f7eb02acdfe9a95dd258b877ea3604eabbe85ac2ee491efe2bd4e3a42ad1d15f8d305e23def4f8fa506b1dae72d33375ae1575d991f6c962ffeb8d79d301ff1ba2137b21979637210fa94dc8858a082c9a2a37d054a3d961af70a78413426fdd1e2c3a801d24d6dc83accff07991661ad74ebec4131fe8b7c71adb2d22228e61e6a7198bdfb88251174cf1d9a24149932767c916c444ec36a9238c08726312a71be5f99d0b0ec3302e891f1eac5c804fd6adbd34e655fc0991a3b0018a5fa9eec722c861d9f6ea49132b09c9557e81076238121cad842ba8f3f04743c198ee9bfb5287e9ba29c776e739f212de5c73f6b30c56a5428561ba7885b42c06b6c062b0f55651af26a23abe8d4e8d4b45da5fcfb16a58406d701e1595534212ed4c6d58aea6117707c0e3844153f69bbc26ddba1ce4baf88b5bf8716de39c058eb4c7645b65da191df172e46fad4373d6254d820f0d89247945ca10529e2a16ec38fc79871894d8d560de965443c489d1a2cc2bf344e143986123262ebe6be2869dbcf5f8ed83bc4a535f293bd70349d305b94bf8fb10b5582d04e412f9ee40a7350770da4802b970fc8ba5e494b918f1c59f5b42c8b2057b5b9ff3795892584cc626e8fc77e9f19197226043e0de73ee7360523976ab62fd2a3f2ea0ecad97ccd5764379d0f1355efd5e53949db398b02117c31545ee9f54199fdf177a52f7408cc8d24e1383e0e4904a1b6f23c220048a21898b87c9a6c9c5a71b9f5064c8f9b317be777406a3e6d08f2c5459d75e50db15e192e5d8c6c22d73b4bb2f9fa3b3b483f5ad631d6b21a5e2f714ace7e8c119ca5266d9f8fc50bb8da295933f0489faae5e6b45a2b8469770607be84c6db4bd676876863bf685ad81da7b8889795754239b68fcf01586337e60bead56b5ba3623c1ecacdcded8cc5b9600d7275bb7fcfda806ae7749efb7739dd8fbe0c44d55c3f24e98860c5fa41aaccc89117530b67a7fcabbdc73d458471919ea03e52dd70f676133f7daadcc464ff162595b686ef6e20aab1195e3f9de0decfbf96609a61a4d0dc25062504f7da1503969f638ea3fab7ade42a475778641358c2420a6c844606e86e462f0d3a4deddced225f71956302c9bf3082c9bde0668c3631d80b42d3a447d48e50651fed126e58763cd525b954da6cbab1608a8932c20bcabaf299c8872413e2bedbec4f159faa1caad48ef7fa785ba5d1acedfdd6b01537d2319c0a43d26cb1598e1eb1a169d814a6ad594ed157bf21d1b16d325b4f8c3cc7c3ef117ada26a98fefe10417577dbabbd6e8b64fde0fc7b838822b0e7e8ca136024b6a7ed1088d815198da78565736ed1cedda015685b8ce621888b5cb9628f1180081cff7e124f4da5b0fec67c87e2a3eef4aa50fa2d96ada11dc124d0e1812076ec355c50139e30a7450277755e8774ecc046850a8126d01c0dac9ccbb522efc8b0d737cbd9ec7f7e16d81c12b462618324a01ad1fa587c96e51f19049001ea6606842ccee54e247d67c874f959abe9124c79eb55190cf76d8f1d892cac522528c18a15a72b01e7838ca445d875783767b08e07acd054569afeccd4c318e6c9abffcdac7aa985d67229da607162fa90862425a81e7ed452e175ce830d6e23eb7403c145ffbd33b4464c9a538fe73a906da665e2e377c1c87287e2b73f5d338716845ca2f4d64a5ebff1120971b0a50e7348cfb9516d41bc670bf82102e718120251c06be8ff3c77eaa5b8b32f0e6668299a377d724bdfcdf709565e306d2da1a78f819d03150367750afecbd23222166857fb8e2c5676cd31a292c2665aedd70f7340422a8f94ec8900cbd70fe95a73b482a57969e41ff945ad15efa1787dfe04c8dd1efedbc37ad3c0ca60ff00f69c7848c57a31ddaf9818a5d8a6b4b5e4574b3ac9859fe60009bda5240320a111044b263be88efd6a178fb2f63eb17004552800ce99c5c63be6e26d33d96831e40119d887f4688f8081973284174f1da56f19722c7008cc4b3578ee0a93335a27cf1e4857e1c9e2e845daa5e3e563768daf6f2df8f1a96e4d22db41129d11d5bdb5c7c7e9bc7aebc40aeccac8232a924afc21f7f5dc3896f31ddf0f2620ade5d64c02d640634766ea9cffa0986cfe918c4dabfa2c7aef5593c4fcc88ff153fa541dc107e528979dc8d8fc8b4534002f2df2809c418156fedbec286a2e21ba83220d5173f04ca7fc03de1c02d1850dea9234344f1524c11e50ed491fc8e97a9be64f7856c93fd125a0a2dde018442cfbf09c650dc9d7cf799ef8e1433db1574f9159d72941b0101e3dff31365cdc00701e41c1e39f8d5c23ffec3698462985e5b9e2148d434a8e013e98bc39c5a3a1fe119f4336c46ce1931b9839f635b0c6285c75049efaecc87b86ea7463a5f179462a9f62a8918f8244c7411f9dabd8d9e31914806b528498f54658b6a25f6da458fac662fa1c1f30f5fccd86dbee3a2d3b3c557e9b7bd9fd8d75a57ef7682b9f780e59684619ad58da46bf026ae0c38e9096efdac53443b0663b6840a236137d1f99249ff01891d5250317d03e96fe5697c87cfe003d6ceff1b7ccfe373351be08c0f7f8a832805982b0ed915efc5f501cf9cb9680a9c546e12070f17545a5e9456fd4ca33e297b9b4128b925f473b6040125b6fb651a9244346b21366942cd414500edd9c1c49b1d9c17af2b22c3d5e79dc9dfa51873d5315271f046fed0b89ff1485055005fd7e30f4e49d8abcdf6c59df67946be5bd42f345fc33ebef5704b07722b4ed0ae413519fcc60daf0b2edcd5555a1173bf32f8bdfd848c3e1540443d729c94b907b562c804cc62e7a9cc0ef917ef0f0a473c8b238f61905908466edea44206149f8e3abed2c1a03014bc61282df89a9341c9ab2da7241e59bce012e22adf1c76e6c99238209a47d95dae7f13cfebe233a71c5f8e170b00f1bd475c94265a5e73552ac42e047909c5719337a9fee86b4930c6744562517209399fdcce9357eaaa3cf42c7ed6634d5f3416fa57d70c8de3ba38f61464a3a459370e06c1be5cf2257c6eac26bd72ad5d01605fded9f9f38878d2bb0529b63cd22753b2489be53c0637082997f42a78522f049fab90621d4e9e89d8fb1cc1d1d7f8e89cda7eafa084de860b0a2598310f83c019d02f73de3b914d295b0a40714d4f0ee5fafeec8e3f2e12c1f156730ed7bcb2d07d1208de05c971cebb98631c6fa4e79045f60c85d9f34c3b7558267a61a9bee81838b91811e324a8ae3dda467c54840607f23c9de7bbea6efca5c293ac60232cd822e55dca9f0be091d1275932e8c593648ad5fe2fbe5bd4cc2713ac9f9ca98bf5b90fd04cc49461753a9631172308e614676d6738a339e9b556c8cc7d5168966e111c69581a80590fdfe59e93a0ef95a8f265a824fd18afcd995e2e28f2d70455392272980860bcc0d348e5dd09f49762fe6228e58d442b3defc6bf300ad02faf76e40d3a7d92c1a9f3df3fb42e12c8a0d8445fb5b4b0e51c320aa4bc171695e88731abd2244b63ce57e559e78a9168d497ea5dea9c266c618f17b136cf5757d2d6d3969627cee14adeebd3b402f09c0a9c3842110840d0d7fbaef2d132c6bd33ba0768dfe6e73e98b31fc1f1dfd5ec04899dc35dcd4d2632bde026dc270aafea28d592027503c1e00f402d055a6bc3e13a96ac6868ac082458eb3fe073f63e9c8013d677c1fe76649a1e92b23288f354777f583e315b128f7559e3984b2e19f54150ce4fb300aced24cd63b431fd2e705d5a3bbd17e466bf9021eeb2ddf2187ae44b63faab227655b146822689a68efbd5065944fcc229b88031ee5f836db5e82342565d2cb42a18f34cc9c06c0d0d5e86ddccb1ff8404902e40a5b880178e2769bdba3a963ce4f9be6785e1651e0e16088141b164c9a3dd0af88bef7e53e5c6304777e7432404bb1a70f5672ee1d2c166347b5adc263934148d4a532ae8201191e1c951701bacdcded4d05e1c2e04b82603cf1f58b9fbaf809940219ece49ab41d8c057a9ea43751cd3bc7668142e30568da70cbbc5a0925cac002888b28ec5c621be6dc655f414b930d850d90341fb6a5800540472e8efaf4dd1392e95c2dba13bfc949d1f30baf2a333999dd311ccee1beb2ecd09126afd5b2d6fbf3fdb05aa2604a2f00928d6bdf652f15e6fd70f3c9f0f0b34a863fe2b8f44399b9472d8edea38d34ce254612f21aeaad74aad56cd6325098f69a6e12d9d7744785a46d5c0d44ab7d649eeff640a335175bb154ce1857bcb7f78a76cf6f4dd2a95cfb96bd66ec2633ceb77369d5bc305d2fe733f75f372944263b7189144c1f65c2e51898c177cfb473f32ae71d5b1a80e048e3cd85700b552272b8c6b072175295038bac7f12785ccd5a48965e347d4570513b458e02f4bbe4addb6242a31580dc6bdd902dcc711e7551ac939be40a71f9e12b4ce4c9c50c32ba9fe7269d629d012d152740a3333ce0d23bfb9494e5fd6517fb00495f9f88f68f17bf3712c0e862c8bf06adb97c2a9f71295f1fe95c32b66b2db40ca8b6040e21a46f4ee01566d0fa53e447ff4f62c8e7ddf00a02e5f238be80e71c2ac8a73670dd08ba657e1fbf6fc799e179bcbeca8c142fb26a58b850ac042bb884ab01184b7548f79e65146c51311a803b6b6acdd7b27895d7c4b771fbe97748566de1d4d89533a9e970d2b3c91b4ae29bfaf1b91107f3acfa6f442a6cbeec0cd97e4ac40f81c99b40a11ebeebef74f300786bbf62698bd540cab9b8bb3115d1185935b7bfe6e086411921a3e0a81edc33187ecbc29036c4a0cabe541a56b552b6e4bde9597215c093372134387ca4637b4755707c786dfa0f4634d0ab751ab70a3da837646e37e9d2a81534b6a725a085ccf2fa77424815a8c62b70e60db2a760cacc5438d059d64658f70678e1bf114c9630836e72c44b5890bd320fc4af32ca0453f6e78592c2687508c9cf0dd7394ee3a34d59b64ff331b26da714c0674e3ee16a779131c63a6e60d727a3447f03e93cbedd84f3816f76036c7bdf31ee0ee9897a2c5eff2efd90d29c8331eeb6b63d481be9387016458eea70c34fa6a5a7fd2b58f18a1f804d53a6effd81a84e911a195e0cb085b9b8a4dc816df4c2d5c67e7949d1743d907c7e036ea01b6a3c1e73c3195215f858b107a91226b3b4e186db49bce28f2fe786c265c250522f3307fbde7a8d25bdbfd3c664433f4f923dd122cd3bd744ecee34067f9bcd7b7033efd917ba0d48f561d9075d330f220cbb6a1d29441dffdf2e962b1feb6d26daf58bbf7f44734d1239ab3001d79712b0fd4a7428945d728bcbb8c46936de76309559482089e06be47fb0ad3f56d85fae26f0aa8f675cffb888fbb1d48e33e5bdea3ee9acd55fc3353d049af03ca4952694141475f960a2a45ea20611a7bdb3bc5ea37fac468d6d527b545c967c6d65b8db44ff36f9d2767f5cac0f145be978dbf5d28332f738f8bcc594649395b43202b6d96973ef082ed530bb0dfe719c8ff697cce28b3d40afe51006acdcf243012c8d6a5b1d85613f87c873ef170bed8e4bc14169054cf7f20c72251e3ddb75c86dbc2d0d12184e24aabde7823e80a7cb25cad3778ba1ea4a23d3328925b6e7832a7575c1d024ba2a804df5523fd2d61827ab93fbec5c1ce7cdea752d240f7b6b49fd1d0d0e4da285b76ee93d4eae6f5934438a79777f0e8690ec56e0f1033b00b65d91cb7869dcfb0a102c3cc299069c8e6a1c90a28780868a2d725aa3027ba1c4b70f3b21490c978d8f937d5f2dd02251b44de720cf8fb3c24d1407819123b2e72b50a399a3187892967ae6f91fadc554a70fdff0db8e6ed2678c5414bea686e6f05a8d4bf60273b7d232e4d86ea4b273c1da436f0134745ab9e74421bd28a6c853634a2d9c2c5ebc13562d479627b0c58de1a6dc7b467549410deac4d4a189c6cf368d3458e307b28e4ee9ffe78569b75521f50bf1249ed2b2c6322802d01d7ab76062e1bd230d070007dfd23de690e3d6c315f31f98527b756ed3f64be00d78028e78c2795195346c12071d8823dc21736f06d0b0793dc1abfd10eca521dbe58e1b45b3e66c371acba7a311cda95bbb0f303228665fe2d902058907a655d90d8d2791cd737731b7bc771a9031e281d05c32d7a2b2f02fc68779e3ae2184ab47559c5e4fba06c47ebf5acf5894290b4041f37452d41d170e4d91f748b740e779e3823df87dd186d33c9046a3380c26dc2e5a5a50b71837513e7f81b69e7e188720332c9b527bd0a3462fb0d80a0c5db34c44b928b606b39cd7d20fff0d8f37d0bb4bb73fe44b4203461eb1a2e763eb386290259fd69edb0b3ad2f6606533d4d55b3d144476000b4ad92057f4c2b3850f7232619d35ea41248140087b40e75d576d313b5608bd34f2ab9655e1d4fa0304bc9b1458712be580f185c10b96de153cafef7d4d4815e7e2f067f8fc8cd2074cc0c2325d3ebfec2c6b338a1de2aebee659d1ec20587ce523b14e11bde3b5d64db24ba5435f6c853a8c3744f27cb222c34f37d359c79e4e1d073dec2a81f1ce90ee2fb43c82dd853bf06e622c719e2799cf874f74576f8c3941dee7650229012e599ceb568ff22cc4c469097ee38d2a7bb6fa465c4a38e95795bb9decc52c8900387fa412c37730e3c553f1e651166692c38c3f04a02db79fcfaa7d4a7e4493908c150d427d7d82ca7d9609cf32ac8d65f23819f2d58b39471da64e78f14f988e92d3e2c86900d7d6b12e7eb9e37d2ec85cbf18cdf95778f602345daba1cc21be70e1a5eeed83c1bce0a3256b2db630b062fe762414b6a17f1c3b4231863e6340a0b12503ad6696730ee781e9a7cd57687121ae63f08cc5e67f9ca6d534b6d3936d92f3f7d9ad983d46df1f78d2053d6896bfc6bc2c2cd97b0fdf116a756fb35c9eb963b5a6a7dd6b16aff8b0ba9a2b68d55985ce99b177dff291db27e7b3d54ccbfc2a6aa07568c59550bf620f23708cd53ecb69c9e32cd78206186e9de296ab4b140b9743a2e14ebf5394887a0809e83e1cb8a6a519dc7efcee10ddd59926db2791aff3112d8a0188e24068da800b2c7926980b535b4b1b25cf2774774e8f793b1e8d865994da83b87dcaad0d72f5ef247564f338ee0546755f325f2bf9dc533064ff883f5b267791efa1ecbeb07259835ef46bb06b17df7a2f32e34bd3e3a7af675e5262d3d5df7c031452affd96911c3eac76417997d8d2859de74b341dded41f07c960e6ecb1f0af073fb5751f29281f2c6f53c15aaa010de327e2786d6d3685c6c9ef0ea3af695c8efa5e0806dd6bdfe4ea160dbdc1120005c9fd9ac75e0dffcb9b44771f47b191878b5d577ea526e76fc150990f157a097459f75c3ac77d9733b70491189247316bd7472215e0580ec82dc2e3249bcb2ab067f5e3a907dc6c91c4f18e44c5aa5b9e457a6221fc9c3b12506ce104aa71ec26e0c40bedbb6f0e75ecd31e8d412ed3604371d006b40fe58402b8fa6ddfcecd88044505a6db9d0ba53cb188c7c7366a3aa582416e14cdcb8bfb92129ef8862f0f1856a3bc76c0142234476b4e285f131584c36d30518ceeda59321f3f17b2b8cae936e878e1af3ed49542ab86bfc48d001953735c1ab0a5c86fda13059f7c9da54678dff0e0fc0cd169007e2be067d718f23aec0df1a3eb80dddb75f1faa9942b6d36d8fb4c9c4392daa3285453644285ea17d089039a4ab78479d47417dfaeb5bf7527e93bdddd9056a6096af72d44fbe8af263e8e591be176048023f507480fe7eaeb2e0a16c206bf26b0971fe3566741926a05fe0aed2d0868f959fc5e5be0cb8c85b76528795fd57ffe7408b42e739ff04515134629c9ed634626a1f10b5297368605340f297af874833985ec26b3af082e57f3033390df47260ad2b1267abb6a4241972c88b4a7df4273886598c96fdc4c98cef2037c02be5baf940a46d1d414a948e14f3c8e8c9ea68a41c378ce4bf4cb18170321fece0e4d9e44a279cbedb7ee21c9bd2fd7a6a83eaa29db89baa516e9a4a3fc1dbda4bb65a86e6d80f007d8c1a5270bb2442ee3035bd4a7397e7199b3b37ad402169881ebffd8ebe21093bfe706136289d59edd8bbc69f55848a664b527c99835689f43cdbe9d91995350e793f9f97c3732acf548f4dd7d2adf8d690bab1c9c257c1dec128f0c377049263fab808a7ab66f5e0d4ad2c7f4a378a969b4d52d2e5d5a984dc2f3b0dadff627f66afb8d090bd4dbc1f8afd1c1e260c67bc44408fd00171617a27f8a551859daf9036725a929f9bfde72b9fe22666407bad453e2cb7a959ccb1c7d62a8d826c4e278c1c74fa2a984ab277b5727d0e59212c4c75bdbaec37e7710a8c7146ccaf942d6f41d83b35d7a805c226b5c538803d2d5cf067502ff4dcd13f52250895783cedfc8dfcd2433924e0ce9ebb701153943647211a35090f70996c65732cbb8ef3893c55d2ec0076c5a9c30cb6a66361e5a6d65ad4ef7b18d9dc6288c7216beb9da0bd0d17f5289bd81f5f22f7cac7afd5f65b558124df36f5c5657e98da793e689c11cf346eb08683f2ffd908ccc736aa3cffad5e3996a2c4e4729144ba8b6aaaab72dbb2b4ab6cac0ea0ead0b1c062d27c2362f510d12a39a17c0efe9094059a7e9e113690545430ceef55c51b05944291df59a2da4faf54a8fac7c7787ddacc39c67811b4639a698829de30a1c8a19eb1d9daae3b60a7976e368f81a5c5688b4678bb6691d66d063d0b8b8745a85234e6ca10f952109bbf21a3c1047ac360f3ea93a7d61c24502f53a127cb7238e2c5ef8e8d2c59e2aad5a361ba5074c32aead3a9afc169d59dfda632c3ff2bdd83246eecc38adf21058d69ca5ac7c2a261dffaa768183d71fd415fab2fb06955d99a4f308b43ef5971f15578887994ab535f91520dbcf9abf19a45598582d2ce7d27d34ea25d8b51c865edd8ab8376698275dcd6fc6e88d74ddb9aa99fdc410b7b87a77b272f576278f279b7317a50e425df29b836551da1b7c0a1dd9c98ce1dd410f1111f6663f302e4fa910be6d130a70d4972e304fd442cfa59585fc94389fd5c4034f1a063607cb9f9c72fd1628d27f93170cc26a3827c7e253d108f3d165ec3c3c3eb96ce9b10639edc1563e952f7c2c360dfa6ecc5c932529499fb5b8bd5a33d2dbbd0aa687bd09c163b8df6d81e28c586af74272c0ce1c10f29073e8e30818911f6f0091f8fcfd5c1a1c72b0dd6c3905cf2d32946907e2ad7b5943658b6ebb9fe948c748cb13ac5374eddebe50e2217c7013dd3ba75c2a8787286ed959f3b19467293c8a75b77179dc9c8a808e8a1201f5bcd98372e0c9f375de25ebd971b977acf484c7e9b4400f4a0a060f43c28f98a2248ff6e039a1a79b306ea613fe36c4acfcbf7f592d92cbce5931bc114a2ef7d9ca6243f900f39fa79ee6292acc10236fb2f37bff7536bdaabf67c0d2e5753b6b338bb7c444c48d2f5855719e6b75f1dbf25a716927661eb8e5eb9037d0cd9bd76551849e3919aff320ffb8b6b63aebceb328ec5c1b7ec7528cf8377a8f691c3969b384c05fb486381966970a998a0f7337b1426f69f05ab9df6c3fe8a7cdea1eac1dba595415e9df376356887cdbb42af187cce36784082107a278fb0c90606ebf719eb52d52330dfee7038dd53d8905b04dcec9b48450b2b52e09ade3063fa38d6141051428bae62c4a5bcf7635dea988e1f4bd426d9bd9c3722df8b5e3e8714dfa5af79e7ed7edaf96c4e16f7033ba9674c69a0d4526b921f3117605bdbb9e38e2cbd05fd9ce86b9298efef712af23738511a56ae943544c8128fbc5e4924b1fcdd4e06ebfbb77a6621912a1f03c9cec9de9561e9e5986a5535213371efd3aa0a38d9dfef1ec234084b25e47927615ebf0d00bc224527c2fc9c820adff9cfa3267ae6c71628f608b8afc72d485a75baddd2fd7a3d0e0d8cc26ae1bd7a136c909a450f4a7edc772969262a8b8582051ce4830b40910dd80f46cec17aaf141f0c6dc7bbc9ee2b740dd6b4a1761d293e9ff4eef0c9685a43129eb4b5a3639218d4d10dfff5c0a4fdf0a4ffa2d97503dbc48cc6dc61d092fb6345c60350ab76834c2a6bb058140e44529cea3997c9546bee0d64676bb4526854178692cb4a0d3ff89508c1cc55a2e775e75a0c0453302ee6d522fbdc4f0089385ca37412092edb04130aae0116a4fde06a4dc46846e63357e7d5e6bdea47dcebe5d04358d26ecfbd2ee6d9fc2f4799de28b351809c251153221aadace8e01a483cb30f84aa87381646d389923f285100b6c550fdac9cb7cc0c87f95b277647b0d9b706b06f52e224566c3ef167cc4697bdc505029922f71c2b000310adcb368977e726ab6c1008a25cef20186ae3aa3b9655cbaa85b3c17ee8a80824a77b3cbd6a053578bbdfff6e48b7485a0b469647e6e1847fa6c98b920f3d0584b8e41937108ef0cd778af90a7ad3aee0cbea545a58caa20418dd09ddd2cfaa339823a66a4966fb95c119141cd4debf23ed4aedd75f3c8a2d8a141b46653337c6ff31e7c9084e346e8fd650ee5b789e75c8f1c3660df2bee2ba70e89b228bb7e6e9847d50ee67532c51a255ee69ff52797530b871f3460180c0fc1e0a48cadfd220c454faf367705462d1832f6a7557a7c02ffca81a55af5724cdb2a83c21b1da1c7585482a3bbaa24cf1a3e72ca728ca71da377d060c1c2dcdd7c7ee699bc8e532b334c1843a1083da244aefc6ffc781e2593ba9c9733bb9b9ee3436a46f55ec124f086cb63a5d9cf9ff214602a763c1e41ea3a52a8ef7298b67944223c5647d96ef76571e0b8d416d0d848f5ecdac51f9b05432d887bd9ddf39fdb60aef6639df0dd514c435b5fe5068128ea147be4cd6bda8d1ad2b57a97c9e4e9982609055c5e68037641292126b139daef7dbd7a33d236c4c08caf0d749796fd85fdbfe1d96bbf8d98c7ec9ef4ef8397ea4e29c9923ed9e2b2a0640231b1802c35c9b596d22fcf718cacc7cbc4fb84f2f431cf7452582fd40b03b1c7d4b10d48ee17ff9361d34f194e2bc93d342b94d1897df38134f78ed1c166e7d4a2745c1d15c7684ffc9d55bf4238fd2431218ef33ec009c4333cc5c974af4c2e42661d8092ca74d61fb3b565fbf843d4f1f6b319d0715c56950747f4c54525715f94b4e151427ba8a617d0d04365f84ec747e9b19a8f184ebc78a1c1a06f342a05d6735b9492f60bb28eb3592d5b03047ca730b8e968dac86648749a4c3b303b8cf9cfc1e0a1e95cae754a0c044101a3d55f0068446a269caf04307d255740b8700867150c3c1a4a1de6ba1c61f8133e26c4273ac06b14c99ddc4314ecf1c9ca9213a60bfccb0fabd84df91e2a4a31dfbfba8ae55c14dcbac3800990881cb53aeff89f2bfafaf1cb0670d27d5707d0983c3263ff74cd1cb0ef31ce6b3fe1ca09becb49f080068739f595ec5634bed5f2a6d8baeac4cd5899f77e4254533c07b697ae0d436c4f184c4efaa36d7031c52d16d9cb6b2a01bed9291606e7b85c84d8505dd7d4a6ef172ed5041607c5d7cef0fb88793ea66c33eefa2df1a520c707e4f0f013d27644c44fb5366ad250c2c9eaf3e0a2e5c8bed02122bd9868fd33b599b6825701618b6c064ee6be4a6c0c7da3047f4d1b1e8329f2a9d6c053930b420332c0c1842b1976ab2f2b605584504f5448cf550b7f88d60bb867410192523cc5289bc563d441472021f4ea599aa9e165130dbc4e1149a651c06684f41d70ac42a5e9d80799b2b52867bcce84a837542f37fa99fc5464aecfa908423821246165fa258abd08f3d270309d1f44f31dd2178843b5b9ca87984d30afadbf4dd04078ba1d13cfdf06fb454bf451e13428166626652e5c555f7b27c2b139272201798b805b83b21feb87be5db17a501db643fe99f3ed842ebefa29c3594f21b55379ce9b1810a3f6e904238803b170c36d4df76ff8cab3998d69d2efdc9e6d34f4730916cd5c4bf0889db64a014bc282e595f297a614b74be2f24033e0325bf11b100195ab0aeefcd541391419f91cedb044a55ce69f0e222dfab3e426e2dcdad5d955f47d0037daff52d7d08f1757c41a010fef591a514aedb3ad6d8941d2a3df4fa32c42cfdfed81c77fa17ee10330896286269e4a3c0bc6a4f179cf011c9e35284d70d89116b88fa066d3f4bf3f74ba7136d314090420045c921885182e3e4730b87c9684d9f760003517937295378bdb743caceb116ef46d168e1102371c7300dee8ef11ddacf5850b2b94630d2346b67bad5636b5fc58e0f326b4a5ab48a9537ae42831141bc13e4b30c78d98761eebbdd9d309d64152ada1cee9545aeeab586da4011e05b784d9b080571218623c5e96a6775170de8b468960efba1a6b436d87b57868495fdcecb7c7a48726801288955941af26a6d8c83627319a5a82bfdf10f91bd4fbf3aeb8b6d987a13cb0ffbe343965b702c3d7797193cbffb037db65404cfd9a48edc9a064b3ce0c5d02d08bb10b1ed122a27e590a6c767afc94429d74234067e18a6180f1da4c3aa9c51941c498d4c9ede932864c8ca5ca9522283f39e5fe9ef5a59808dfb6c6c9ed7e4a156b39a1626e5744bb294b673e7b95c14e4687655a1b52f324c95e1bc1f30317247c242a3a183c5618cf26ea13b5d972b29defefe936de2fc23aa98bab7d6dc174f23eb3c291d9b7bf11b51de9c629603d6b68a44aa2943508392631c9500772ced32c8a8bebcc59845218a14a52e13f50e9831510d7664fad2b58c37b96714fba5847fe66e910040aa67bd12d5eca05e77a2db23f6445231491266e6a4c7fbb1225f9a51ae6bb29bd325fbbc9c0ca1f4e9e35b4563675aeb51b92742ed62626da03dc88e0b423e3eafe506f6f2c1476bbae05a37f417e5c7d1511e4a817c382daa4df1eeb8b0e3eff309eefa7d82b0aceb1111e67a70dd0a2f9329d2b2e00508caf21832f7732f287c43ee2aea89020d3a65d1a6d2f3a96c3acc8a524659d62c564210d41fedfb141f1aac751257a52e95ba638d3304b5c0c75f0373e63a3686be88f84ce8a40151966cb9e90a2e8588a9662f22886a85b40441436b59d73e10c8c295ef7ac2f7abe0e66b1df39567c065b135c342d16629627ca792bf0ab333f323714f2d9544a67c8e036cb3d84f4bfc58dc39cf4ba2ea32ac48b9f3af2bb8e292cb58ef103e82c73a21416d86f305be0c884f04cb46c2f8855924df27541ff92a60840949fa0cc8168dec73f13eb74ef1066be8a9812eb74bd54734d9367b8ab8f6d063f9af80ae224db818b1e9ae201fcb80b321873989587e978cad9911f140c66c5affa6225db20b3b72211bf2d825f5df37d30a3626992c09578f6a5e8b79a73818ddb87e5feccd208ce11ea839812ea6c2444862f7116c225ef23b472a4c178f8a3a6162d04dfae3195229b9742954e4135c9bb3de566babed4ad191be5809f9445e5ef9d025af30dc1d2cd15405f1bd45e7a84cd50f3a61c991bbef24718aba16b9e1ad5d1669ec65adba387f72880d59141760167c574137c30531c81d3361a75c50ae7f99f91bd3b0fe352dc0b057a5f9d581a1ee90485961f16f71166abe2a7e933a1479cd21519abce71fb5fd16990cf8a87d4a30bff3f2c24706cb76f71ff8f81aa555830e0d2168cc3d09bbaa0eab1a644c8a7a6c867e9e6825de63951acaa3e21722d2fd39c248673de87672f9965e026b3c7e4b5011741a8aa7b1bec7948cdf4ba7bfb9663baeb5fc735ce0556ff0b21501329b6e3efc80dfafd804bd1d12da1800271e470f3566a93f5e8c164121ddbe0fe363c999c4b234369c6f21d01906831c8ff66a051b04aad430923ff60e3313b0f3cbe83c4cf592af2cb0ec3ac0c7623f515019a9a3884c2f15c480e0dc40f1c1e9d51f4330b04f3e1aa64b8e992d31810f7bacca8c049b40737c66317c7af134cd61b25068e9934117a0b4218242244c7b3158fa92535b6d4418a882040afa8ed68bbd07935c3bc48a0259edfe9848e16ba43811060c2b0d4e6432509ce0bf696dc2b293527f9d0bf33ded122eb2cfd44462e6ab6d1cddee2b874b7af1cb04f3762d2720b9040b0c183f335147225d8cac5490a53dc9bad02a3295c54192d86ef440925ec5b1caf20dc34e8fac94b6c4924ecaa49afc0de8f222c158f35621aef1945705ef94289c0739ae588a11dd0d87e4d38e2e1a675e02408340bdc9359ec35c214bc3f2ba1bf91b3e5a0827404a5f89180e0dc964cff7d9abed5fe261bb9617226f917958370aeeafe509d86d322e290f14a86560cbacdb5bb3ed2ac5974d544d32278e5bebd83fd9727e725cd189bc6c53d40d465820f3f34aa3da1757afa2f8599dc3b6e58b6d921942af2d07228ed2c2dd780b6d31fdb64f50438a36e60b9f2d34d00e07b5fb07198ebd1407a72cbe1cac0dad78293e17c14f5320004eacb2fa3f9186e903be2454f91bdbdaff428660f7e854d916314d84102ea1979248819325f02954c83196d008615adad0d09c518cc55364f7f725e12d1d71721ef831cc66baecc844b6ef3750552337527d24f6a0f8dfe3b2b6ee04185c7219d93ecbffbebe02f7d08c9c582201757576fe231b39f42292e562017d4c8057aa353002884fc1ee48cccaa3a4d18f98ec60ce47cf9a01faa17ad2bec01e80a85791754c49dc18065560a7bcb1802ab1472ca92fe4d225f3b61613b1a5109a19e6e151502718074f6d13cfd4bb822c1106018dba7349627b68525428e8ba9eb930b4fc568a011e90132ba7f51e4c5330fc7b36a15566c3e7c5c4eee3aa455feba27aeed6e14e441f7cdeaa68ddf2cb70d16453f4aa45effecddd5140ee704214bf2bf8841ceb2a7f15594c161e3f40fb80b4e5c0567c26edf490e2f11d2d42689b30409c30824522fc8c035eac4b451cdaeb90327fece818a4cf148c83c3d34162a37e36971d98ba82d5741174b6cdb702bacb0811aab022b9d349f3cf8e7c02cfdd129fcdd0eb55af69d9cf08618199f0f22f193aef7476033a3d5f9743158fb557cc84d4753c26be9e13ba1fc11ca3eed562e6ee20e739c7ec0ac3d2c8fd187ad580cfb179713e9cfa185b13c1391c1da44f0864e39b0e3fde17475ca0c30b97e6c9a7edb8b3bb6d169cab613024b1f186ae3f963e92131593670075dffca419b483f0146cf2cb0851204b2dc49b00b1c61194c5d2cacdec7c283842b759f2231fe70a392469c27651e163bba7a5f2f850f3c8f41ce123d72481654b8d2eb06a2f593f70d738f376593832f79ccf74378f7ccb68cbd864e025e872fba3f8c8b7d1498e53fd655ffc6769d3eeac86a2308d02782e22916683f7900724539f0469e6bbfd7ebbcd52ae2b02d3536863d27c6f703b956f90243088b75478d073ccf09f50b11322e4f566c534e9fbb78d7b073a1aa2af9019eccca7d455118831c75331f03b37784d7db35ad87f613d9a688fe6b1dad585bd0e3042058ae9048176abcc69a39398a4115778f9de2343833728cb58912017db48a9692eec6369bfeda6f1a47fd45d00c2ca442455823d9fb8ba50074cf188be857631a856c1770c30050c3e782b1f0ad4b682d24ae6dab65881f9c49e6960536f4e58798eab2ae1c11e62a824cd3cb78c7963a7f358a49c648db76a23f2379a43d7c26de9aeddb0805a532ffc74bcf71ca629633b072ee89e4093e91db4a354be30c691e1ba0557e1b8e67094dcf5ebb5849bb89fc4d7b652d3a14f1b202699816842dcefa479534c49a92be6474f73e88d456ee3851d94c8d04b05a76934414c048d11b7a24a176db2dc79b1fa09265cf08c75eae577ebc499e3fc66064b63797d2b29dfd12ce4efadf5d363be1e0c9a1092e199a2cca581e08bce8c32124bb8c05dc000b36f0058886a74135f2aa31f03e3e7491e6fd4662d6efa8e6c92a1a8ac7863a7a986414ea4d1f63c306a052f14fe5433e0b08ba05c41cb8af778b70a9710b8c07d212f06f74f4cf186c843d942a41e4510024f921c2093845b6c2380caf3d1c5dbd4401e8a575f0b38f94490bffbfdf9fea1b01271af3cf9e270266d00f53c8bd352f8adfcb500d626a6bb2571823b199bdf064c32956ad91c5d8867eb8585eb5933da0afef2f743afab057d74d016c9fee958198b3e9f135c5edec3af214a6fc54c872bd5fe507d3965065e952a74fce030e5b87df62648388d6d3e65871fad549c7876dc30410eac094ec7d9e4b9102055e3b7594cbe1e0c921b719ac4d15aa5be1f38ff953d75f71720d6f494076441a9630d49ca2efb78591fc4cd0fccf03cf86df2408f1c278ad32adbd31431c7e7416e74c7fb5b7bded863cbf472b14f02ce033635c05c61dbd0017b135527f25e96c33724c47a3ee5e2ce8ef745700fc32d8bbb884deb4be191adc987ffb88c13b1e0cb02056df2f90bde0f34795afeae26fe01766b03fd9c32e228b5e9d4f018f028c6da947b89c89e4b7bfaaf0cded931dccc513c01eb8b25af505a57b886a45557fa7885e8c7c6a0add90e2b2e9bbb0a907bfefe41393400ba52e20cb37e916899c97aba130eaae640be29835314b0178859788d6b169f575698f0316bb7dda4f5f8d4cf2b61ef38526777338e037962c3b44b2bb513dbac0cd4dc03cbd2297cdfb64e50a5395488f7ff19f472c5b920d5be19a40bf67869629f305802d73de61ad2043cf28b5c320b37a4e4a08779cca0fa19ba2a030cecf4052bef453c82815186e5de33d5cf559b2ba08613e0a4cc503f93e40536afa4398f3e9636f38643fbac36f7e0b237373fc30b88c9ed8cad4927655e1a177e417570911a1ac37cb97c930c2c9ab164b504dbd35d8aea63e91b188184842e1bebbb707f0b505ab9d89c31d5d3a68ce9cde7675faed484c340b6ee6c84f2262b910cb1154dac8e385fb4fdd00ad40c9c310688078c3f7027a35ac1c15acec1d746d9fa977aa19617c438269019b0121ae900ba977fd8596fa71717ac2139ae4caf8e6ffc18657718b8848a7d2b5c1d40093eecd16248d56094c86a70afb12d3b7aec736d99777f2136006752556eccb377fcd9e70ac9e622f9ddcde284803259f0407aca2b3a68bc230a4ba0eb9d7059bf6b3805c33585c0d57005eb0b434de08fd9de25617ca4988145994a2934bcdef4398ac596c0ceab53628a092630500db93a62449e2b75062c2c8507267b0930ab490cb86c16b2380e8a6bce0af007a06af0ec235e8e987c2b093be41ca57a04f88112e4f0fb7c1deb342f607621cb5f0e0a317044b5cb1695bca1bf02e66b4f88ea57414fad18f8827677f827e6ea9255936d0e000ce110fbeaefcd6cb94dcdc485393bbe8abbc8190f5b63b0104088259c1092fd33e24d6d7a2014c78e027d069b8f33d9db7e7227e81a8490cd291194a134c9e9928ed3097f2cf2da554822db62ddfecab3963d70691ad0a7e15303f469fe30043f8b4d1df9d294a5a58bc45947db644840e156ad28c7d0f2d783b6f1a5b07a4133c488d306a1807e77bf2d872af4a25a0d224dc5b56aa299b87d87bfe8e20e3ef5d1f6776bdf0c8eb0796dca642ae9f90785969485b7794e23ca5852c9a9870017b22e468e04e493f3a157716dfddd7749f40a463d4019fce6c80eae3ea6328c3ec46aca524c88fd747501455f5516baf562633be0ee5bc47004124289e293439dc807d26c21cb4ccb7f482ecb2da244b66ec385a2ffaf4eac2bd1553b1be01a019008ec111655cef56d4aea8ab581149ca02eee61f995662f713c853293849ae92894792a4422a0134ee9ce85dd284a72250b8e079c23eaca4f494d94d99eb14a98744e640f020885e34f9f99dcffa3cb0ca7f753c38208dbdd70a81ff97f58e79796e1c86d171a713465701903e3c854fdb2eed7ca71d0f0e92ebb656030036e678c0db90d59afe95a5a9d9454a46235d4bfb01b16c10eaf148590bb72eae3c0780e6170ea15ac27f122ccd7c288088ad4c8cd36fed77cf4fc7ae6f6199bfd13a373ff71226a969925f4015a91c6f940e3a9905a8c2d3fad5c960a48e16806cb933c11812dcb7cbea0cbc7875c0ca8e3caa0f71017d0b3e5a566c6c219251a41678ac406e2802c07ece75e9e7cfa1117aad43c320ee390f848fc5b6849df3ce4aea3dd071424b837f649050900a5eddc7ecbeed2ff1b53f24d6b92c4ad53ad4ead71d70bfa284e03a73a4a724515a3df37f198ac2642fc83544ed26feb7407483c029497ee4f5fdc30c472fb51c71653d418fcc2a0b5211f5f4252081ce836d8a3099d1d97301533f537ee4b5c2b4434e2f3d647c3e64a15076753744c8c39d6a45cf485f3b8189c5ca6da6aa927a8f119993effdeda9fd4d8e36d7f25ecb19c1c7474af5ba5a899f9689994c4927bc6ed0887a62eabe79ba05fed24e9cc49cef2f0c08cfa0b0001aa97737e4fbabd52a2a0bb1f9707d73f61883d1927a2e4537512c7bee8d30c3630944eba24df0e3aeda4a76357afaedb878f66648b18a2fee140cecea23776b7ea81702c4ab55ad325bd6e7cc4f92987b0aa991ff28fb077a084186a3948f3b4e5c554c53de65233cb76b50ab44fbd6bf990cda77b97ef679a0d13414468f1a97f8c90f17a8eb91b82c1591746ae24d372016bd7b478620133ac166c52d08a363f45c771b3bd7b99b470bb3355284209b43c091907ad72549a58b8774d0bcdb04522af8c1af99b5bacbd942af2624c2bfe873a658c64021b9caaa97a06794f28ff9b25cd4db9ec0c58c262c3f6174d3e9fab687d13ff4ee3946229d7eecd19d64deb53bedaeb9a3aeb025e5ae993ef267975c1f59895a3c4ebe0038ad090da327491e3571d6e88912fdf024f21389f5ca93733e0b971db074aab2329349c6d2d6ed1420c3799767cad212a8b5afcf5714f44792aad92b46dcdeeb1f304a1b23b3f31f9aa2ab3006dc975b1a4ace97acc59773801212af2fc9cf6c695eda3b33ff8fc0b89bd78665992ebb35b289cb02511ad2bcaf1204f829bb5aa81da7a1b7bddc272a19a9a26394578527734c773afd4466087108e402c6e42a5000950fc0a22d69b9f583e698a78720c4321b0a9f519a882b5164388ecb0575f5d0c27e6a523cf0006707b3a2a7a17a82e50af3ed74cbc3af7f5419789fdd2d2c93c514d96ee07cbe2e2df9409a906ab52bbb474309d6f981fea40c0895d34cbe215f6402b171f88c453e93b897bc362db9ebe81102133906d542a3a62da25f6f224917119a379eee08d90668672387d9c84657e94f37b870a6f9df29322ee835749738786d2bdb19f0cba3962702f314ff851a4c12142f11dd86c30a0a5fd57a8333fc9b8155567fbe4c6128ecad9f63cb9c0dcf84785fa07cc1e06ff1b65c985426c12a561f07a6a8f03a266f34ad05854049fddbbc44695866d94812b5bf32d16c719d5620233427536d567c9fa2109b0031fcaff85bc3dabc91ccabbba953b76ee0760c343c5c5d018920370451b2439b6cb2a06d161c0c4d3e3c3998085306fd55defd52c7b5363d640a515ecaee00eaa1d5b7cb548aee4e2e3443c4fadaddf452b0a7d7de661c18f26ac31921bbec182b5ddedc38bb62b814f43d7ed9d7a0758952013ba253f78da8f66944004a4f772ca09128e5ac58befe3486ec4aebe0cffac116da938c41d3dcb571ae218bd8dc6e887ff311648659d0b0e4262ae0dd67c9525ced5646e6eb7f352f3c84ece9e59003852c97d28b9b4d066c8fa222a0ae8fab5c90eaa90fa378b343526a90c2b1ec35a89f6ea50898d9feb58e21a9f80aed8fe9be7468f8722e544e19491b3fc9b5318055f9ecba936d907dc138d93f00e90d178374d5736d1608d9a8ec0ddca6943be132b1ccfb4f4355815ac4979359b4e24ac994684b41074aa0af5b5628ab5e14e708898490cbbf782956f7a7913bdc4b72debe8e539a185e4598cc1ba52c2d93b6d2774bdd3b8ecf3fa43b3c0ff302c4d76d3da57d8b797992e7c9b2b3b652166c7bec88095c84126ecb0920c13839e26fa35881db07bc2415fd08cbf1a8837bdc68505b2d6fe24662166f21cf79bbbcba9be0634ef8b7e98e1fa27c923a7b217e3ba9b7335213834e7b6a7ea512da04c3f5d0a4c1e76bfb4884c91f5e9561f1f9d1dbb40cd2bd3b4bd17d4c5eb472c7debc4e95d2241de19d380572f80b82e699c741e6d85cb16d51ca3c5bd8fc799e22bebb20986e5530b6131dcd3010935186d08e8219ee4ce0ff09767f46fe2624d2f8e4f3512ce43adaa5f4cfd0816f89c68268e69480c25fc0fd742bfa99f785934aab99a795dd6bb34ef28e349c0cd736ea0d71c560e2310db670eb85f5ac94eba044038df6863e3d1295f93c97d0af71249e8c21668054112f3cf1ec242171c32a2dad44bb56c037638f185ecd7bfa0d37d5db646ce5d6b49488c2f48da1e661a4f8f0abb8268e6bfae2ecde7ebca55b1967a218ea505ad8ed8874d4f7b0de5161be68844005c38a5fd7ce4f5ea32944a7865a36d89e01a673b17264fd1478aa0d96d810ad345c69bfeaf5f0e9bd79e300673b09ec76e7ce2a8ff2cd45a802861a0507002945850326ef2fcc5d76f00bcaa83ffdde32ff7528067147ceb1d982a4bf8b2614f05794a6c3edb6a0cd8ff15d16f96ce14d5d328c45622422af291bef9c29e7305d69a0cfd65d766bbb0df856d844724c343c78d9ac785831440ba498dc709e32b188f3d89ab5170fb4c0352710fb28c1068699ecc460d46791296892eaa770d2a57afd55beadd17f3f052ae59fc17de4dd7c38b4824e1757be26412c9805ccadda69b97244fa0cf636079429f809aeb80422282a1332301138e180f8ac7272d16be8fec78a0589f1cbdd4cb47d8affee5b88eed8c1c96f28c9ee5226765092d18e1507b4f175e5683b534405412e45d6abaa397643baeee06df25c995c0a2c91e9cfec586c70d331203c09f50ed3679321a9e25a711aec0f44287ad10e31538d34d509a5c986da5d7389b55c8c3a84c13b1d8b07746b0f79e1cc1fb18af24e0d9810947f5cc0509f4e12aa0ce88c84918e01b8f1a37c1eda0a34993ae235275421baf58da1c33f01df00afb76d454a1ffe26ae37b176af569cbc5601cb137441a4fc716775c73e11d7a15ee59004216bbf2383ba6a24fd4bd692ddc6a9d80e0db6e2e1a9cb56e3551ca12f61554b79bf337304f4f5bc62334adddcfe9783dbcf6824a1cfd6fbf1a2bbdec74ca41d5df828df4f94c5c7eaab9e3131aaca281d551b5278eb249baa2b06f7fb89eb2568df5128c9a17b9a8440b918a0f0add7c93c48bc79db75fb920fb3d61303b635f7e108f57634d6e387a3fd532831ef282209bf22c0f6da6ab13b8113cdc4c50095349a2c48931e220753c23443c4768b30a1717efb518f2ea47a90092d5033383de4bc923e62af5b3a33d471cd1a84633b9ed115d6b5e8f9add253b0799960dd2a5752f4c5640a281f553bda972a9e92a50c5617c14ce6b1066944a8b5cbdc7873b5951f720a32f5b4be1575a3174e7f9f1bc356caa57ce0a59f49ffb5036db009c3d6ffeaa24ef5b651e36c8a9790453e8e287f73ecd9fb4b8577219274210fbcd9dcfe30aba8e1954960a9fd4ea373224bd0760a66743bad963edec90254a97f3a7d1516afff85d34a9b173ea40c0deafc5d4abc643fff0ead0afefc1a96318a8bdcf79b0b1c50cc70b1888e9b4476b22e608008ce1f45206ee9998219b1bf1fef7d17ca3313a16494271b550c71866e00113d228a4a9514b1cec227d4d11f3b89e10488a07da99c92cbef60fba62abf96ae1f997551fc1c052b5aa0613d1b04cb2468f5b5c879a1e76a34d84178e8bc06bdcee4d9f7cdd829891f858c6f0642f65d4ceb17e33e6822590fb9126a1b7cac0b0b9a594452d108db89688443cf0639eb17f0f601f13da1134499578b4a5e81aea64dd2624425ee15b0904edf1c0b57830e77917acb491654656ef60775fc49c5df664af409abdda5544a6280fb52dc08f2c145ebb2919219cab6975c4ec4461643bda6a2a247ad327b8e7dcac493f7a5d9e2f13a0a6f3988e89881246cc87f4e5f14af74f2794b948908bf76ba9e45ca95139b79e5c7af2b12c29d62a2c3ed632183edb0793e38dc3fc7febf16b6051078508f2fde07eabdb827625ead47ffbae6d94fd28f675e425a1e92c7518a4f76171540ea7352012790a54a418f8b14d4c6e872b33a1fd6abe827bb359bd6a229d72837756154fcb35d6dbc14c55f428d552ed9307b5b65a2b104e7edd2eef5708b298415c65b04ef2ee71281367a3dc06e06f4a31203d08391c610dd5bec551a1bcc0a2eceec6660a8e3c4ff389e22f7c0e93c9bdcc6c5710e6d5e7777fc98026afe07300c28fb2ebe985eb834fcdd867d53f6aa5808b43b588b7133d633d85f9e196b3c5e30627fc8ea9798f6fe3051fa206efc332aa59d56bd99fc7cb39b8d13e4f04a02a5c8cd07eb4b648097fd6734674fc60d6613ae68fec1de3593fa23eaa682cb969fafcb28fcc278f26a9dfdf67a39848dfcd1c94cf6d2190105083fa2aec96214ee56879c6c89243b36caa7eb9abb07300ed48807078c4201d40cd0cb901ce808f5c13aca42183ac5adc33d88aaf9440e75e9d4f9dd92465d607a6bcde210956562514fca485c8eafc5b6a7b750f783bf5cb0c09a3fae2e7c4c06b5395fe9ac8f954d70960ebf67dae62b46833bbf5763a221ac40b59a23ac69f28af07417e0877fa9c7b13f0e2d873d66b4ce8ad8574323961a7f04c8e529dae20a52485d13e448ef76385ae28062b57598967630a9d055c0f371dcfd4f4813f4b342b6f570e61a24ef87dffa75c554a4113417b474951b065f3ddc923338115d3879a52f4a3c6e65e9751420244b33d57199fcd655ab86d3947944032f8c00496c92fa534579938b71f35612109e442c2803eed7062be6074c669433aa4f85da7c4db63c4dabd1c3b9656c2aec27b3a212f0c71d85caf4195bcbc3577b9b0d0cd57daa2f7fcb3b8a31cf43b994541a103253167f1f695fe2d0b2067aa981b9003b855bd42f8d816fa37ebee952986566d68970273688235615808609a4d996ef6823ffcc053734520677a2098ddcafe59bf31ebfadba3c26b8d808c8e6b38548aa14917ae532aaa48092fc95fcc201a313a90988767f67319a57f20d4f9c5ec56fa28a6f0f9cab57a943994e790fca1588e8bc5fd9487aa4dcab92a3e703eaa23f48b738b967cfe121ca277f53a6a06a140115ba57a93ef7bc606cca0c3b14070c86ea629e8c6a83f2717e38fa3d5aed69f5a27aa63fc492ec0b7725382ec90f1a5b7e198cc0bf285d750c081d1d8b6328f18569e08df6e834976cab500788f0ca344cd4b8156a13b5f4225da6dc27be7205ee5f35745beabbe86caa795c3155a80d5f6d773a21d0ec893a56c48c6b39a00544283ce395b6ef487ed0ee788b32c3b7b9c77a2fbea7c091ea64fec0934e2adfecf0960bd94a3360a275246bd86047077bd898c1a5a5a6f63f25e44ff3ecc86a159a6ef6143b996979ce11ca2d8178db5c10fb35d484f503e19b84fbf305aa288595d588bb5328a0781ad68c6bed2d447232fa942e8561440c340e656a3ff7a438c9605994222d937b2aa480b47523092c8b3b4c0ac408b7a4a1e8d37b124699a7f9f1b7aa3b4dc84e25199fdc5ead10c298afe269c3093ad583c5b3ed0a7edc8ab26306ebda50d9b2820a116b7a1a7c8e9455360811d0f1336caed02dc57ccc62cc092f4a1c7e27bc918cdb53eb19d1f5a9b73beac532d43c0bb6c604a79f6a96066252c2580ffcff13e7de5c50ab13a1ce5f4ae37f2fdeab5469f6a3146456b87f3c4259248b2e0dc1ae13c58ccd15750a3b1fb086e594f21651041ba6a0498ffa741ebb22fc3d2983be47064d195f9ccf5c978290d17d630594372d8874cbae2a7e73c985492fc30b02a664021bb6a49223442536c930fba24f8e0a1b02d82937a97474b6f867188901dd6597b7a3fe315ddea07fb86faf80d805d6feea9f9468d6ed5d948cb1c987407d94cd3a9be12da583fe47c2c7cda20226502bf7f4db1b0ffbafb3d600515f840e731ceae5cf526237e5a88b74646ac8fb94892c4f4c5981b83e6c07695bc3d6d23554741b2d0cd6f27760a1ba869c368c2fa4c4f41ea5b299bc2d720ffa56ac6ff30cefd98d3498bc6cec11ffbf9bdd0ea3dc6f2a210f60e829b9b4f1801267a90f624c857663acb5659c2cab4c2b5126da84c8827f2caa9bb92b921489c20161d26c1fd08cee10e265af51348980a563e801fca01031bd78a93f4815f648804c3ba4e78a482a3cf565393ec59f6e0620cd3f4fd20ce900cb4c3a11577d293183b342b60575a8cc1636eae95ff249d767f11746f231bb680ca73297b2ce5fa2a145aeace1398c9034789a94601be6c28322cbaba2da17de3c95f8ad8d8d37bc6f4ebd24182fe2de126c2bc235bd0bd5d11cdd8e67010b9a6fea6d7b99a2357e4b5925ddcbe43ad20aa16718476adc34e162c525069eba2d67e00b33f4d27d2b8684eba7a8ca5e4e5556e2a8ad5a0f49a8c2aa06320005482b557da9dc63cb78f994d4c94e7e89021973198062b708d6d56d4b127fb5050b4c22a99e9f74ad2b8619228ee22c31392596145b218870cb2e0226398dfe3fafab1ba5138c81d73841a0a9b1df885f2534efd0cb30d1fb458b9e10cd1ba3d1981c95bf80dc53df14fc3c4a2da35eb5a0244699b72ff880cabf9e0f406b24759b673fa971b02cb25a26cc97122f6079667326cf8c4f6fb60b0f635d85388facc878537cd4dc2b3eb573a9ded2986c29abddb55f95eabf03fa2e926695003746528e3544a2bc458a5e0cef231206aa17b7e25d2ca9ca71ffb708282cb7e58097c69c58955834b52b78a31200dc1266df5c48af36b98aeac2c5896901785ed804da3213c476c42920ce081e982b7bae9e43c4a0830905ee72d835c7dd937f5dc782df60a0e920b918f120ae747d0f1dfcba331e03f9d494e9ec1ea1c9274b30782677d8babd4df8bb378824d47fcfbc950d9284315bafe2ee04be42837cc714153518919b436d11cec1998272123f2b9bd7fde7a0219bd529c978ca6b0fd6e67b7b38921d31bcd9d344b0820a7c5327c812c38fca8cacba43c3cb0f75a40530addd8fed844d737d9875be3d35f70c08aad28686f80b4afa471e71535865ef3d1dbd45a705b8afb836750c84405fe0b2278c1ca8f55d7b9c3ab6169d5f79f6cdb275e9367fc5224de58b22b6b07aad4aabc28ed9ec6e5a782a4a2d24f6884e3951f36e6d421ca63a06569fb1808f307b876e55f70c82a5141d6f5aa5d044e6f218321921b58d2150e9aa931555d4dcb0a49d4366622c09937002f02bbec5bfc5af57086c642b6a09c87ef281993a5196afcb75810f01426fca5eee5c9b5542f8f80194bf915974a22c30cc3e0e7b29e9ef859008321c488528cf69c48e72093450e5da32f1b2bdee76744b5cecc19ba8ecb3a11e3f9bae4b7418e485f7fb59bb4f102add231ccb8498f778b3e695ed449a7d221198faf3d4a93f79da56dae47365b207e8049d61bcedd61347e18a76fcde3615d1650e7f03314f3a96517b96a84e57083c1e855dbb7f6e52811e94e9e7179519f987a4d10ba9c54d04cd4ceea9a0c3f214da906c425026a804f1d48c93587236ae5a2d99969c6a779f0723f7addafdc58edb8a152c03044846bab4ef099cef39b852f46c5a472e42008b18b988593ab7487577c96d0303794e8beeacd4b368802a6dc16e2fe4133b302c60fe46cdfb5fccd5c76595cb7fe6d56344389b248f130633aef7f6a1cc273a9d97d06ffa0d675a6b138e78cffeb29b421c0df4eb2ba38f12b39db3aaea8d5d68728c67d4672ad5bd3c30d3744927e364a7df0f8c7ab2842691bc85ee5dcfde3601d06c925bf934b544e31d448831d4dab8c1340fc177388fdbf95087537df536a0fbf8bf59de6dd5a738d9dcf1b3babf10867946cbf9db4c8eaa32b5588226f9c113b0653022f371db1fdc3246093c701f8fb6c553fddd7fddfa4d53fbb4cf864211ead3eebd2496c37e7ef93e87dc06ffdcc5099127e27a59ac56e340cd5db2281057c9aeb3d9d29061e4eecdde2d008dde8833e223d371a533659f74b1dc4abe2c729b0426261c85bdf6f0706238b49cd7a1defe5f1e28c55cd3d8d347ec5c661a063ac84142bd4733b2d3ae044dfe5999a7678142c4a7f28be90c3cdd9eab24eced31ed303046b95888ae4d1f88942960e354de4a6324b039f7ea8e165479fc873ff7aa5504fbd71b95ae66246eafc5f69b004dcb3209b94367f0e81a0563028bdf69821046290d2b0f9258af46361712d13d632ff76ee18cc400d98a0dfff399b693404ea94bf98ec020a0aff64f483e06422c14ff919c1f37b8192344519499af26f198f03b60a225a01ddaadcebc2389deb802420c4681185871081287ef99f504c456622f872a534fdb10b73b1cdcdfb559e51c5e89c0b1e04ab1188a2f299d7341c56d49a0eb713f3780c18648af7222b18ffd357f2e81f1ec88d11ef596797730b31aec54b0fc95aff4c9158c8c4df5cf0aad034ac5111c07f8330411d8797b811daa4375844b0b19c668692b2331c7aec104077b22fa41b90fa80680d550d99071879c3ffe240f9ee692b4d12163439e6192e6ad8e6612062c1b0aac8dfa10d7859d2ccda54fe37a7a71179614bc8f6ab34582e73df82061ea6863e75a1b3f008eb1fcecced93f46516d737ad3a372e71bac837405d0bcacf0dbbd14ba5ff82736206d6d2ba9cbda7e7645e48b61402cf4003cc9bdcccaa9b9b8905f2e344e80c40550422ec2fa3c11d2ef94db356ba54fb3b880770d8a902da67eea4e043aade083f1096f4cbbbed3467c7f37e6f3ea88e76dc00d8430f3718199333d1f3334f5bd4c36fb8a39e673180baac5b399f36f7d28a274fe0e680ed6b73c9dae5555ac090796365a2eb98a442781bb06a8c7465c12bb444a77eb2144afd882d96c2593b1ea6f7cdb3c1557bd5ddd43145ddae3150ab3e7f2ee9ac916716f608da945dda524e3362004529a79077f2b5550c6b4949c8d4616a80f452ca9730f3b0c514c9d18a9c1fd522b2f4fa09759165b757b85e96604d625c55c197cdfeac7eb845a8a70a8712e1040fa1840977b33120cd2e38fd5e5840dd97ef11f8a7ac1aafd62bb08eb2424b1c5b8091212fe316addda7cd39e6d65227455c72cc9e2931526641916c3925e59f3ac72db1c2cba568f6901d5afc6c2ba07b775cf2fb02292e74419e0ecfed664777c9584be8bffc7f0f15f7c23da50474d35ef688c80a213319ed03704e5934d0ac3f29365a5fd8c56c6d43a9d345fc115381529d18809e36503a34a698cba17c05e62eed18888730ee59f39cbadd7dab731db9b6f5c1356dce30a754c3da129006865d9d7d3d5259f02278fa58446eb935cb8c4c1985cdf66b59ac3497a194d56cbd7daa1c6f6effbcc67976650546764584f94c77161c501c8236945e73c7181914982a78c950848b344e981df34d612fdc59959874a5df76d841d503cb04aa0c330f6882fa026e11b78f3ccc589ebda45a427dc549d06ccf9cb2ed7f8fafe0754e2663f0132ce7a2f01211a0875612d0296f4d00119fa3deef50e698078694cc14814c5cf5a23ee30a671ad2b7bfaab98a64c0cfef3994fa7cd45df0169ea2feb4c20c38e8b56405fcfc311c78b315bcc16e88d90ebd33df02c904629e6d6caccdd1bb8fd7cd709c030d7f7f25aba5086dd2c94c7baed806fd7220b219e25775576f2a9ccdb61cfcc053065b2553f33b776a9c1cdb15c092efc10b8cd4134c54dcbe5a25a1a2452985ca17d2db59580218a58e0a7e6f8ac317ccb3d05092370c8e1603b5fced6e5ac290dc82987775cc94cd1e81959bba0183451935b8a08ca40dee0e9929cb3e08bb56cf8cf359ac93b3d4423e9d793976cee2d43073fabdf5f50a1bb64839fd8dd2bb41e75432a4ae1156e3cd3e17d332119e8b3c3fe1c5457b1afc29e3191b5a213dd407a024c8b5d06c5cd43939b7eab2627e605e47a0f4b1bf38befcc9aaae2e8301d93db8c9774b06ff281b8cfb3a31bbfe12bae1d0384ebe892a125fdff1f1179400ffba18fec4c590c0ccf9168315baf16613cfdaebee515e27ad8b958e7dd02a7690ba6da14338c8674878644d9d7d4f8a9a4ac76645f25a50783ac964f57fba697c56432e0a0cd34df724a647d75ba7948791a46eacbcd3d6fc211fcab2db097af1315919613bd25e88a77fcb36fb9ba58ebc0231007c540474ee5c1ab8d2343bca3932c4a19e5a6e47a6f8f6df595f5349cf7650c9cf1b8d5622e9d7d5fc2a0d8f4896d1b525f4313f8dfc542e0c3e69efa81a010d449cb29379575c233030426cef0f8a7c0d9b0bb008dd1538c915f6f8d20a355443fc359a6ed518adbd5b9556506d50c8c141a4a55acdd74fb2d6aa8363e8d8bbac961a9d7bbc667a5019f1d7824226e6c04a56b52266ffc6a00789954a592020807a38bed504e84ceca9853ef26740e1c10e682f4623a0722b40e1966a86e2b482e99e08abcc77bb139e2f288175abf4925b164d66e4f4b403b1a19dcd9c069df6dac6645fd6aa8aaf602ea4034d00dda1f26e25b96e680dc099f463224c0053807995f0df08e064ebaa2a65ce9128fce306fb7229e92d7128d02e046d4c5cf41ca22e65e7ece053de0d8dbdae283381e0c03974586069ee9e3e0ed480d35cc29c6a9774f930129e7f6c24609cbe3806f3bdea707c44e85b4d6148f96921a6696d972849ee2734e91a7e1c1fdb415056798e28ae1dea691757cc689166f5fae29f23d1902247b3c4305044231065934d833653db620878146ca2ff86701509fffdbd28bec3a988fd72c0a45503213305c8b0ca41f53ccfbdc01f206ea2ca45efb34752693b8caa5d560367f562309cbd176779418485576b9c0f2f02f361da5d8a59aefa6fd30cb9851f0b94e492ab383ebc2ee496c8ac6e6655011fdcaba0e6edbd5d096c82316a5c72eef18b08f3754d84c991a94c37f857664a4f837d2f0309f19317014449a070292275b59b0d039587322f8d3ed456bfcb5a4ae331f7bb05a8b99625e8e32020ca9134bf1504521e0227329c2e0e540fa32efb46246eb024495f5d21e0305f04ab79d1d086c1c76226d373a190e5aecb1eccea1f364d48b76efff816c0cf0a2d782d994fc1977414973ccc3b3d29f7c103f227e34ddc1f336791851bc5307802b12ea901dcd3a83eed7cc020ef5e4be42b160621d24e0185b5ebf4d94c8584bb29930fc0b462d7b736cccddfc83bbb8903b44d7d3230fd818ff6efb15640d76488bb21dd668578da725b1bafccac50ce0eebb7865ee073d560aebb5d0a27a391ca598311d795debe16ff9c59626f014b616dcfad4b20221346f6cb6d33f148f0bec797d72edb2ebc8aa9f09dd7390b6426be90c473ae82a4953aee53be797d2789ad579a546ef0caabb5802cdb737cfa19c0638153f3162152e3354b3e7fd59220f8c4017b2ee37f4901b5fa017feb630e944879ebbd6841b996142568f4d8afd11f77f494c510baaad73cd0438f9e2969c751e64110d704b84b48e797e0ff4ebbbef99ad7bc49cb06acbbca7d13a636a4d2c558defb7fd2712c893149d03ae65177f7ce3fa180d608a46213edadabcacde781224f8ed955bc4821959fd5e8b4da2a6be50b38b55b53f24029babc77bd051178e5a5cdcb97a218d863e1185dc3f47f51b03fc3616dfb0055f26a726f5d029f622ff5c45d5de3cedbfef8f8552a0db994e921569cd7086ca83c697e8d798ee70676d9a04725717b9259f3bb0779e9da2cf6bc395e95f7cb0078894597f50554a118eedbfb35f3fd7c3783cf9c821f23ae156061a34c3850ef377daa909ddbbf5444547bf90d43c895a77e994d569ccebf9f20ef532eb129497d5c66b0cb1427b7ba315b7930f070d5625fd871a71e3035e652c8298339c552dd25f156dacaa4d5822e8bc8b8bd15c7fec4932d75b916c3af133a04631ed6275c35fac949abc1497ef8bae156208e5658ef9c9b5356848b04c1ad5b90a0302a37d270b6f779123ab5c0b61758c46d4236469f97522fb4f9327f6dc735737481eba41cbbc4882660edbcf3e22607d7dbe5d4c089205571a888b478c74f4187f57b36c67d8a42c66dc9a42c243d7d1dfe7fc5d2d5b12abd4c388c6844e126dfad587aef0c53a6816b3f350e6edc6b386897ee11d067e7c5de4a756477ad329d9f52421c9058c58eeaadb2f327004d05e24c4277596c172d7b47a4262c16eb77b4b3c8aa887245f823395eb39be5505ad7285e9885b91a9eb6728e108fe9c875f4eb273c253cde1b5f62037b4036d8e852a5fe17ec0ea3b45d236f4ff95eb7773c43dfac696ddf7839fd6ebcd458fcef820685a687e83b33dbafc6106d3e98934ba6559d2d97a9cea9bff02919ae05bf31fb7a924465bc89bee4d7c6b332bf3e31f5508a33b0338bd8aadec23e86f307dc8105e9a0edd6ab1cca287d5092764866307d75daa78589277412f0596cf8014fba762600558d37c5ba043db87f78a41a81ed1d2c813d2003e769b06cfdfa5bf89ebf769422b9eb8575fb81821d7805826092c8494f0d8056f143d5d9e015ff511cf585ffa8846ef6dd60be83b798472911c59879ca3a62bdad064f53a017a113a0d2e00c96f0638697c9ef032ef2aa9e34c378561b1f07cd479b89950d0327140536e527ae4fd4c782934383a413def6862b92e4e7a8840e08f0d8819c8fd704b4a2426f79c9e22f6a5ccbbe717ad8f8b081568ca4d864842e74f56998a55d70ecbb0a88c2d9d3c7d88741e7921e85fab0af173ea94394d5839d889e63e59aa76ccd5192e9d063882abbdd3b70e53cb8917f30409194a077b265c99200224a861a455fe137516d596364397b952e11b10522bd69fa0947728ac92827fce17139e2366e5993d3dfef3e3144dc1c48e6d104ace3cbcef252964b490eed70dda671bbd5fd942a23060439944881eff85d79b995ac0be235cc41f36ca47543861eaba8fdb133f5df1c9a02450097aaf7911ee1396beed1cd750b5439b5fde77c91758facc3f42dfb3857f6c0b891ec729e244459b6dac7469cef6ebe9608fa19eee5fb84017f3e407e1ca3c2873a0a89fca40f98f04fef4334d81e8c27bcaee5da9b3c4b60f8eec8a56271d08f7eafd0ac3aab00619633f9489aa839db0fb31f0905c86b1bcf0d84d40fbdb05432bcd031181c7adb291db1bac22addd620573b57c8867365562eb6c1c17aed1f3bc1875af22559f9a075494dcc39951d3d005ec3827294a8188f772dc6a45775d0dee4dc8921d0e112afb406992e04cd77bbe42d40f3245701b332a73b398197c7684cc0ddef77a23dea75dd48cecd69d872dbeb59891f4cc0ba27a41488421c1ba218985088ca17efdc28b7c7fb1a3c77b78529a59ca1bb0816e9e5f6ec34912015158e4a5faa6572c8fab6ac1418bc184bda1c1bbd6db38db6e03031d638301e8a023d224adaff03262cdc4474780e1a940408653c692bde88c8524127f0a33262155b569483428ead94f0e258f090c0eee10bf6ca20a682993079a49a44dcc028773462b1b09a30e11bc7ce10b8763e5f84ca9cae3e61fef6fcd7ebc3def2d181c3f5b655d33e535640ca6de50e75fe7fb3d9b63b18169613e49f4fb8ad1f1f388bc6a3c83e77498b93da7eb4b27f08a86b057075224afe23f3443a3166bdb511d3b336f0ab76c4363b7d83886a95912d5862b71c762f53ba08158631fe5d673e946a60efb5c298a4715ab0ca809c08595e30d4005415fca6581e67d331ef77a73ac2e42d5c2c98e1030cffd93f4f3215138dc4179c685cfbea653beaffee3b242717e2488558822ed23b3bec793826a942f6cfbf564474e41a6ebb9029b0989b2e75f9b978d84d705412251f04c03ed1699cf78e2a92d2f5160d4591b8ae9866189261fa60d24b1a12f5028938474e5c3b852e40e8ba7a6764933ad377f485dc22986e86cf39e85c2b953ce97355ab0a207e83a102e79fbb5a42b09d748d3ccd7dc0586d3798083ad04dd95295d4e5ccb0dea6e10dcf573ba512e28c830ca7d2cdbd4b0871654b7ec6d2fbcb845d37a83c1b763e441ca7bbc3ca2b8dbb1f97ceabf6080b3af0a984e72eb566a9940077452777e10559892688657d0bf7346c9e2390a65fb1d63e18a112c9c87296e57302a86daa5171d6559dc885d0562e3a0a37483526db0e33be75723d71ce735bfedff673c350c327e8fcca0da9bb2662395a2057c0207f13307975cf4bb4beb84ff9f3411dcbbf1eda768b3d134d46cf82f5d71008cb2611d5d7b3a74b9c261be8a2a88b5db01aafd82d17c6a5f8a64943e53660b08dd6ed964cc5e7d70425b452e3b9a15c928b4e731a6029cbeb489180fb276155fa789785a91febcdecb94e81f5aabd0a271e2c8f88839ab8181733847ba30bf10aebb35a564555be5a382067c7efa378c812d1be9760303db8fb49459d69d4409a649f14ca4347d3d72236ab7ccf85caf44ecd6bba7e6423c4049e6c108dba27e9709f8e1482567ff1171343ea164d0fcd5d5112394418647d98d3fa72cd8b3f1b774c3255df1eaeb7ce4c861b9bbb519900d828b07c9a57e4b99c1cd9c1699bb9d2739c43218ab040233dd4cf8b08bc424f41b08e2f6766425d1e854dd3d9342f7cf455a8b2268824331835f995e551d76801d397c69b78a2ccfbba4b07ea9b4d41588fe1b12786018af9773ed6159809b4446c63845988521cba20449a344ac78d2b24604fe86bf75dba2f799b007cafaf2bcadc4a904866af6d251b6acc67b5c355092b7c0c5c2ddca2983af9e9f042538080c4b1ebc635cce3f6ab305ce81424f4af5ae898f44a8548e1422abb0361ff86ab2d6ddc4cda40ba423f8e02a6424779d49c519cdbfc992b8908c384994ffecc94ff03f888b417171018e4b486862513a8b32144e17597e58737b424a87094164675d62837e65bf27febfe32c886af9940f35c769be8c3fab72e1dba7bd0a0ce38a0746813d8ed234e6091d545cef687c1c7e04a0811cba0907eeed0869ccd99fae344fedfb467a741947ec1ea50e6c1be2df2fd55aa6a0c92705c6f4ea650ef51757a7cadea11c85698f4498adf8a28f0351c3668f72e18e87d216f3ee53bd927e25bf55face72652ef568b07b2eb3e6b855eb7133b3f56afd390cc95f35d3e3e8ad922a272abcc9714f9880ce872613ed8abdd52403adff8d778d5258db2578421747a14780bba95f2a85790fee551777ab878e6d42270219dd56152176333ad2d73678b3bd6a7cd6a091f135aee1955ce18e3ef465ca9507253809c155767868c6b259d003fe98dd0bcfbc5c43186a3c8af6e12edb680fd53a14004b354556feb15f5944d32a2b56e5f0ccce7fabb04746d8093a18c3825739385b93eb1a23b4f75a51e6ebac37400523361d571ab3b6d5df5c140ed6d7ab91509e7a991c5bd904daba2f17380d4375457fa806460606605aba62bdb0f323feefbdd09bbc930d9e940582a122bf936ef2c62429f7345a51345a1c697513cd44feb8b19f3aeba2a334888b0f3b07a18b0d26c56d51e3c835d8a1dfbffe9a77b3cd6efb7f0024bd616e958dec622e56832ca1b5c19b5d2ed2df3feb8f831a6eab79c3665dc982b053d26bf533efd6ea7fa17f3d9e5811fe6e342b96cebf3260cca3242c96080b3330fe2e072d52337c92094cfb4b550a803e1464e8e52fe4a805141a22d4b9eaeaf0e0e47e3a4bfe8b8cab5c5bb2c1ffdedc714921656cbfc17a61e7000bc88361d7bfebc10477477d076636f1cd0bbaae30a62280278cfd6590b491353e788ebf5481c9fa9cc9cc261169296ec4e534035cacea6d350411f01f3ccae5c65e4b601686fa821c1825231f8bc59a45a2ec1ca023e290d5c6c097d0f41dbf612d081e359bb032e3fc10ef78280be9e0f22029b0c956ebad4e09c6f36425c1f82ae968d1828824d1546c1e1bfe1259ced742035697c45e8a1129b2e1399b37afa7bb4c74cb15e3bfaa2750fb50c3d42603a763be2fba92653397c46a973cfcb098a36acd4d5e27dd1a048159093d497d7662ee2f70b760b2d8324f4a65c947da4ec142d9a4196d9ff46087da0787f88701ee1749f76f0b045ed42aa87daae1fd63aec8cb96b60dbcfc81843228dfa652d006fce51a1d630b152639186e0ebe20185ade3f7622851e60a6f57d8ee2362be6fa8e1691ab72fbe849d02617b521db3a2119b89420ccfc6b2151f77aeb918da1f2c34d3039450427e730ce9635ccafd396df528093f2c68f29df2dee82d01265f4e6008b5cdbef603e1f26761f6066f220d857cccf803d981119e26dd9dffaee9c893612e47a4e443d7aad0a564948d05d5b1cd82aa33f47877e275c960d5f140690eb3378fb5e1455f5ec28dc25bea5e74360fddd36025fbc1f5af98b2ac73ccf1a73b449aa51c3f66ef5a6e98e4c03d28a433eb6e703de706fdedaab590ea44e848e37542adb91ac76dd95aeddd276aaaddd384e20e57fd3999c94266f1f52b1a6ec959540b7fc97ca0100c4e98d1cfa09a25b0d12acb2c3b5877612123675236aa23386880912a0983a9537d8c5c509ae64181eb7635fcef40b11b10de19aff8236c1129d0409f04c335c5e5d3335f68a0bb2b14eb883d4b4871b11fbe5c080c13e0a2f345e49b7a44127f6fabff086ffe8540dd4234809add7bf22dc4e931f3464e0ce92dc7c72f9c193d10f5a54857052b52487c56f32c61bab9b8d36ee1019e35f1a957257e2e96261f47b9dacb691252793c5b03aa6ba76589ae34a65bab91332ff09a37c2feb166cdb8a04089ba549673ece69773c7790092f512ff7b33a9c283264f50f8be9f7ce44b7819ca374fad56a8e91eba741222aeddffd5d81c0349bbeed207f6eac672d378b48689a15add113b72c518caa4101df96d3d0685d176434004793bffc27d95fbea594b6822c43701555f9c3ad42b06c7365639fed8f43a6acc8d674ee18ef1b85375cc74256dca5baafbdf5039270f321538ce5bf36258cd915b394fa5b629744ebc2ee438b803ff4a8977c4e0148f242b6a72558ea64293c28fb061465744e5dd9382d6021054e2558b5abfcb3f285b7bebf5da2b8934024eec1e8a2e2759db26f3cf877e701144aedd40ad6dd6d22d6dcb0471e1a6054d2cd43ce97b038c59f303010e7dbd90487d89237b3065a23e4f524bf5d7acf2e8d5e792c880929f08335350402269a339aca95301ef20d0dccfb9c6b6cee7be83c5b1caea44b649ea77911c7f646ab3506b5fd96e7c01d471827544e5952209aefd62b86f01cfb3f675e3434d8f83f742add60cf11bac967d610b52f992d4ed2e849f2f04a6cdd02f89c268eec3d8797ca85a9b7e052e067e477c1fdc40a050bfe15af545561673c12f6dc28773d6ea13597a6aa1e786979d743c1645ab21679ca3c4603ce7324cf88ae75a93e8677a06fe3c0b5760037f2b0dbc26f9f60c5ffad9447d8664554035e18bdccfbc7d31389a66afa38667a9af997f0e56d59c39e19a88bf678caab7851c2b51395fe4850a74ccd23f62e9bfe385c818da135136677f9e9f2c507cd7344bbd7401966a221898b90add6ffbc0f9a1cd1b270efd668d2423aa785824cd7c1fa27df0f7571dca5463adda1fe7f75fa283b4553626473cab7ddaef7fce947a6f717c116d331708bd6d43e756375da19ef5024c91b270ff382c5055530be6a9b55bb50fbad9fb392880f5f7edf2a918932d69313ae7c7287620b1a99632307a8f1a9534f0e35f2fb1e9673330a7a7ebe0ac0e07ddb6b8f4102ccb0bc095d0003e712388f75db445b755b6e96042872bc9742ae33d605f22397d461e7a82ec672a3d15300bcdca535e90817732be4806f79e554baf5cb633252b7b7d2415997c5b16e0cb3b8663760b5b41fe4f9a7dacf68568b09c9e23338dd2bb6050db3aaa02807136eb54c0d6ec504c30d226f0edb67da41c0320ff6c2f42a7da444647c4eea86bc8537175e96edb470bcd9ef9960b1c2cbbf4c0ec044c37534c7cedd13a18bc09f0fdc6f270c536f23fc41291472d110cfe167623b0292a628727a18fb918c10db64b8b7fc46786783230235dd4399056ce22c7fb268e2a56cba54c39bcac0c5a91ee23a27991f9caf2fd8a5241ad471360659d5f114accb2420fa97856384118bd705d2e93c1ffa0480c7aa90cf0e905fa4262f54aad23c0e4afbfc84841df8f1facf173c63a8d1ff3b2bf33d0c8458d3fae6d205d0cd200a9833cf47ef1650eb2505e473c836f538837c55d36e1ee1c51b2c430f64022adc9bf901b4097038344620bdf70e8b1f1f4585864df4d761366ed275cf62735c56a48e56cab23d6af69b8c1be7af7111079c645891aff32ef5810725a64223e8c5b39ddf797aebb12537e712b0e0584c722a5c350464f9a23fd5f46bf3c96cf7ed062284791f0b2e765b448e107a7c7f2330eac1142792811fc370f6b986b3716e4c4e5a975c8cbe65cae190627390c7b948d87629a1dd9e164824cad1790ebea6416d6562eb8a79fdc921192e35aa5e46ea3672d01c02f4c114432f2d8740608f8435ed314678a947addbea8fb6ecf0d59459174314ad07e7f129715838fd381deea5948dc1dfa7659df679c671b8451f75bf7119687a7b6c79632b514d76ba1bdddbdae404af027a73ab195e1cc5e80dc4285fbe9a6a341e20d2c1c3b4b6237a1a20afe50ff995d7b67941913319161854b39d68fbdfcef88c8f8a1ec6c80e70ff58ec6e473a810d030a6e7c7bde7adb2390fe28d420d369dae8a96e3bf927f71447ae1b07f31138f37adea3a27deb610c7ba88f293ba8022b9ce15604c45ab3731bf207a232073fa6e8835afeac32b10203219e5706d7e8a757b5c482210973114c62818ff348a079d65460cc4f0ae0f65087acd7c9a5e47842f7858d133543377efdd55da96dd9ee0470a223a092d7a2b98dcccbfda49561ce06c93eec50fc298e60f401ff32e1526de5a8e1353284afafdba38269494ba8797299192cd6ec6eac34c1e5b2e619cb3a9f0fad9196a76cfe2aae802326eb0a283e695db6fa15e94ff0525c1a412dd9e7403905b23cc289a16cfee85ed0eb45e54c6986a2cffc9f03371eba6fa0e64541813052e340c44c1faf1c14826a65843ead480e4293e100db0ced2047dfa10139492ae27a4dba99cbc92f7c84a8b50e854bde8729297762edaffd7fb11d93d70736f44e2029de6f75f6f6be45073f67c21d1aa40006b0d496297bb4cb952e9d284afe23fcc81649399551961beac1d40f93a5001fa32a18944cd07dc9cd7c7f4df9ef2df2cfa0ba47944fd6641a4ed0b90763f697fdc21cd9b003f37b105c2673c4840abd6a84354e6280500d93313c5e2672fb2a5d3232644b1680be6f1100d98317d4ee54c963fe6be7e9f8d7ea0ecc5abf5184457765d4d1b46a8e636db9f2d97d6b6d253e1f5ba617397b95a8a12c4fb3f5db0e5ea6a3ddeb2876aebef29bb9e12977b05d37ccbd412a1e18cd05c4cbf29468bd451fe225a19c015e2fe89183ddde8337cabcc5e4fa5571d5504053124e81b9d6560fa77ac6d92e9d477f31dc88a63ebdaa75026557cbb0f110fd97558f82bc2eaacf6d376d5df445e0c6fda6e9b0962733b6b982501b06ff4d0ba6ebf5446e7ca2dbb6ed9fdad74e1aa5a1a12b89c6cce3ef11acc4e7f201a6e1f2e3c3741167ab0c9724b24a6d4c4aae13d37b62449c888aafd1abd54da6fe69043ebd77692b68dd21f3b02997f9fcc895f0f6e6b0333fad2441b61bf623ee32bcae56841059d783dbd9f1ef78f3ca3aacaccd281e9b0e09c43bbce3455507b22c7e74884ba65a6a45ce78356f095f4efc1c0ca3810b9b9aa06c6568b7bf90c3bd8f30723eddcb4566cea7bf2bb28a4d795fbb9a8e7f7ca978e6d9380b680334e324e628923e8a5d619cfbe272f45c2370b698faddf7db96ddaf1543476ae4bcbef68c7229caafe5a840675f8273c6eb611a369641c6e452fc22ee7aac281f131af9b5bdfc91a994dbfcd878b368c5350caf495b23a1f9451a11a384bd44d5f9881c0a4b5e01affcda4dcfeab3d70acd4dd47c1bd24e0fe3ad17f9e633cc29c2829103b1e7266362d782c9545a371de6623f6af70f6d230c9f37adfa19d9ffaa0a7d7e89619e7bc74580042eb10e0f7fd96b9b6fbf18f7acd0d88989bf41a6ed01ce19c1f394215dd82992e54c4508fe69f347c518482a68c712863487031fd1ff5a974bdc5b1ab55654174da6f87c83fa7cc3fa95932453d796bde84da6e930668c0ecfb237a0b01819309faaa6a8c7dfed0fffe81ea5c8e11e4498f90e8449246e2f665148f55b51c3179d8f2e553e715285e8fa570a41e14d5848e0b6f3be619b74e0cb039a97dc74857b83adb3f1803fd0f5f49bef404729e1309f93748b7913fe70575d1b718ab9bee860c869a815727fab623ee69462eb5446be3825ace4254b4ea62da0c78aeea484f39f248e44647d99e80cfbf6328b0ad866afae98ab5cd6457271aac4368f589c6196825da1dd07967b6d1d2cab633000c0cd4b95bc42fdb6e5a768755e1748fc05ef0b54e087b1123aa39d26011c6a5177cf037484af25354cbadf63c27bcebb87151340028dc402a64d39cff8bd6a8a28d7e216a21c131b1dc3ecbbaaee89b4c65286704fb13a198b491f5f1a081300dabe2046f714d5934f10cd34786cc4dbd75ff5d4012d786cae7cdb979cbdb3b9ddee4ae184e8adf0de8969c8d58fca4731984bcf11746d1e58a19074a32cd58f96a678fbba56e8e72a9d017ea3eabb177a13ee3262650304f40d44a0e22c8c9aef327a8c17f966d1ac3f13ef1633eb7f8696dee446d994b34a4f789e057e4ecac354da6298b5d0dc514cb4ae975c273c79ae535802a20980241cbcbd21b29bec84fc35d03fd4cccb28c75164ce9ac54da771bb368ee1978ed937276682b10fe389ba8fbf6bef6af26f46b3e4a0b9368cc41b4745b33c3756c2187981b748bdc1a144debbced97b3eccb613744e4c0685ba643110c441b07ba99308fd89d1708ed518697a0a71ca0d038691151ead9e8e9c016dd0372c64d560d6d347e65a5a715105ae8062b5f9a23003adcbc83ed95fdaa2e811c9c90b383317bc564c763155b46419be444bc953a94aba98359a971bf9ccc7305054a19c9b498fdd0f9b4276bd1855a410f57c2ef30b4fbda7993217f9d31bdc479f88a72a386758037d87455ab7cc6bdffa5df1e087f03a64a76bd2ec4736530c896110edc8534255a12450db49e7909462315297e6b77034c6803e3078be27385dc47ef93bd83e4445fd6eb09c26e6a7a3a94757607eebfaa197b57a0f7df8e24c8ccaeeb3d81d31ef62b2cf2f5163a9ccdcfebaf569cdca251dd69f6a3c3d55ce58ba49310a4825d2ae36f51c3da6760eb867831f90d12b2c5079c24e4656fe089187280095f6f9f736e38b4d3b8f3cf90398dc602ee4a9ab4e09407b58cd762820679af49549056a6a670e74032723690df9f8b953f2434efd09dde9cdc5eefe93f63fba517ce42a7d9e3200fbb8f60f7e2a88a9175721139e6247ccf1ba68f25d59800645284bc235dc0b79373872a8f70a467ebb0bbef2a433b0678d628423ea9ca11ff3604cf18331d6dca05ac5e4e2a151d7c0db9fa003d19474aadaf4013aa6a3ce7971223337d7144db1fe3e3d92880c85b4ddbdefe6a988dbdd9db961ef9305d6ca067b9515318ec522b263c6c02ee861963c4ab53f6b61d93bda008c686964447232f75b510d3b5cf8e0453be3e6c616253ca86a7ac14318aa5c44872275eeb72ae8b76a752451f25fe1c9ccb388b49e9a6782dbdb27a876341647fe61608b747181045a0b277ea371ba5818bf0e025a22d0c48d137518796b56f2092335dce0a0647ca39bd54c614f0e922fab785eea124f98ba13527ee59952912d7f8e77d63002b1c76c62e870399c6620af897d935e866ae451c2f11e285aa237e6476ac6806ae17250337ef98cb2c465b83b697033cae264444beadf6c55dd3d3809c5f383434cb72be090709c6a1a2ca0d88a044dbdfc08c24cf4942578265274232504bfe15b1e8991f063c98c01c3f69358f6e733b0043958024fd10ac4aa6c3d18ef6b620ba6b8559cb67a6b5764319ec8eda63b95182db93b6d57543d6cdb45a38cd8f132801847ce52fbbc32e4da85dd73d9d840e86d5eec238c10245edf56f3ad40c24386461b02b8efdbe6dc8ea3e3905c47b4edf611bf75a427a1372f7f727baeb7b9db63300e605a591454305c7bae548b08aa207c91ba6c58ea4f86c9e1d85d5b5cbd5e3de85e354ba3f529c35443483e90fb7f849d56b7ff5375b4a52b668cd63111ffe9cee8a9dc93b080e4a5cd6f82241b2c211f4ffa4bc3039cec70fb6338982acbcf34d1641e59d1bf6cbf4dde709aae8653d559ca3c1abb092f4de17601fb354938084c77e46cd1287014722d681e19e150953b0ae3966443a94948c4a9341f784421e0ed7bc02127439c06763203c849758c57d312af42754a691d0764631e0301edb23702767244044fee8dab04ee1133092258af2466c80ee6b2a5a0a154cd65bc78181fc811ac9f7db5209b140242b3cce8556be83c6b17242cbbfaa09578326e4aff2c7f757cdac8d85acd6eeb450496d8b85142aeba7fd2135446280e0430f6031a8a3982afdc0cdac6a84dd3abec982d9514a8ab7629f7959dc63d92d6c8b5f8fcfc4be84e11319819e56fbbecd5312e8440c8ca35b2a9f618151ce550a7055c4d9edfc34838bfbc36e9b33532c2170fc63dac435c8b7635386c4acbe207beb89b9fa1cf546916c4997d6d0292290319db287de73051fb803d1ca60398bcd7a4b69586eeae3923b20023daf4620a3ad6348bb4d89918361092c634f83576a14bfb2886471b37f8cabcf3c3f34df810d4d5272581ea6c5bda12b040139e9b91967b0a0854eda4d64bfb4ef57669df3ecec797b6c199b0c98b9ab159c837807657e87c9a89ef4764512a6abd93477e78a2357fac5c4b6440d8cc1732f0b5a04021167c28420ff77dc95a075209865ef8939c3e76592f59c808917efccf705437643ef5b8eb13150bc88fe8946112cc340831e8d58f8a006a0780ecc730b03be990ac2fb261860072b5331d5366861bb9aeddb0b4c1f42ba7803ab45cb1eb251ffd5241b1b753f25fe80e87b8f29ca558816f0d6765d9ab39b224b05ad8a753bedee48c970f69f37876aa653540e6fb5ffd21335ceb7d03052185bad7f48d7a0e58d062686a5434e905e836671b734208d4424c48ce3f035e2edc55161d31f09bfe894e87f6a7c80685f68740d673d8073e04cf208822d07e3a4a9fb211447e14cbb0fd9079f246500c55a6faca108e35c0a1283db9014b4a18eeb270ee247fa970c69565bcad69a16568afe21e117fc9a24c50e56480a89957ba54589e2a507a732bcd0a960824174aab3fb53c3daa1f737657f49e2eb99e4b4083b78d1641fd756d54fc39f9c7f39e810f5cc5ec84b159571971c03b13df8f13fe07b7cc741c4fc26ea16e335fd392e83af1ea7d6995f11be6732bf3f8f21bc0d82fbfaebcac14d555001092d68693dd79f140bf4cd8bc1ead9a0766ced4df26061ffecc100e32af5426bc393f257330870b6b6c24da953bb2737014c7ccb5edd32dbcae68b93471b16b8ed17cebe672b801a239823c26a2c62d4e493a31d91629ca88fabf6bfcedd360442efb71cbf406308deae7e0b9ecfec0d90c18ce16defbe9148e6338e54f99fb307546a62047b5e6bb38b2fa6fda8fe25d326a6c226dda7eb523914a616606350951a27af1bb4ec94a2d22def3e83235211c7257f8c50dd205329d309cd9827b9bf48341d20a93d4f492b1160f271bb51e42581fcececd76b2eb28b7e8d78a59f90e15a9ebe4ab2bfae5f9bff640379542cc5f36bef478361910abc8483f12b57de4866e85fe6621998932b620f76e630c615fb7e205ac14bcdeccbe07e661bf39ebba971f32c991fd14d483ab41fdcea610bdcab202ce553160b7fb39a3169b5105b797d6d24537812101d7990160b8a33591e82a64c397d6c4c85227fd220cd1132ae384ae1c5ce6f65f7fe3e177740789a0c0fc72f786fb90eee90ff86dd64416d25e6a77dd5c577a2c8db3194f7e0094f345236745511edf1ac6717e75201c4b7411bca239dba6f0da71fe3d5b8a57aa1eac1712ff2f2229f186e877bc52fc54f650c182aec5655c84e3b1f15b9467361c4df06ce69881a966fb2952c8e8410ef7be9e72ec4f38fa19bf00a834d604d7a1fdd67c947348c4a5002b2d49ce95b15704aa9daddcb7be30fd3c6449c292473994f00710c2225c23ff30039f13c1d9bb5ab406f21fe38565258d282217bd53295f57b48acf03373267b0f28223f05b5c0faa5932e6068eaad7890ce2d25ad57ba8a7109112d505bcf183185d8d179b9893218dbd720c0ee8edf8f4a6396b4ef74ace32a969c86246b8c4a955186a90a0d08785fa33aa24508ac78343ad2e04372ce49ed8d57d4b60e200bf8df7b1281cfa69b664db61ecb50c1c1a97eb798b5263f134ebbb3fac23080d656688e9693527dad7242bd0bff41a78fc2d0964d63483cd6f1d7fef5aa2d24e1f8e4d47643a019e84d267c1d12d9fa5e682119206653179d080e09a1abc646411ae36fd083c615419a4fd527a8ba6e5ca03b76cbff076a036c827f4f529f6ef61904a8746d5514d52d48e3f3fcaa5b8c5b38fb50fde55a15bbc6166918bf8629517f5c3611e3cc260d504b539c13e1234109e11de8b6f1220464854f76128727ac9cc1062488288324d95f5b898395b551f072a55c057cf9a0574e19deeaacd8eb3a6f7b11626e1cfa0913f2832dc713b40685a902a0013546915bebd57a43b6cc49bf973a1180ae723cb91617674c0a77b63aab301bc99bea7668fbd85afc802e507cf3473b26be6a809b903d2ae45195401d3e394cecb5175f3c85bd06d839bee8df987f096dc556175aedb595265f13378237bed0fc126919b53c1d5f9e1f923ee13300a810df54f91ef9916b05efa00342f42a85b8ef7b63a106a528e7b29d95d36c9ccaf9c21a4fa01a2990dc72c260be2ea40ece2d55736b02408ab202e2c25488d222ca25f3f9f23c588f12be28dd381f5153b1cf4f72932ec99091c3c4ae8ba605eb78cd8d092d31e5174997875e79e0defe7f6afafbf9314eac5ef306d8b63baf62ffecbd46d8b1ee791d0fee9bc45284fef53fe6a0fa13f530860b6ad00a9cb847159962aa34728d2c79be5d9f22f7990872f24e6556ed45d6de0331bde0b48d61fd679de44ae32d4a724e2049f57c0e110a05ae43eedbf03912a850d8344dc3f7618e2bd7ce98b510bceb24cedefb9dd5af99c1a64dc32759950151ef74675c57410b2e19d2773a62a7878135f813b754651f4af004a44a1b31d897e39e9ed352eab4ed754619ed44b3080ed9576e664f81e2c6f4d393f4c71a8b122aaf980ebe0839306a500adb62adccf1ebe75776cf03b1c57ba4a06e889cc4fb4e5fe84af3c16183a969d45889621ee6ee8c702d9ffa04eec354282df04dcac76b34f8e44fe1317c9a53f7362bdf80e2399ca8bccdf566ccfa516f4371dc6b18b5ddd892b214aa47d8d93e60e03ff2e179a2067a2d8cfa86c70280a8e563d2f7f0e20c4e02394913b0641e23df1188722c7aef7ad6ef6610c2019dbf0657b27ff6a2abb0a9b699c3a75bfc32894307cb26b2eafda2928d22f727d0d5f6f7a18035a275fb12ee5d27f1579761dfa4593410441d59b2dac0466867602db96096b32e29b4532fb42d56fa26ef2b07f802afe49af4cfe8e66c75887538bfb71bc97df732fe08f6de9631e19c5b79cb91f7de73e9bbc76c636463c04588113b4c8855baab3cdcdfe11210f836bb56198a7672dfd01550286e4367ed532b62f666b36434ff8752687a6371468ee30a847cc96a215794d94c8b9afde0be9dea7c7ae61b37f7b817916c283c059abd9cc1d80be8842de941caa9d356dd328a32c507960728eac655614fc06c24c2b7c62bc35b5b28476812db3703061da43a3dae6a9d5e81394daf5f69c64dc5c1775e4085164d80bfd6ea784717dc4cd975dd76d201705020d23c199dbf4ac059f66a228e0237d410c097e1c216370bc693a241caeb4bb7d1a885a065c82d1fd36e7e1703894cc09acb401ab162916fb5d2a0985119c26ae788ee0ae35521a051cc6c0ae0606db378cc70698a2be9804437cee7844a1634203581eff65b1f36f67e2207a84de971a88576da9c8cacbb2c81ccfed56a99726ecb41ed706a8f88d1e232c3a5d32d92805cc2bb80ff3291defebf91d478ac9656987f254b93b571a6a2b011bc03f100777372209d1971e6eb4942b8866e90a59d87b0bbf62b3c7e7cc0056ec6c0ee7d37e73360bcc57f924236977051acccba478c6f59b8bb9273022ecc768920d81b0a28fd96b06bc4e3367e091771a8e381e69927ac14a914c7c01e1b6c33caa6c289a9ea2108b803195ef5bbf401e583bc3709cee64c78ea231c7e58b76c7420adaa62b424d2f56ce9384c4bad986e61c21382d03bebac60e62733615ff8e4f8ed10d8c706732cb8fbadae53ce6a656afbabc403645fa0ffa4481cc694e413abe5b6aa0ff48873c56c54c74c490753176519ecf11fabdf07a2a6ecee274b54d4cce3e4d65ae02ec84d8ef135f19e4e2e123b0460ac1ee2165c70552f0fa2a4962e727f036e7824ed2173a43bfddc316ea8e40c4451252ba000ed243ee69b88496b66d918a5979e96638a60d68c4f603ddf5cc4add8212997f05e45096a980e8b37d51b103cf00bbefe55aae21b60e3a5072cd3550c5b631e44b6c594fca4e294181ac419b0d4cfc6d3acdbd810dcdc9e7f3a33a040f8c8504613566e240d9193eb5dcec8467c341237e5d59a330d92e13f3fecdfc70d3342f418e2410b7b5aa0606d3afd8457b1f072cfde442f62914e4f7e7f937db96b24810f3347ffa73be879dfe420b674f7204180320e16a7b4354dd79c223c4f4c16d4d6c393f39c0a7d0f8ceeee6af475e954eb138ddacd01aaec23fb5e6895c48207a930d7fc82cb474d522c0bf48d48fdd14d66a1d546ac686fe66dab6411dd1de9acecabd5039f95332f5924eb23c2c9cf6819e042e9b8c0324892351b64067e7d5827715c3aecc214a40439aa68057edc8bf298c16b06572268ad1f78b6470879a30b961333a89a186d77dcd0925d1a786f7c77d76cbc983ff995d1ed351e66c809c3599ffcdc19bb870806c021491997df274f6b0b632c2131c3e8f0a01f04085ee6464d48d2694c899c0a22b268d02336e7d94b0f25b87006db687ae3b1280d6c461c889a790d6f4549dd60e2c37b78a5ce0fa5a1fee16bbf59df86c3690a5c6b3668640b00719cc457992a471602e75283458e95da8462d02c26989de2edb141c2d61797a7449d8a1ea5f26f16e53341836e7f40e0b33c0a5b52333121bb79957a167afe494e142d6e7271fbe87c160b90b7d2e2734bf2181425627dd3aa59b0c0428c3727a7f3398afe20bc3192bd6dc62c49a0ab7d1252c10630adffa3a531bcf5ef6de1ad016ee8b8c0ec74a648a32c4688083bfe3dd3f12c3014f4a2e990647f6993c84e5c3b3065d374babe4c8dc104962f1730a9f03b0df53a8a0a647048426580d7a6f29f34e2203da55050af56c1605655fd9ff36da89bfb0e1188bffce16fa40470064ce27c3394700653f9beed362306d0fe28a88a3dae61927cad5dc2bc46c817b2185c9bdaa2856a44621cd04c6a0f79b63f3a967c651c719a6b0b52a056fada5fcebf19a9f09a2f895256af3b8646b04ef75dd0ff3ad758f1bfccbd15cc69c44e3b4da777f88fb679de02ff7d404d3f601e2970edabd823c367c1f53b98cff5129ae9cfdfcc76a7e2d04cd8f449f3d617e150617405c55e084a8e60d225ff3414483923c0cc7def8385a07463431e05b767984546e11e65ea79ed33c16976510eccc917be67ab399393296ef3557abf16c504c117b2aa5947094c2d9383af98912c06debef29d0439486b41b05b757d1b353086206007ce33ab5f50f5edd5397f96ef536e8cc1cb11f17dea0a9846ea46ab56299ff50d457326de643a3fc12b9153d4afba11ae56adf6ba7f550005ce28b1f539555bcadb0298c027d77957e2d2107f4558229f2512c7e15a49140eebe694198406251f4d9085bc87e7ecb8e2217598fe3e35381e3790ffb882463f33bae7b1a3fd6d6634febfdccfa7a4d70fb40735bc26bd061089caa3b801f5c3e6724c874585c5a1ebe3156456fb6a5c5b9066c9dc115d74646a932b45a9f179e4ef95ac8a712b0ba20ad66321e857fb6011cdfe8ec12c1b52add72dcdc39a2eb2a8358504951c1950f221b0f518f2995469ea216f3d0dae371c4e52997a83368924fabaa84aaf47fe4c101c7059dfe8951908262a510de756f3411d3860ac2102831dbdedeebf386c819f271c1e25532e798076860bde2d04622d7abae84b8ed0f6a3a2f9622dfd852a7889f6fcf86478368510403f57e8830e240f6ce9265c5e430a8a6e8a25993d967f3e6d2e210c32ce359f9a040ca73a900da3492179c2fb4244d0e8d081154f473a09ba12e553451606946935ef9f89f4af20b7b06d0ac8620bb9b4a233b02d75fe8a67aa8d7ea5b5747593f20729ad3c8a00de9927f06c257d629af0c0fdd889f660ec1c769eb8a611dec21c362537db7e23ac445aec449f091ee0afd00da1a3f3ee8a33ac10e8bad4e50cdc06954b3b4e68a2ff71e88afbb188cc58c7e4f01610c20fdb207b1c13c4efd11c99bd9937e18581784c1fc0f696fc026973dc0ee621b59ba868301cabea15030c929728a163f46a44812c8b62d7350ee3c9bd6251fd4062500c1bfdcb9ead439f217871bd2eef3a5c3a8cf1a5be444ecab2a5394387a2272c69921d94189e130b1726f0c483d9b79b9c0df337432748a9e50630e097c055bc21bb617958834c4d05cd43631690b241c0f93c27d5a3060bd65a62feacfbb1201e55c5c35dd7c0a37780383967715a7339f1e0977c3fd503b05345a86c51199f1c19ac0cad9bb90f3641955cee26d7b830c2b93b48691fa564a18e9cf77d2b24d207bdc4263d64db99b656d51579b17d0bb96edf0b35a78ac69be7ce0b0579bc9a4c7fb02dacff10f20d131f04b8b393a42dd4de6e3f9850b6633be3f64c9b19ba9561651de3bcfdf7b19fac3683b1ac144fbeab4a9534185f4d52b7583599122c044fd11055d4b653ccb560ecf915e10a70394c59b8fa0683a514d7b5d31d21051577b3f8fe469230c33e14609e2e2fb55581d9331b2dc5d7233f08da3f52e36226781e02151fade1242614d375e8f5384bff02a1ae9400739c8bbe0a9a9e9cec79aefa4c86c96496b30aa2672f1c77bbfb0c87335a31ea46c250a609b531b0e3d60383f2d04edb1e83ec71cb5b955efa5438bd387a4cb7ae0c641ce70df22bc42d3ec85be0c67ac05a022ab2ffb73674f44a10f7bc0a210b1299b5a4fb653a75df752de8ef606c1acd34ea1f0b07ef20e1657b3cf92be46ee991b7a74034b0098a0eeae3abbd1f1ab859c2837fd55dba9318113dd820415b65b49cfb671982107d4789ce0a27c6ff1e2f36a24e1bb8948f7878c16cff7f8c3900936498914b75373b6bf19866da729040bf4f3cbe1d070ee569a5112966d65d32ff41c701c9f090ef4446309895a68a5649fb4c9e0a647e5032a629a5c210401af64075de5e0971ea9bad565c758e8fcbb5088b5ce681dc9f34819a4de6139745c3de5e2990a335994b75bc3abf22db2658fb2c2277d31fee64621deb26c5e82f64bbcaa289222bbb052f95dafab4eda87a7066a569104548cea2be485b3315f30cbc5f369718b8e9ccd6c4f370545c0ad9e96d48ce6db4c40dd7261763c3780ea2fdac7ead871e4b77491b81b042998923688176996606afd52d87319f6c773a4197cdb5e1b1f476320b18c90e78a6887fd5f351221469df6a1e437774058366b62cd20cec79b795fd5df0472238e46705408d5da516283b2667b1f6ca9feeef0551da2ead5318cf2ca8d16245b66dff41e4c70ac20d19caf75b8e9690f520d8e5b807a8a6c4e82ce52a9a1117cab514b407b4fdfea0eec2de84f9c436e5f7403f3db5037d74c532a5088259fcb3556e53a38c664118b46e718c18f431fe6ebeb2ad9bb87ccde28cbb9b6e889af13fb406de46085784f3c9597eb9d57a1ddb1c1273ce64122d9d62cc1cea9d5c22c91e2a57a6626f774858d5558e58101a4d13e501ba6e50a4ae0b4d99f933f2fb38131041a0fd9f4d61b536dcdb627181edf9e51c8edb6d482c210c3a4b87cf4ed5307c4b17035d05b10c61d7b5272564630480e7bd0e4313ad25a8d976f3aa1a99fd265e3a2b07a853c7508822bd333298b05bc87f31cf533c4a14eb4a55fdb209563c23107f5d42e76d9179bf4aaaa76a12cb1b7734b4aef92718b05f4a89f3dbffde61f99b026d5bf2c59dc1c11ac922655ab6f1b8b9ae88a0d0bf6a32d0845b43a12834f2fe64f95322be6a9018f0b8d3483763b247e5b1792bdd0c2c9a30c63b0dc8a9ef3eb426d6f2384e2cbee741b9082b083cec9ad7b26d1d140471460da31f9d9127136aa4d01210ff9f281912b3befddaf1da0f2005ad5e6c332410af39c8abb24eafe34c7674470182f8c769bec8bd4417684c7779580e389548569a8f6b094e53ab2492cd9d9318dcd25ebc4332ae33f99081ca81ac3fcb73b2f44ddf6317fcee84bf3e7c27584e93e1cbe46140a9814470f91e8d3e1283e8fe7ea6e07b8d06d0194e3bf640f648d250edd803e852d47cc99766a07568b34f0bf50bf5745409df2f7a5a152a6bced0d20dae4b551b615b83ea33180cee24bf7d87f1d28230e434ed956d1eb93e8c141b7100a665113fa027caaf59946edd023a6cd9e2cf3b470c60c423bed8c17ef065f00f692c8530cc638a72e3ef7b2ca1eec24ed4f7306459f8b17596049d9199eca6233b6f7a0e4c8aba22e1d66436cc8789878d8b858eb909dd9f8bcfae53a072ae069c2aeb3fda1176fb1e557b77f4b9a0a07c8e54d41b4c6eed1d5f3ad800bcebafd50bdae63d13459cfc4aabf9e47ae0a57dfb18dada5ba1d5c3ec651f4312eb6648f434ad1ace8cc4c8e96036ef873cd8fc3c3883ff38bd2d664509a057c3ccf77e7d875b372490dcc13abfdeb1def8e3b8b45c310bc69c71d64cccccf49f41c8b76f7da37c1b006ed19926047342eef88c37a0a4f74fc764557595ae3b7b97df974a35c8f05e1ceefa360b5bdbbbdef6f68d86c611fc188e064b48ee52b1d5338dc0620e20b9637b60931b921d13da1fa2f3c7e9f360fb4f7becd3e7789de9cdfd38411eb5a0d470b209b7f73959b2021b99eb482bee2d9f568891a0c621e396c14c56c17fa9bdf0d843b4e71b62808ff524b2cd037e1e9a68e5f2ba7e2241e08c4b04a6cb0d1ec9a8c077bbc34338881a2e506b8cccd18287de80c14c084ef9e1b8ec12b42b96537f555a368dd84dbfa5c1789525f95bddd9df29634a428c63d8c822b41406e832b3a95fa8e74169d6bae0379ed208e9b43b6be07af9c0327888e82decd2c20710e2b83c41825f2b6ffaff9c0416625549e79789fca277b9903236dc527e2abcb84551ae4d045b0200ce8635fe575356a78f8c3b21b4e175044903e54bb22dcf4165981f59e6200ebf945157c5b58a32eda908cc6cc47a478d5c575bdf355dce0ea984a06de6dc1455d8323b769f9d0a3eb8ddae66e84454e58a6df03d926f5be192622cecde83874a146ee784d983b1a507712420d644d5af7732069bbbbd0c529d5040b77c4c9660473cea154e18156f783db8e12902f3fe07ca914673c7dd5410c3c1a96e3255487a4ae4c3999c36af964e0f9aca0db208110cce303dad2fab6a0235942d74f069a337e39b6634b3067fc706e80675efdcea01be411eb759655aa8433a6915f1add31a8c5a7768dcdbc95d22da844f6d2e60687f2edccec9d5a026930a7c5d917a64fa1534bca3608ffdd94abd8735ef80c4356db62200b597a83eeb8a04562157f35314b1e9b7836dd69adc9460c3664e120e1272c5ff578b0689acb2e84f7be75397116ad156e75d09100616171b30f5d2cdea9d50a3c7e998f296ad945d883145abf9ab2b9ae393044276407a625e813a01f98a3e6351d27939e0c03bd5d45ea366544f987e6fc197495beed04118a3516c259f72e06bf384729a4c3fd4346c0315504c5c62998e5bd3b0f5536504644086720f0e5b3731c2836a23a5b7787922fa9bd5c88b8f5726a1deb474fec56dccf8febd4d3a8e6ba1a98eb2b3a331ab56c8120448a03873da09a724b44489f3e523d8e5e387a5c595f334b45ffe85d03b29355dc22e5c84607465c977ee9f90ae979c7e2b703d1149c6216634f3153f2a3874be6f255b97aee19c1ddbb92daebd9cee81a6c8613f5257a67ec4f3ef96a2074c772d5a780e5361d64ff5e7e44186acb67c7385baf81334e7597f2f9560bd1b2e9c2203b19e1899cf6d6cbdd2f7a9f63516ca406cbf2abc7219fb1f0eb7d9eb3bdcd23e25dffe43f95678033c19b6dde87619b8883c9ca227b061fe0e703a64d163bf8a102df1ad4510a98889c23e7739b9164f0f9bcb6e6b14b44b2c74fd68df955c4b52e65b384753dd9c443a8d0f02cee48b7d1211fbb01204491f39dd4a16a6724a863cfeda9e72d794237b1ea4df7e8297ef02b5fa4e7a9a7f1a907853e05df633ad221d9dbaedc74a2643ff685478d6bfd044f96ea69e3c3a2e3a71412c96061665178a1ee44df69bc239ab41ba8504f71b8ecd2f99666906bdf48dc3073002e7b1cdc60e9f266ae720ff744b056932552c0fc6675064733900f030fe099699fe55f6ecb5575317936836a55356f885931da9ed1753afd2689ce1df0b8e105698baabe76a61cc998a1cdd6e5e5d6bf18ebc5f1793ec7ae6f045254f76bd7650546842fa1c45adfeb887818f208c0256c907e96402e3a2a36e759d5dc06a06b8b60f8086cc5f46718e392af2861872a0eb3f7cd2df7da85a472c46b3dfbb6c59a7217b8210d153e4819200f42dcfbac53c8842ba7bdcd9d421c9ab40b9a4498df3f7a41492b9b3f88b6010fae8b55af22c170b6c789c9d8568ecb7b1ac31b8d4c98dc05a8fc93574a98baedf3d7effd0cdf624284fd01c72ca6c860209a2a5f13fc8c776591f5e945bd25865e9c7248954558006cb45c5bebb5b34c994520178cc4e019bd575356a26962030583348f99c6579958c1c5e87f219db7306780facccf3c8f4fea2803515b8f2192d449dbba2cfee4586aca8c3ea7b29ae8ba9680100adcc036066f96634854e8bc0136bb70ab1850c7fbcc77f62fe4c5e453b70b50b1cc0219d87628a14b33d579bd37d5c992d3dfa9408e79cde0a1d2dc7684bcff6a6c8bcec4491da4b6c9f1afd1f5629b88fb3f0b418dda3234cba599a4fa346ac20240d9511c6aaf06c1870572200c8f8f8be002367c1f0bf1214adbbb9bb317f01ccdc76a4ee21232f3a62d183bce088a23b74f9d6b0a37ec905be1bb8a8a2a12f174e8156abfd8327b365c62fe5bcc1ad31fea17a3a50f679fe5a1e0dad4aac579c6c3cd13134b6119acd43e6b7f79b84b40e5aaa2e5afd6064da1d670b121bd5d6b93f0d6755e54c261c8c9ef0c535ca1dd2b96aca4b2d932647e46ad491ce2a50d5baa0ca7c3d4867ca98b95dd051db85f52523793667b12edb348e7bc48a0b50d6ec5d0d7f6fffcc219004d838d5de9be45aa4a65c4cada4fd42c23c5177f254ebad416dbf3087938a548b48244b663fd7e77e5bf8c69d360736ee9c8da04bd759e5b130c2baa96342f53aa60f619e7dafb0a99def7da3d662ad398faf772c1907120cdb224993bac63c5415496a0fd45a6c78cfac63dda03d382709b24e9c0449c683f40d9f8eea5e22ae9221dcedda2328e2861e296ddbdcdb7e0f1dde6c5adc27517fbf81d1c61956c8d4ced0fab823c2179717c78b42dd8e088639c4a2b3836e2f74d038594c43baad4c8086f402cd6e6a46e5f8ad1213c0164c38ff6d1701682be04e11d7d9cee6a670ddde96e85b3582eaf6bbb8827ecfe06f7b74b227d2f8c17db5ddacc872a4bcbafedf2bd1d171e464dfd445a0cc90ad827f6a202655934ee3d872f9783a720a05f633acc90453ca39ce48f8bf28cecd57da0edd53c3c2c9a0c928b5222403e12d26a96f66355d473bdbe5e0f3b6c366b863f0ba6dbba4d7e67b5cdefdf7beb82dfdb36dfa8fe8bf0225b7f239eca00cd6e3850247ed692691bef0b593e63ef4081fe50d46f8a0afb2cf9487541d0b2d8692a3cac4d5d81caeacc90b4ba4d07247b37c88ff8002c0ba6692f47e099c49904907e1bb60cad8dd4d509b81a8f8f2db51d5d6d6fae52e98018b2e70da5f02998bd24273b4ac7d228bf277c4eeec44a5fff133d718ee84ccd0a07e5d0f55f073680825cd6001d3fbf6a089f918174df3c5f82c48c21a9c8465b346ef0cf24fe6d6108d58aade32e82850198c1998d6b9b3b241704c38f6a91bebd55998a07b24533779cdaf6dc8a4957a6dee9cc0355b820f15db806c63588b344d0c583b3694702d99b5b66a1ec7de9b76024298ba34aba12d0587aef8f1cbbc93a79094c59d9485964302a9fd898db58b3b5158f356761d468f0249564ba0785b2fb9eedc953f3aed9a358e9be70dcd368b0af1be136ede2d5bf95c3eaa589d35a72d84570a2025540a6993185b886c116bd2852f4cae6239345f5c7b905427b78d3b01c3fbab88b5fac7c64ee3e91fb1957ee93ca8b9cb42e57690168d9355ade7ff06bacd2286b794c348f02003f7a340c32a4722da5e3ade9294de8db12a62cd12a70ef8a9a8d45ea4cff5a9280a7de949056990950747cb21d73d6b00eef0abc8eec4c2ad5904568b245dec27f0f0074b6d9a156ad476bf5d1d61a82dacaaace557dd96ce6bf342c1a6373ab54602379c08a3a4d976ffd466fdada108b28af4fb53efad6a91cc4d89c608d9b02f3ea704b3dcfec6c30e650c6389a38a13bbad51c5932a43e290128a89742ec2ce08ec51fac5d849362b390a106ea33728290d18879fe3d25d00b846093e64d1cdf97517a1640e6b6d2458091553954b0a2ac6f0e62d6cac5f12cb0ff4951dd9d9484c3b1e7811adaa9350465c6297ed1a2f9e3b227b229c2efee0490bb91763234ad7721b7cfbdf53ec22591fff6dcf7a8a126af08e4f5a72d341e6a5decfc3e0facd9af97aed83731c2d3d6f1c0a35924342a3f8b6a150b6a21d70297788d2ca9d986548b0a7e736bdb961aee3135d8bfb8d5656d18b55ec350e1f97ba476c9e67ba3350fb824471d3143e15abb3a8d7e19ff71d675347e658e7f50087a9899c3b140a8c2136541720489e2f3c13aea7b8cd02cf96d71fdeb11713098f9e100a7812822ae11040c500e19f5c6a519673e90695e6ce5a71ef291cfe6dd872d4b1d217408cd3965437e061204ff9e1c476a9c0564f58d93b99e5c7eb788ab5675e45b7f4c91fa497d036413190d393cb302e9235eab0b6b141cd62a98c0cdc7c620a8f2d2cfd853a171b98fffb90b71be2f1726c0bb6cbd1aea5e01ca074ba9313a9d642b405b1e43462e27f404d20ba1508cda6fc9e1fb48db8b20ebc452b9b4912f27e9358fffe7750721861cef8781482d59e79c7d505ef0cf63f7e7e80ffe2cc9b35b08ca07ad2d9797736fba6df6de3dc73324a1a78c62f00a698f797573e3278e02573250a5a72cbbbeca137f673a689194c6b17d56b8c8c024f12ad83c3ba237fc1a95166ca6eb419828e0c9fbfd02eade0d23d051b550cf6feb6419a5a5a0e6986fd9c03fff4922f4bf1c606ab97b0327b97698e75fbda1791a3b870f0ecb5772c9d8e907d7c09506e15a8f7d614a9369d18aefdfd3859960a0a7be6f6b211f09d1b5cb0e8def1216967e28aa30abf173aa9d23b9da7305f007fc35ae2dd40da774276a88cf871188acb94c7b9b860453c0aca031fea41d61b3868deac2cce868db4ed22d6d8d36f75446a3c84c05496feb4ae91b464ffaf999a3ec452981ad96660d9bad982038ea1e91786b219d3f5d2f50d287085369acc19f22b5410c18ebcc228c3cb3dd4dfda2d3302b816180a7dd68b38cb9c89fc9a65680438470524fff88b9b85756f8ec0751aa34b79a323b6d829d421461c40fce80ccc078f9f25d0a35bf2052b8fb247fee9f6e79695b8aa80ba86336034bc7f989d06cec577b88699aa85eff465fd3613b7f84c8ddceb7baf4be8a595d19bc61053f91f5221e0ac68eb5008905629a8958b30fb862324bccd91fe7e757dce05057ebf8d2f2a9581aad397baffdce68dd2dbfbbd0fb682f7c59dffaa1bd65c88fdf332330b8f0c762567ea943e4b642aa970951d79c91e27cf67267b6995472a66e5742e70e6a9b4bf5411c77e3b0195010099e94ede453e07f91024ce166edfecf61c1e0415f694410fcda9fad9383a93ed4bb4db2ba6a0dd3d8f3d9f9dd72a30b835a9d39c9c24e84b753655685059fbede957bd4734a64dcbe04201de20a7bdd136f8aed9e1a9c7d0d18a8a9ce1852386916c897bd11de9201f14608abd8360841e3996bc3c17febcf86870bdea26f8a031d2817a34c4b231f97677e733febf53a51b36d8be9bb5b499b6c69ea8f86eb5062c59695ad1c83df491eba876f04df6475574d0bd12f79ae0ffd7de208add36cb61076ad26ddf461d707198074285e48c1f0c7fc18f4cc305f0a907edc09f49bc1dae1d8a338ba9758587c99b7078217cb63a5f12d933c5b4dbbd7589520ef6f84f8dce5db55ba56c22be5b900eb016b9dd7b1264ea605bae376126254bc5538fa9073493f7c5dd6601900d9d40599d0f5756100071d0f86c1d8ecfb18a3a67c024b70292f2de1dacbdca21d840c48e8e6e38d38451aafd1ecd5be3bd44a4aba07991baa113e6e8125687594ddb32767b6f6414c620f4b5fc27ff6074802f8340a81c6530b99fa168b1b20420f4f63d842cede89aa9138cdb335b95f8998607f575b3352aba7f52e416266b6e00e1f19478c2d16e6a2119e399212d36c0a7f42ee6d93d36dea360e9b04f10f389fef0a0aecd103b170bfd7329833a28780ba88ac7a1a740639eb3fe116b93aeaca828802b1ed5251586a8a35570108c181b6e69261f289778101c26ab492ebb9ae232bad025106500d2822bb26915ef4df79771d7ec9b00c21403af58ab14c91a0872ba8105062b254b154d7be0ef08958e13c8f0c0ffa4d979ac36d22636a906cc8371a8e9ea5208cd26f80e27ae5dc40380b34e11374fcccc95c73ca224bac986a5d9b2761f3ce0e4f0726cf1cb358364f89d0fc8528e9933bcf0975b2101e8d4e32c9145dec216d4d8dfbd653714ccaf0e1816084a80b617d33c4e81923776852fdd38706c8dca62563fcabcb7f4702cb1948b01579c8ba0b5a8ab5a631b9dd8b187222e1ff54e82f2f7827257c7910ff41258d669a7f5fd9c975f0011df1c0efa2dcc80c8aaa8bf87817506b4cf2873f9cfad8e6319c582e54afea465acc443def837f05cd614862e8c5c4c55c9809c71687c1d0b9e3f8f6c12806d24cbfaa329aa6757e4d980317fde8f4c6ef4b9926d86fd74ee089c4bd141c2d9272339e471abe6c007100ff113328db4a97846057e552caba1078832227603bdb47f8d13ae594153ced69779748878d7e3dd6f82587c462e0e1aa46bc1de67c6033018b968c68a4d5bf72388d5cd963ef4b4d0fd7c485009c4dc05f6890b7799435f9042a7a8d413bf0371345622d1c9e8ecd8a6950d90387f7db3a27f399f7695c4bfc2e0a0bb4b2d947413a5cd386054bb4b0f8b77120e53462bdf9336af73c61902009e44d9082a65d32937b47c309370c1a186b324a62d88de4f49bc5b44c71dcbd29c9f5209803869ae7a207481260b24685de1da6ba5d1b03a4090bb9224c620d490f949021203c39cafd599128adf36221d348bad424b79c1f5d0cce701a9195cdbb868fcd861a51750ad8a0c253409acb5855141b74687ec3f94ec23599765d0d42b5dc0a188859b32738a81384385b4f8e68d39b68a1a855064628780d5c64281db57535bdd0e01d04ffd7dedaee5c369e262a95da9e7bf4837d44a395e905fcacb8d83083b9f4f57d3efa8e4153a9298bd357964bb96b7a734cec7c8222eac98b6b784a667ca5f799f8d74e920e6adff01334b7e2e44b71dfb4231de489d1566e0939c9aa0d54d16c1a9deaa142372a1fbcb6fa7f102558e2003dc46fdefce239e61df78413cbc36063ac19bac7a4ddda830ea4a2e545c4b554817b390ca7f8d0568fc35712de6faffdab25cc64c5837ccd88474e9b288d2ea30950d3ae7c2438a6f1a16720a8269eb4677512f4d3b0c6efff5805c0a10987d3863270893f4477b44c4eaa8eb73486fe2ff43b803666b212514fe903cfda728c672ba423151b36d39e571b0195069f832fc368d4b9591cc8c1aadcf9d5a28f9a2b104b9b563c3a2a2d5e7d594ad138162fac9b1d5a9bd8abbfdd8be752c064f77b27d422ac4956058771e2c8e4877bbbf17d16ca3ce597afdcf72a0813b577757e568042e262d063962abfaeef44fea3c4102608eefb2efe335a6fc71af3a14346046e571b7995ca520ffaecf60252eee03da02c17fb891beead7296b7942d5683764f53ace5e48037c779dfea6d0b090ed193a3c907e5b2655408f868eaf9a2e66127ae1e76f873ad9bed07fb131e4007d82cf52367c5a762b943350f20a5d26473bb756f75e9938a50ade086c3efcc0a2aa9f2b42c0464d286d254991e519d59c6289990396486fd10a3584c6d5e776d1166e434596bf0ecf648f66ebc895232a4d604016b68c53609caa9187004f6c9ef171e9e8550c614013d68e8ea2fadbdad230f47975a7dd01910d02ef898811f207d8c19456beb01e64f2206134392e7c9d82f348976e0bc01d6d5f281a0c76c8eaa843654176ab82f3566cbc539b3005d6d4904bd6ae5dbbc258608d3ca1d18158bc39723b5cc4ff6413355701245a6f0233af26dd745af7bdc976b8fb6efdc24fa54565a28fb4b8e6943600a431061f96f94adaa5d209375b964267fd1af9e56dcc596055f0f48352579928f055f6dbf677ee8fda6878e9b2471c2d485a65fa7a403c34c42845209f16813e467ece59f8533eb911953926aaea8df51cb10856451f0ce73ecd2979939482d79156955010516f7effa6e0b945fdd5568ea24cb52edbb78a3fba67fbedd223abf3473825c563cb7cf6e78642a2e966b4bf3bc0eb0c0c8063a8988af971b6635dc093072219c82ed55191495c23986631f6b9952440ec805e309463208d6331199ed2363ff4278c8aa207caddab823173e50522b3201d23d877dd83fb8649f3faca8e5e7bcebc6930bad1ad24f15ad633e2e1617326d0a7b27f86c2896cbd004bd6c7f9c31cbe5fe9eccb1c723bc695a4e4f36fba6bfca0bc7ca5bbe1293a268856ede96d425c9cb52409455725e967a084470fd76dda7767416f4de683d725426645f13d09fe6111a0acd7312c7e76ae597f28222d7ae6cdb7271b0be44d31c4f1c23a01e27484e375c889b823faa0d468b0439f0eea7d0a265c0d2928e9748f1f07d0b56f62efbb38f69497ded1f8398acd0e2d210f21de36d8cd62e82089a55da823431f181e6253f0331d9e4c4599f09bdb45a80188a6002c4b19e52803a3dde087c2cdcfa5b3f8eb2af61a6f82833c6521500e7bbea484663af08eb0e4a98975b457bf3aa8d2c0d43dfa38735c72c5fefa7ea0a4a29360dd90e2da34261cbcfd4699680a62a44dd4b408b8f0b7aa0249a442b660698eac770a67d4bb362644b33e89cc449e9f90c1ddf473532aef3dfd004926fa1b473ff7a6a49f8d731981997f1d71ec41eb8b2c8a82057c7e02fa7a160e2b00118dd22826b7dd82b4969e1952582cc2becca8bc8f72d66f32fab6498ffc18f77d23c2b89403a5ccbf883b29e2f04bada066fd80459ffacdfa60fe2e3a1e8870e2e75ea7b89c7faed3ee05b0c18bd9a404c71ba30a2960563c29e10b875cd30f984810fda4b7217f1f7715286283fdca1c4644a6bfb22831916f51300f510ffa4a0bbe6b2930ca6848e90e649c40c2c9ad3415fe48ae3dcc4a0d02760aa6da3277633c005f1d7fcee7605afd2d23ba6691da115110b97115e9fab53fd706aa71f458b4e3217698ef0958f62b35fa49b137c59a2c8208c18fa382296d78a0710d687e42728ec54a5fad81b1b0b70c9a251e66314991e68242e542ff6e1332ceb6cef745998aef232a4974d5091bb26f9c5d0121bf313e9ec9cc005be972ad0fdbf7d019853b05ac0f46248c61af7a71e61eece6b4db3713deab4a14bf8467c7b405a432238c299c0a7f2e8a8421e8977f1e1bf4210de66b8689eaaaf2dad8bb40f18fdae2ad855d7ec6f2c7b606b6ccdd2f01a239c801e6bea02867852aee6388077f056c904dc2c4607e536b57b2fa7bb7bd74f310b767d1e0dd4f60d0e84bf565bdf7b86ed42a83834685bbef2510e92f9b04ef708191c56956e05d48cf05ab7fdfb81c41b18822dfb221f90ecdb5d0a4dd28a373187f491504a8beb56d87379ef88c4547f3f101127831ded95fbc2938e13d612c6c0934eb003eae5ee617445c21ee378566b67f32c29aacd7aa8274a288a036afb19b49ad6132012bd22e53294ec09342e828c1482fc5067537d639aafbb81f957fed3c4b58e7ff2cf1e601ca75b93287cb8e35cf99d269848410c9aeb52e517dfa33fd6e267ce5126be6b5d4073854a64de72c39b336f9b6ce728a21e41f9585119c43eedf33b5b49fde1a35b30118b01a7d7f6a6eb7ba24690219061576dbd5ca1ffa5cfd012c8c870555ac7ca4fefde1732f3b3a2079cba567bd7935b22514a0088679be26df38a42a584c257483a8fac321f80b32430a4e91d126e0c9e7f1eab038bd5c050cf9804965fde05da911185688e3399ae325ab1e0bd0fbefd09b615629d10d9e3837c3bb4afb5c0c81590e315baf25fabdc51ed2a8cca962b049a30f8315d65587fbca6ca555161644e9a6eb4a05b74934764b8e05838498d3b4d436e21b2bf68b73e3b00b6f00309eeb84d4ea02a61e1728cd9f21f42d67cc984cdbb42a660e837103a2bc807b9e22cf19826db795a2aaaf8155492f2efd0637500cb2bc062108ed1d2429159b5eb9c8a9bc32a8fe41794361291edb21c9f8d5df0e6400c546ca7f02f36d8cbcc1044d60f4914ee95d8401e7fbedb038a8fa3d93d0715974314ae5cd5ba09a6d755d9874b5c095788a6267092472fe03f6113a8629ccdcb5b85f83ea83a1c6a231082a5ecc705e442482dcc255c6259557051f55bf23d67a5a20dea05f06eb3d87b545190cc6fe834616262aa1a51bec5b1b40299b14445759c32038ba84bae48831717be88fbe086c2bf11a0cc2e9b54bca77d5c4445aa4624b98a6f13f36274866f7975647590845004f6e88592e5bc271a002754bfd72e61037d2474236f660796934a52d80b78b0633c348019a5eeaef1d6cb096484e8bc31361eb69e6690dda184d3f0d59b4e1def5d6b5fe740b7785522c5ead0bce671ce7191a1346d87b8a9ca98a89fc40003ad16d866b1574b5042a192b1c47f9eb4591d4fed2a93b61cc5181549ff54dc5c867cbbbd06bc04eb99030cc13b378a4f193fa2ca938e9019ed42f7ef3c554e74b3dbb84da77cc95cff714723f3a99412f9497db276d972a2289f9dbd161025a6338294945cf167a43d3997fa27b1e8c5bdcf9413598b4d32218e0774ab1b1adf4274a602a1dff79881e60d55c48e531e9dbe2b7325e0d695bec86aaaf12b6f3de385433619e542e2dd482440d27569458a2477b8834c3ef1cb327bcd3d575f13981c7cebfa33449b67753fa29d4a7026f68a13dcfe892842be196ffc8be54ac8d784624a4133922ce8b26f70ebdf109a106ceda12196bec692c589993856e98467bfe7e55e7f629af1b30e2b2850ec5ae7c80dbf3ba52310511c364305821f4294aa0ec7badccde603270ccea814e56a46a41a5bf939f85574e37a99057681ab588e89c2107011df863afb4e53efaf4cb97ff79f1c07ce7a6666191ae1706d6213db879702931e48c428bde10f9a438a26e30a9f030c13e39208f96aa207fff4056bd79a45d0fec00e9924ee6254f8c887c80944a2716eff4081452f66fe4ef7b24ab6f798271a2037473fd3ee958f3c4f4136a19bc6d18eaedbd66edf56d9214d90258b189af6cf9853494a29f448a592b57b6c25cc9ffad615974e115e51e2b0e6c672ef751468dc184c3b657496d611c5f2fbb45505a673d46478a2adfc627409d5b82671017bd5016886052b5eeebe4d95b870cb674c71a1cb6ffffc7dfe54ed4d0c205205a22610bee61872047c2478ff7c218f170f1d85edfde55bc5365fdde9de813d2f011a472253f216f291f2a7137932441147e1b43b900613463d674b378fe68823aefced3f9fdfea3e422163c4f8ac185df5fc98225344a7e018bc09865e9ebd5cbb718ccfee23283fdb9bca7daacd1e46b688d41fbc1af0856b851dfb2a31d495f5ec1ba03a882046abf6c270cfc0dc145955701b91d207d2cfa7b2983598d0ed904877da0c7ea95d85e9c3ac3a18100c017daa6d009a0aa5d39f74328f4613d7886323ded61172ead1e674dec9d0ba379400d8ebfa22a407bed5f8ed047d34f8f0c0a4ab7eeee2e9e840b29cc9edfa88f14abae6438295748b1b5f34845b2f15d995b08c56903181bfb6af418800549b557b3d04699fecdc8c8dc72d547dd1ee68df8ecfae52309e7788ac402c7d977ac43e8d0bc777d72f1861e3762d5cc91d630b589737ccb14916f15bd8d538bbdd8fa51f529dd70fc4278a4408fed0782e425eca9dc5cae62b4b0dac0df699d5ce600ba51c03313d8d9d6019d5852acb22e96f97eab5c32222a4d4ab2632868c8bfa9323c7b4b96c0bc13a82f5d0bedd08383bedcf9c2f34c97ecfb43e479976b4b7c8bd143e39f2a7430710a06c39022a66120861e373f3900bf29590095a9beabfbde8971abc57e343b185610836d84e699888952a899fc4bf530f69a2473aefb2604dd186b1624257fbeca3bc4f4b8b00e62adf5bc379ee1a013c128479c52c1441df7d9ed915a8ee166b81fbba406e30da1ec43d88419c9f5f811fdac20f4903c0d7d21a4ade5f773799f684a95260b6239038e0d9e30fa2daa93f83fce639ad6fa600d6cd186827a43590009ef82e25ba40a4768d90ec3b716743be9caa01ab6d0c667cdd947060573d0e25871c5a3be5e2679c83abb70ecce24bd3b0bcfebc8bdf8690f9895bfc09f49a1a1ebb5137369407333fc5aa34789abeb04de7dad33b56b39f3502a95d7be3bd65fc46857c6cad5c1e00f44f8faa182b406613e3f77efd3508a3bc84089acb80be174bdf1c20ad3c47b94dff3c493099968098a87a96649479406c4e4a9a965d6310b2d82e3b580740a625c55258385b0bf1cf9c020597ca70b515fd858da0327faa9e81ef8de7f27aa84313e64b5b1b9e738c9c7ddcbf9c80d30f3b411c78814c2044864989ad66bdbb970d76415e09d49cab321570380dd15adeaedb0dbc6a05fe58793e5800c4566f3e85ee7b0a907969814ba030d63a497438d7d24e8a5391caad85ab1305761f8d818c6b1392fef6c38608c293801766264997048a0ebf876c6bc2057c9d6177f44f14c1185e93de596eab601974799d9f5ae5ade1d94e4463db7ecb670e241e7c567c24a0f0aec6972fdaf38804615fbcd89fda55abab166294a47cad064415d1ed954da756b39ece03e972e0fd67062390cad5cb710cc013745ab3c9548c65fc53b1cd7f911e4fcc667dd20a44a12f17b193ad079e9a3102a64624af0f424f86422c869003c7ef8614abc936139318a46406e4147cf8490573de7e916da4f335b8e1b0996024615c02839e7ad4c8c5c44356209aead7689288026edc10dc6863c92201ae98920cb475bab00b3285588901d39d49e89eb029a833959adf708254ec43da5cac19ac02e470157e45f048c3ca90acbecc93b9ebe2474ac1c0e6b650c81970c2fb6112200a53d7d717707c7cdfd05b6ce0f33ac3bf5df2b53a2494ba3fd6613b36f640a1d17e77bd1fb7a8dba701777629f2f39cab97e9a1bbd03e9772a31111564d16e381064e741a562bcf0119b3c275896b9b938eeed400b40d39e6507dfb576e1d1d9097da63f8c117a5106e672813d9db6695847ad15da531963fd3be81a8982dbdb0a7fc90f6eb4d230b2b0ca398fae19d08d52a8ef23dd3fc58234cb6e9bb3d6ef7db8a73601302dd6b3aba1fb71e67af89a6b9ea6ace18af5eb4322007d64ff18584ab6015ac52801aec9b9d7276d257e9dae3d172e057ddc86b65fc10c6b7d2d8f8730ff5e2e5060e20773c1d8af5a146d489ca494a8d192bb7923c809b0e35be05cb7834d2433189cbfaa11ebb9bbff4904efb24b0460e89e0914ce73f50ca0e67a82234e82e3426acafa04ec30a699db70c9299a8f45df548e0fb37bd83951813c9b3c1976876a2a03523908b5c0d07e36a5f80e476e2f43537df703f165958a83de6bab70ae12e3edc63414a8e8a5e478c78ffda311c11302a341b130e4e7dd0530150ac95a2d6e9014de249d31af005edc640a570335874b24dc8ae37498aec45ee017433ea4f3c56803b8f2ac964714ca6fc717c75d17e83e352bf4314a2ea8b73879ec7bd433970ad36c593e983df24dad601b2fc0691ae85e8055e076837928cb88015e0ea261e6614c49d8075589efa05804ed18921dac8e1cb46fe70fb5220d5cccd2164fd73c27a2a719c81cc6b6cd545de5d42429139f16d1b33d07ef22b5d7af2fa5a3218980641f6cd5be77db3dfaf0459719f55a8f7a28ad6c55c4de3ed4d0ade7eab0312d2dee70bcced855fcd99057b77de786ff49c5083ad591a067520f50feac82bed1c127c01f68f3d09e6d1dc4bff65524b70c65d9c4d66b45b3bf41667dd9e81e752cb2bc329dfa00b523e191ed1485a320f14ceef9f50fa7ea4ae4adedc11b0004647c854035f3c85c7c9065b5754fd1c87c72961d41cbf89648ae244be42055d084b1da27bb45ecaf3581b43cb69ed0e2ccd91048af8ec387a7bffd8885d0f70b99ea23d7db13425088ea70dd3520eec3664b65e5e6abba507558ce081c04ba4ff1a0a2d6146b81e4734062a8a8dfcb6698c3e23354f497c7724cf3248a3755170d6342bfb2aec6941bfccbde79b1ade9683d0bc78f348b89b8a464a828276643138e66be1fd3e38d32633d76fe96220e19bb460815468e736b21a955a1340998da8d8b16824466807820659c7786e2bb33ae51b248311c2a818d5f60999cbb3e82e58f9baa92fe2246bf8b2ce5407b7a2e21fd54cf195f949416e0f12bc7dc409a82ff38533c9fad295ce83ba81f52fa994481d233efe55dca1acca5343d8a10dc1cca6bad33395e931c5b8ff503948ce24155e3dd1b5874c51eff0d6a0c980073a463c440f675f5913f392218d2e7a7a075399358ac6a8babdd372d48a416c9d4006c6a195230389fd9bea383221b647973e3c3e8a88f9a39a5fcfeca752e082aa2a1954632b6707c8d6af4b6128c028f7585a4739b755ce86fb2ecf8df18bc46a71015a90c0fe91ed4ffadee5fc66381193d40b57b72d0206f9a583d33a5ca329a26c911cb9b77aca52c98a735a58efe8c4ff1c67b62b980f76d1793d5077942059129b04966ec8795d733f1594e2be38bfbe8f56e581f2f019df17ed1a8f6be6293bed0c45fa36db3eb3190a1860aebd193a7e9c4bacc40aec723da5946d11e29cfa4ebcf9ef89fc30ff9f0d5329cf1e186871a530dd02c89e7dd6c0c077fcef54e0e03b066ce23a01625ea765378c244f3339866b5fe506dc1c558fd06d6881804c2513363070dd51e054f026dd879a1692b20d87a6c864ba57353d581933f01cc9949600d0fdbc9471a52ab191975518843841c66f7c53c35f3b08cfbd793528101e2959e52e5099b9bb7ab743fd3512e21618be0bddc94e9463e8db0a6de9a927b36f750c2d77d48f3d7982a6e85b41dc22456a9a130e74be0eb442567c9f5161d753ced523fa89c4072663d72eff207f564e946ca942f6f2b4873e46776185f101d43cf7d263728d64e01f63818c428de85b219242e91ff03cabb5a56398a8f138df79bbfc17c68bab1ab10536a58c675b0831add675d6039eb4645c8df0be7f6e171dc1b2ca1e0289d85ec78041d280519109ce9f754b311137c58b5896ff22025609394339ffb0ee4ea69029a68d7ad953a1c7ff35450c3501e9bf91163150acb17abb3355e3f8e0c912e6ee3b347e284304f9bf3e128fde55571d506ef7dc281e8cb070c7b5180028f39e09e3f56eb038b137a4943cf5fa1707a6c32acce694f8ac3a108920eed99b80c9d204a6b6475f0eb2043205dfe08633347b3f189b500c12794021c7d95e1f7338f733669bc57f0efd57fd141e15454c7d1a47d8e70e78e477b7230b56a458e9ec4094700b66b13e3a354d8148739bf96dc575ac4f7e890cff39ebeee13816711effccd6fec2a5a46da402ee56082f1c41b1b026fbbdc5dbf1cd90fc86a62e8f4e6def437478ea3c58d725e224f7cf48aa6930378c33a036f1cc9a47efe5be3e3221a7ec18824b657135b9b282f30282679a5a86b291af96c9ccabf4492e79cac42e2218bc378e5c4865342ad4a6d1bf8f4eb61cf987f27ee49592e169cb809e888b468fedfc88d0d47517fc5e3a36ad45cc2f8efd5fbd662548d236fc7beb0e65a6825a8170ce05678aa0205ae8faf8859dcc943339aa684f3a7351071f8da75c2cff0327b49b89980eafa3e53b2cb61766e8a031d1fe1dadeafd08a2e15fbeaefe7482c703e784c8bb6c4c7ee9d17d1a5e659aa9096cb43d21db0a943e876f6b299354f2c5520a608c202b2920f415bb83422a49e32c78a119931e92cab3cfa020a95c93beb20e629d30f66c1d9e1a9e9a2888fec796074e0c3921b1610ce03787a71b3fde79d4166d7fc1ef69e19f0533ef11e34c6a5f208186c056524ab14d4b902ba25a31eb4fa078d4c1b5cb4d176cf56d2c4125f6557d3fa363003471776243dd23b1be4f6651ee29513c3a7a3b762f979fe1a01e31cf40ba7c057f0b9e53410a11b7c5f4fe898ca3c3165f12bdd6025b0d6c87588ad459a9c15b0576c4ca4b62975f582ba0c0fe4edbfad1ffe9537953e57f313b7e47c7144619f9de7f81aba3c4be1e6371b96039e79ac0f31f0912fb21e6d60f99e6468e88ab012fcdb708c14753afd9ca53596f10fb0a971e0202436dd6272003b1a041eac2813db0c7ed8862063cd65fd6bebcea6b0a665bef3a82dda0915cd215a966a9599a8b6457bec334b3bf26dbac29b28588aa8e8c760dae96d9e00a139dc28cb4844db5b64f2df274887d45936737dc1af072fed9a7d7f4785b2c4c570c0c7467b4bdd5fea3e577341a65bbd121bd9a72ebc33b2bc27b5f624cc3d90380bfd3118b679937f9e065cb1988ebab080d069c8ae768d149dc1acde53b04804328f12bf4171b36cd156e7e756dfe7b867299167d4c5c16fa492865ed3e2c8cfb7d4224305afb71f4bcdab87771acb9475eec8665ac9b7fd45a870e283d1b296d92e4c29e37eb6abf219080826351bf325982b18c11e77d2136e7fba2cd51bd94a7d102ccc2f0c4573ec18366973484acfaf9c3fc6cc6c660245c1cc5157691e029d42bbd1cb5ac81cf308b1b9a07f4eb71ebd951f5a6315d1bf3913b26de56e15449e7908d0003254a5a06de8547a7b379bfd46051110a002d4631c16f742d7a6cc47bd3a0ed92594bb4d31557ff390f1e58e748c39c0ab6501cf876e13e2e8e0c0fc106b227167fd76341875b2841114c973b2d154803d2db2298c42112737033a46b126266762771a3f12a9e905c4ae0bc58af3a28e42995551736e2a9fdf7e644e14a88b4aa460378a6464c3463417cb4bf7dc7d090c2ff7ce1b32aafffa22bb125ec94a2f60cfaffe24a3691d315decf2ebdf5291ce81547d748f56452c59cb8cebc6c91dec619064beb0a2544ecf9734cb1f4505208bb3f305f9365b18729413c9d12bad419f23bc723ec4f7628ff094eecbc2987e93fbb6665012b9e801d4a73a72ef607ee7e034d3c982d808377a6f212a973fc9aad220c7343e5bc94cd11536ba06d666f418ac4953d7ef1f8cded8257143d5c4b0c16268a1994e6cc379d7e747d366dde98137906475684a45051b3073a87398eda0091fdccccc878fe0692a18b0b06ef604e237678bb670deaf5d68184b24503acfebd5b3288e523073591529f350c8836f6b224bc71d3d7d0b626ba9bd765eb9f57a090fbf870b2775a06bec90423cf7e98d097dd96ba0e5ead3cae9f78d141f5a084bca8ec572b394132a8acb8041025606f62cf07a4b7e9a978902a8e52c8a01a9c2e6dc714da0aa80fef297f6f182f65ecbe4e444366580a7a13cc66741ec250da7081564e51ff7d3cbc2a24d89f40246ba6e16ed1d94fa887f8bf5695402c0ccc845cbc288b86d1027f31ee3ed6f080ccb0b292cfd142d1c82e77ad147dd27610644b95d0541e227dc5dc803213c3ac059fa26f69b46b28e8078d5eb65fa0d42d7a7d06533ed28c86b0fc493d1ee1343954cbb08fd305f8542904181d8eb4e62c4d2bc126a6a7b0284694872ca1cef91020f814ceee98d250375b04532916397ecf16d96070cb54a9015373c7e9a6e80f7bea0997e09a082f5fbfe13e5df6f4c805c8778ed0738ac27c7128c854ce10e2011367d802eab18bcf3744ecb3b32404542cb14ffc28d3a9acefa781788fe8efe0738120b938169f05ed7569e5f55f315aadd80a72d14b46a7b96e192039ed2625addddfab08885a07e974f81d5e6da5ec0f88e52304a05ce4e1a03fa564db2f629b7e46cf60f46caace83e75fff360a1756cfcf2278ddb32fed0f7740ce553b409fcda448a1943e406c73be5af8f3bf46c031c7b7259beb345d2fbca407fbb6c54bd67aa20c71a56e838841b52c2a8813aa775660b6036a38ad1c1ead3715b43d300f088dfdc74fd34553bc8a226ac05f2dde5303803620b4e2eb567292e8a2fb4f8430d530c49aa73d81480f834815205fbc0dc440cf7246fb0c1f67828ff7cd0d3d52e72f952a1b852633b1c7f13cd721d5f6d50ac19dd3d9a1cf3ae28ba4a43e0d620b32b4b1b6038d5d6b15e74f043553f6d291aa1595dfa95dadca6020c50fab4df2f5d70c4985fac9030d8f3c95b8aa16a3a5f5a6e76672ccd39157f0a9981f7abdad9283dbeb4628d6bb4b87995fe0f7386259a26ba8ff815b339bee0a54e6886c2e0079b832f7e57aa1272b73285d29fb598f641344609990cf9eefdb962cab767394fe6ed52cd5751f5024b9ac9ae8631ac52b2edf773689135ed25fb4b1d49206f199b1ec264601a8bd693663faac651587c1edfb2b03654a33cf0e777918d8c64b52beeb37d25dd36e201c495bb7f98a07f60b32854d6ffb304481eeede931b31607a564fc4bee797389a1fc87ad7fe7478085a575dc537fca2ef712da5b59f7a7660ad3abc28e71477782526e87851c01dd769c1979f88b4f3f75225d80df3bc6a2bafe7a0c15fab454a0fed530d9d357844536006a7e8370247848503faab314a1c59668ed0b50597cf424c49c8aaae37fa62bae644c8a16d1d22de5a9fe1495ee593c8cefbc519555f40ef20df8c2de25894c69e7eb2ed6f87f849226400ec8431ae60dfce0dd2ac62e166880a3bd3a7ef3388d3857d3a20e8299df873aee72d1ca764203b648339d6e60bdd073f5dff08528cf92b0b133b7a2552f13cc05188b748c028154231e7cdd734b21771fc4e1c05b3eb6599de75c6edae39f13cba4e86122067939ae280ec2fd93b7ec496cd597cee77cda2da242dafdc390b736ecd01f47e10d4438986eeddfb9f0624c67824e2e15c234f40c563a6ebbfc31c393216d7c05b55ef3d141f1277627d5e2674910e8ae6c77377cfee56fdc79a9452f1a5fa23b17cb2d25ae25c03383223c466ed095a334124790fab110fe2d119730f031d93c3111ffe672c1d948e7c0ebf73355f752ebad2c3a34acb1e0bf224396d78762281bfeba0c2063f630233c6703b9c3ef276800083adcd42a40c45cc55c4c1834167982ce8ad0e4a46c83526abea09704dbf867eccae0d78981a5c3831e38ea06b0e8e1b4ebcf6bf26cfe37cdfbd96157aabe13d158e357285758828f9d7057261b0fb70bbb904cbb7fba1d74086123e37d74cb03f384556584b61874b283d2135031a4c4bda913cc36a595f33af80479579a5b8e5e490cdefee410fefe6c465b3028bab73898adba717f8c49afa9611d50d6e2bc0123ebca0c3c9fe4b751a60f328d375942bf202e39df0bdd9aa3f000b854ddf4d6ccb472be75cdd960234441726da112fa25e90bf0db59094191ace2fc78c4d1ce7862ddab99f4fdf66fad3875e6b8f4869772284f540b188caf82e89b599c2539a1369c6e100fc74ccc8a93456b75fb8b27ca7bad18f6d0be261429473871e69fbea2a794b2b7f54b6f152a3b6d29d65fabd3b6b1023aa018710d6db768c25176ad51e66f796708e56373ad7cc35e7b42c215864b480ae6220737864c1816001377602f818f676dad355625c333ac4a89ea150ea6eb001988e2c5b98b5658da29abe8b2929c86cc81176f0fedba3a076c5cb2268b7c1564c799205dd25da9f6864779fe413ca9441cbba86f9d3145492d14094fb0142897b8a31d73c9da6512084bcd7a1947ede4b53b86ea4ec943a720f36a1f6b6caa9d5f7455ace61344312f27c5a022cae3a411cf793efe910dd8d36f63ab0f6711ac70b4366c35c11ac73e4faff5dd5e2ffec39734e31f7d829599d5f563e8655bbb77751124ae76f9b0cb34fc97fc8ec5e416f1ffd13ea203d3e9f614af37a17275319c977a976a5c83544e7a60a215de796fc5d70ac4a81b9d75f837eb85b5bffeb84ba444445acf38f60008f403a0d1ca84f8724032f75b0ed7c5e69a0784c494c17671e6b61a4d516d52ee425eb9717bbea6a4c9ab7de1af72e20df492a15da8489c6d328fe4da171ddda2b3b94da40ab54c6abba617fe121ce0ab0ee8369d38d010160727d8e13a3eabe14471d790fc5a7cf419173817523b0f29637c4fb2fca62f9bc646ef2137b7c6acdea965ce0a561afc3677df0f853d9c2772f7a0760c031be857aa526e56b6ec3aafa302dda916a01e7814a3cf7d509f7edefe4c8f599b61869e3a77de5a4ca7e1ed59bdce1f1e6587c69a3b9adcc45232be18dff613c2a026c67de6cdf94f0a9fd31b81c9feb49b659b3f21fe4e2156b25a77e73c00ca229fc7f670eea359e231724aee390454c151587340a55a463091616bd6bb4b01e71d290eba0d9c4351f1a4721ee3109dfe28e73987ba66b03cb47c8e836555e9a70a56a890640c66ba5cc533d3a2d95ed7c4c5ae455f2e6b392352fefa32a8d33f1ab81cc45fd7a63119294c38ca211c7baa27bf6e1de7499d4f839cc89391a9973ef81a1076026229241f416ea7a785362b0edbb4a47dc011b08eb36412031ada86e2b691ad6c72bac4ca18120cda3ccb32808902274208b844577ea27438cecf60fa09cf12c2a0eb7357f943d521490dced8e40dce3739e415dc8f51c09de87f9638df121626a995d56e08d13ea61c2827ba5e14fa38b8e17747c1c07287545ff37dc8fe503588720f6412aba2f88e93e21d5333936d58ee125bddd4f016e318af8cfb59b21515dd2b0041f0d1e81571d0db09dba0f51270bd48726620e03dbd2e3bf1a45cc7b661a2d5bc42bc4df3a1e26dba1d59a13bf1744410e325dce5d410cd47b09277fbef622e920f8095d6fb8b68849f2d2ae551c8eae3db31c0ebef52a7001b2071e8734b2cc9e998488b451e7ad325f6c068a425badc95f393e2f414236972c27558df7080c2ffc5a0fc5c7087dd0ca8c509b4774455cb20999b42c10cbe30be2ded4385aa1854e3a4be5ebaeeef3c9134527fd9fed7fad0a512bf050c64805893ebb43249f65825a0be4029b63e15273bd06c9d11dc257ac95367cd178367341b5a536c726aad192c9344376a90f431847d026a8882b372eb5b5f351722ad575c277856b2bd6347ebdbe5b5cec2bdb626a36203b963ddc8bed642b38f1e24bd6116279383b7aecaf7f38eb355f253f2fa8adda3afa3d5579be092f12a6ac1ce9f8f3acdac8acfbf72e6c09e3a4d2f6ed6f84085e7f1ab96fce13bee87433332be6bb8e5aaf26a8b7498cbb35443a6d4c90dd75206298a1c9b4ebc9611eea9dc0632de342ea52ff45add57fbcb0a6756de4cb7fd1d4d3a9ef3a41f8b2faef0cb58ff54317bcc54f646d17c65cb275c0b13f2496f2864df294e68a2664305757cc2b61991abd1caac2755d74bd35026710da78c1e549e5a303df90e1e110d1f8e2a6f9a5658c3e689427e76281cf1e67ffbe1718ec2c2a63797f4debdf84118cdec1087a4d1d4ea43465c7dd517c561fec00d44b74aa59e19328a6bc8bb2ad572eb244001adb6abddabd0724b96790d343b3d719b9c0404dcd227ffd8e47d5bddbe380730c1d84d75eabecedccc3c22c0cedbadaaf0ff3dc08a4750ee21009ca2abde1e2b5a41b580e1405e0a8fb17b60b56a2f64656f5472f4a1b816ecadcabca05bcb19d20d06b9082031bb9ca48618200366cf4e8094cdb9ce69c8150e53396d3fa1738eded80dac5fbacd402f5017291d74c94fb462c16682cdb0e9d2f57e41ea1a12c8e11ca34eb831a280b74ce76f99c3d97d87f2c24025e6dc23a00b26860beadec991cbf4ec463ddef61ede52f403bf8805f531461ce78b2e3172602a4daf7f099115b9c636bf2e102ab88c437243bcab66463b32c8f0bc307e3dea9e4709af13c5fbcdf6e77fd082d152cd2ac9187169ecc5158e75876ea935b262dd72d038b4b7470cb70d219a459208fd54931a4d10a7c215afe37122510404db8a6db0e6f7fc20ee7c2638a018e0e5f790e92a005fe77829208dcbda749c4df98ad4f2ba0fc08aa7f7c007dd9b389d1d077af2a4eb4ce61dcf2d9e45845b936c0b54b97f5c72fd922e7533ade334849cd48d6f9600424cf9a3c57775002d18946c916589a8dabca2203b2093360ba9fc8e229297f0dfbea30443070600ca8fce3922f6e8fb723ed171c06f6f3ae0a3efd2d4cff0536171a89a3eb7789de7eeb764ebadc27d454955c6d4f55a9c4fa6e0c12ea0edf798e2b919c0ceb8215cdaf81498c298b4c694e91d65df0d64e673a7b7e2e9211096778b895960a36721830aaf3f615356b422f17a0c0ec6fae7caa2f035e612e36da4475a6d36544acb9d13c7fb47275b26ba4974f19275c72e691f7f81cebe0b496d4c133b09c7e1479cea311981bef82b29b7c8c4480fba183c9013a222366289cb4989c3d726a072d83f383e3591b129ce374140bbc9ad76963636a587211028ab67cf62dc7c42fc40762b36426c4b033def2bf4cf4002b418093d0af51c6af7bfaf1d95a7cfa986544ca61bc068a61eb5baf96d74fe5eb424befbd225f7f1acc2d023fa96dae9190c228a6708b60bf889025f45ca0752c65203787ae6aca745ad6bb69dcad385e66ac20edacc60e34589913bc36c7d11b7d070e0d432f48592a1bace629ec22bb87c77689d16db07e20f9fc9eee448d30627e7cd4ea292f3979fc6d97a074aa2f30bc96d434ba358a90c3a76eb92333235c036eafad47c91aa35614cfb48c433079a40ddfb787e19321efa0825881bfb865f035f5eff0f3a7705cc07e83051411efb20db110a2cba1a769b62c1359225d0eff034b504f9f8ca86e12729ebd77c8e1baed1aecab60490a197b3dbad2c08d043c71fc45895225fe57af9b73747b811b88a715498ddec5dfe6c798e747f1139348fce30115a67db7fdbf71a1c163fee63e59b3afe0da5ee45236daeb64830f9b8783e35332c99ea486715aca3a25cb8f70c49f2569ba1e88cda5091c4736d1d43acbec9611fd2d42d37528b9d2ec3fc62bd03002353cd90c49b95f713e3f8561a4fb99e52b165b21e380a7e84497539113060704b4ee81ee04581aade4c6adf7f7205619d7ff1eef6ccc38c0f0373eb872143e4ab1b3d28f942d617290686d3446aa15562e35cab47f957f17e095ebcd544d2d13d444426587283a08c7b5a1db93ed9e24fe4ae32b7d9d1dd3d4a93d48e926b86204ac4a18e6fc8db533b3cd10fea2625d8163553f1ea87fac21241f4dd8bb2ef2d2cbff5ceda03ce9d759d4d22363e46562bbee137c01ba76c2e7651fbc47be0bced5f5730ccf41a09b1583440c32b382a21e2f3249f47472a08a8555a5af71337fd077923287e9bb2147ec8b68f2f13fbf6f66ab7f52e9822fd611446bf8c1bfa7190eabe38e143ac8c391525fed6f32ac222354caf0ec55994b11468dcdb6c334e0545062e88c8108535f30074c5193fdc2f06dd8270fd17b9b1fcd94fa96ec4ca722ac79626a340033758b54a24778ee6c665a9c56c555d82d60633bf32835cdc7fec4170b873ac793c2b686625aaac35285312f5ba7ae319b4009134e839083eae78bf5b503f65b1970590f643f5d6a3a6755b41ebcb7fe340b9912446dee78926a6cff79dcd35a5c1a698664434918fd3c5d2107d613887b491a33354f3ba69107590e308daa6a6adc407c83c2e920089df13f83ff80ffa6f62e3e21b33d828fea8dc6fac59843432a9fe2c430e229de21749882f79d1f208204092f5573f088f0831ebb2f567a5b7b945490b2cb6693588c35fcbd32eb07a2729a29c44f3b4bfecd25994028c61cc5acbc3345ed8a53fff35967304e33aef53126fe917eeefca6da208020ed4b62c5ddb75c77a0add9931c5343b6ca631c78545a40140b989222d4f39e9dee94fd1c049ba1b16474f831888c8ffd0bb68cc26937a4b48fb15a941824e7306f3c323af92ff90ee37316c2efe850c70752eb1ef98e84d1ce3472a1046d9212230b3a138c1640c564dfac77902f11c4eb155d6a3b4f602e57a94f570a02995e072cc2b0e634b61d7e4cc79f3a0d5e80f2b4b9c73ef658f415cb37624e1d3d4949626a882aca80fdef868abffbc14861399e0822dee1b442f843c893456781632fac46a74d8526fe012faa027e15e4b0a9a40a7f36b55d4ffe546fb90c19aaef368b60ac82791316d722f0f97695ccba46b1b12184b7611762b5f2c27478c545d73d3393b5e819d17e81bba9bf729cf872e1a37c91e3729fb360e738fd7121489d93324ee7a3ea18c56c7be8ef8a764efe52bcf4215a68c40c873a0337a2d0e790a83f317230a08d028d370d974d7f570f888a73020dbb9bbe54ab7f49152bb916c1342c81f8366e6fc008ee6b355fc239bdcfc9a9c309641ad14de893604b98bfb35ed5438c0c11bb4d9c2e0e7fc5bd53c403e2920d960538a6090aa57f915db8cafc43a96067ece967b4de32a0e174dcf6d0c2ba51c722dc3d194b030b1ea2cbcac4baab87ee2ebccfa66856c3e89b059b9461eceda6b8df418d017ff45b9427da5f2277952ee81ee5dbcf4d35940cb2a6e244c443614fbb38c91909f273dc5955e953f44417c16989523f5143a172fb2301f795a4cbba17a9050d4825d490333d723383757e446bc91ce859815108119b6b489d48da6a86e9cc27655bb23a87cc7f9aedca99b78564de2fedd1e96281cd506225e1274cc3837e20cc7584eaf2f08912dd8f86825eec522fafd1324f41bf11a98fe0713822ddcefdd629855c28d67c5f5d7ac428e266c680567af9bd0182c56e64461af709a8444da04a342d656f3ada3599b7d19e129b5edbe98fc0a677b775d798a8cd9e7cbba9753dd41d0def169b23002861b72bc711fc3b4995f1c8e5426ab846a0fed8e491a40eed6898685a743ec0c31ff15554b0704d01c4af9d8340966540a2b0b3c28e9bd9c926486dc1bc125998f39d9ede2b6811934a013c76b0a8a38a962abd2caa99dcf8c15250fde939169b2865df19b028db61d98794da6e3a9f3aa08d674f35669dcc7b7ae332fb7bbc24c92ca94951ed9ed01f91e97d84ed610aa3fccd29dfcaf9de0040e979b3c3807086a5c4c90a5bb6c9288e2f61895a68d7332e9f0f1bcadb870d232e5ea46f821e313bc4850d272639c7697d3ed7572224191c4128d7cb8e6822a3366308cae9b2c16b197f5ec8bb91129063fd8b0481985bf27def823a60e348f2b37b6db15a15f1c69d4dc54e9b46f3b8483e5c41f97be69f2cb4654a4b9fe78b7d8f2fbca213611886990ab9fe52bfc7fbd33581cc3db8aa711c45e5ec08a721a2fdc26069dc547e44a08664f9b1e6518243b21bfdf0a229679d5bf4e1cc65903e13cfec2f83f96ef4b4ff6f0be5af9ecb48598b0ec99c0535039f82ff5f3dcf2d9818551e823eee89bad82b327304325851b34195a3a0e4e4ee56718978960a51443c28f1343e7623ad8ff1eb644d3416012cfba47b1e60875103b9a9924d5d2344392edc5a36548c198cfc56c55f49abaec4bc5ff5fb6892f59270ef6ce7d988b7cb7e5c97107c33a03d1e2c2779a824e396e542d624fbbaf4e9148a89d5d54c339718c4f47a1499798a67d71f4c878ede26fe5288ab82ee3eb8ca4ab1caa88f402143c05c903d7bec8d215ac71c2a4412862ff29979947a17a094326e1f5278db9b453b2111d4d8d7a6aff23bed2ae2a6458c003c90dce397acb7799046b6daf57f3cc1752b33372cd634c01dbac9a4f53895e0c6d863672918c889aa516627f2f717fff2d33676706d16b912c757a40c08a41d32858fa30c631859928523e62b1a0d0a13ae00e43e0db65443fb0fd2309225bd9e007c70d5b984f3e5fe90306cf303ddb9844130c2f3fc39dd4deecf5520ceb6cc6f9581ebabd4c18066c51e7661cce0a65209553f9b921d914258bea76731aba2c18f040a322d4e08b6aedb7ebd236305db5f898aa8d8bb1576a8e2055c68b3aa4f2014c1044dd9630c7a23ac56d597e0d127d88c290684a03aef9981d2ae0f920e1b7ff4428d113d9271429ebc7f1ea9940e04c323fc1a72447a63e1f3228ead4ed5165a68e0a1846e0247e2d9c80eb2728af7c172ad554a66f5ee217f10be710cf071ff93491cedcadcfd7fde0a3cb485291eacf633acee5d3602efdb7167f7d0ebd2c41e86f86de85f7b6dfac3b84c1ab3aabd12ed6fa7b005bb2efd90497f2228acda81e3fcc043c46918e852a179a8ddfbc17b3740c29776297413f2d6e984300d4b8d7b6ce5c9b50fe146cadc205c9b079b56ac5210fd9f7f8b0ff27f3c0a791f71ee50c39f1c50079dc5e4d1882d3ff3c0a6ed246687c81bf3f4c2202373ecba7d06069ef6e6aee9141d6405de89fcc4b90c64171eb757825ffcaf526adfed04f11028a8b44184c783da6436b38b8369a4d4854195121ef53486d3b3860eb0fe269a4811e92767231bfa70d479c3bb1eb4548189ab62c6b50ddda8b30b3004e2c7455f1668fbcb467f515b9a0990dfb2ad9b64aa46ff812939c823163344022e54e52da24719d9364aca2db01f077aed696f1c5dcf2b42043bee372a5e0c2d4fa1dea0e4637e8942876cb4f5dc6d09e6559be449b383105a4907faa38c8bf3e054e26506d97bcf1f37396ffe44414802f48e03c1e37658f5cedfc425e516b2a06af232cb8e7ba9d1cfd4f4d6bece5ed45b4ceb7a54d63399e92a0cc731f6944cb2b8770aa9d45a1db70110e3975c7f17b3a619c980700ce7ec24c7526094fed2502d736314b03a29ccea21037fe2ae97bbd051b2e4bd2477dd059d8a044053693c8de776fefccafbde3d93b60a52bce7443b096e809dd953f8433d77d27b4c5bc1a0e63d5b7509f633165e20306fa866b3c321945a21af6f2ba68db125df35a6fe5170ca62b22c1002b7faa9cd38003c82324b1ffb6a7ade9826989b7b86b5bd6893dddb0e513f94f915398022e1eba0be09a611f0973a9217a562b3f613022a469651369e264b4eb835d14c5256f4018aa381244abe721cb1691a85db3fa8093451ab99168506ae5450b45ee65ac1439ca04f6d1ef9167d26773d4516f941e3a156b189bc8f3522d5d168ea492e52bbbba6264b06855e5868adabc6b515c19d50fb71d69b9f310ab722fb2a4dc57238476f6e419fec0c051ebcc95369932fe8dbcc11f903b848f66d424df3a5926fd7b1258f9d094504c522e51fed086807d42b5882e6070be793d931cfae69df492dfe15bee66a7a3222038f1e930de283e70140dbeefd8dbc8ed62deae795b3eec748ab41206bac2bda84a7f28b16998e7771a34f9d029e50e4afb9a829894e32ea3dbec6d7b99a03fb1bd6930965ee29ab62d4f95174b4b5a3a47965798a2f92e19e33a1d2852138d4737e762f611d7b240f5ef77b762b4fd76cdd8ab440aeff51c1ea11ee05c6be26ea928b3a12dcd5ed885b8fef6f12db62ae9aadd53623355a10fceb6ff152878f14377879a88b6bf3321e5dd18b1232f487f78b835ea05781884733960685058036322fd6f4f87f044324b9f438636fc107e5bb538a39e7f43b538ead87c79338cb3156edecac4511475fda58abce72b3b3e911daf4dc3fbcce79a557b700c12003dc20132ccdc31c9eca69a559c6eec705dc0af5b7bb2c2dfc68c9f3606edb893fb28b30708c6724fb5182247e8774eae76cc2c65342501b11f07a4d4f924a6096c320171b2b2311e60757bd994f30403c8240712da162f26f1eaecdd52e8a90542fb0c4b4873ca03bacf9dfeea047e6586127fc0ce588290cb2e26c4853d8ec7577a613633f2d14a982fb2703970b6f82a90cc19d9f4644f2d56b8d8078e8302ea3ea7f785e36683579e2b8234b71fe791e075bf11fcce3bf3081b65aad2d5965316efbe5a089b3b49eacfcde4882c2ec423b5dfa3aa3412243f3e138d38de334dbd848bf422a6354e9db798712b7a801c3700311a4fcca64169daf80ea8fb76a26faf5c160e7f3134dc4ebb5b56d9dcfaefaa673d51f7adc3aed5a196f576409c8eadac8e811e0462216a5c26fee56e3847c01bf88bff97a1620c0f1c4fe1ac9d607f6581ad08614dcde894f62b0a9bf0c562e7abf34943d83051879b4224c94cd3198159730edf90e7a9bbe57d0a75601c0ea8b5be26ebdbd6b5227f7b017085ef9174b035f5b44c72c26b797eb59928be3cbbf474398a2a38920f3d34bcef812a4ad68369c02982f651da3cbd0b3cf8c7601463065fd2a4202d2dacbd49bebc232ad7cfbdb0a6f87d81834f3b8835db9a6c234cbd171a7b45b205f56f0129cd2d30ec0612aa19269f9b1db68fa7e01835db201b12893ff5203e605ae94b978476b190c9d3ae2a26280b651f6673b74d3265f09c5eb772b303987d6acf5e623aa1a0e5e76c1c693d700c20ff9cdd8e39644820d2e40c7b8e87c84dfb85f3bf3b8f2e65a4c7d892b3cc455d5963f5a6d16b9c8b1dea50d49c15547b1c0d3ba73ea3896c5a30d598899657327022633a82252c0fcade7e0121590bbe247761be2bfeab60948fc899764e713cbce43581d0efff9daffc5ab5f144ab08a832329b2e7684ab5fb99e79ea43d924c561e39c777fb919dfd1dc80bd3694dc686ba37093074f2fab89034b36986457a2c7f6298e59815785e8fe409a75623397ff17d4349e6112f824d6642936a14e8a3ff2ef52eb12d03c0e290dfd60f7c8601c5da6e58d0614dc2a3a88ae660c06d62d0d635b70b8cba8eb2322698fa9dd765d4f7cc59e9d3733d4cc12175cd04a4052f6425783f597e7e18920b5e01a259343df35553760e639121fd6c53376b6bd73b8e87dcdbc7129361a4d177de57bea1c7bb3257166903463c6b289f0ee3679221408e6ae2809fb80a394dffab74b91b761243561c15de6d50cce4d5bcc07f36c19e633edb5430ec87b3691713f185e5c9b49ac26007a1d6ee46847bf9c45ded53b3a4540efc37f4970aa17ab40d5fc44e8bfe01b54c9ec98ba3c0109d32f1feb47de0a4abb706a926dc8959a4ed07bc3fd869b062ad4c5c6d8da2596cc6edd545151c0cec5457cdff824589131844db564a8ab260e7c450e3170cf4959949149eab5107756ac576d4c10f02807b5d181a884418285246866a91303edc94c0b779b8eb3d89a7510655c5b4ad06bea03d37402dcf5272b2f99d80168da51f524e97524bdccfb3ddb061f51786b0bfdee5c00b10256ede1056178119b36c23c13a78bd46bebc6db497934bad03d534ed885e776c1aabd90815a1986ad64b5114093580a569e34c0071a0ab00dceb272598c158f28456346a2f5c878c979bd416d3d7eda3d8be4c53c05a082d106803e4aaf12d0a024355a85dbb87d687e042d7b4eee37214b80b1f9f50f6654b767d0abb41a44fb98eca57e875edf4ef80a62a5148a8b69f75b93e154bab6f91b8607d71008d0eef6e63b208d41467e6fcf626660c3b08b1bd066a013050243d59b6a6823576cc66eb9f17d9f61d00b6a22ea671a226743d7588c69108f9f24e0f47f31afed5bd95b33687d5fd016994f972c96518d3fbd285e6534c2e8d5a0beec1535b5a374da5778c2aec2baa9ca85c72726dfba61243b4be7ca7e06773e91919bde57d1f9b456f3bc91d6ed36961802ec8c0d4cb211db25a8b32c57f64d1bccda979f572f94ff83453aee5b2f51bdce8b789bc4006f9f337314e1472edaad3f595a033fa6e39779a58e55782361a32cb6ed8b4018f9116e7027574e4df3bafbf816a56453455e325915ea55578c9af33e0a7be6e03afdcf7f613c64e9f564218733a38973ae7a8dd56eaaadcda58604143f2dac99ae10f7eeaf26164a75897e2f5a21db0a206cbda26fe2dffd29feb5a93d56997000cce3a1d2d8c0205f906e09252924844506f1b23b5b692a554dbee10c899e8d6bd0d9db3b91dc0db660a025c1047cd2a693db7936079c4f092e099bbdcd81b315e639b3684a6f8ddb7c25f69c8027512513c4e60a8b13322b2d334dd177a6b192651e00f158a73d6a68f7251a8f819ef841736993b1123ff6b680981d300b70bebabe111cad89523a24f8eb24b155868f1f5bcef10249a1e53d091f533c44f78b9de42ef9ee763362a23c3acf8145258b54cd43937ccc96c99c5b44dacdc35443f1751c185e8751480cd7766ec74274cb45799f0353789a826b864e0928d1e4806a57f844f0569d8a30c1587b7919508f9217cb4fdee4ae4fe16e6c9c59cd1d07e158a9624bf442ea3820bbdaa847603e84739c568e87b0af9efc5675f4ae71ddad6483e8d94c2d8370a573a5392d0e641dd758a4bb1d855c55fb7dd7adab4e4b46b54220a30642db6906331073cf110f620900e1520e7d4438c239308e341cd91794ace3ca1ca954bcfe05e82469f15c5e8227eff266dea2a2335b3b2cf74f5233ce734a062cf0e8d7da37c9247c73ab3eeccacd0ac6fd1fd968dadd14d416c69272d27d12d76aa55d72bccd38c26fb4a6a088f2a8dfe1253949fb3709287b895c283df66e0ec0ff58fca947d6d6086af9f42c16b22d50e6b69df3e62ac7f50cc050e785bf374f5ad8ca69e3e599a3d7f4eeeff1ffba078165a978764540022abc47772044740ac840017b529363338862affad009d92d9ec19330e56edac0ea5ef20329b4501935dc3de2bb2df6ac140c8174f8577b60bd3e642177aa61a692cb6dc80559e2ca26bfed656b8d8d6d62da7b611046876bfd1fd4b28cdf1487badd371070e1bb1e1ee87800ea68251aaf143da4162718ef73b64b98d69c05d015991a8d97c5238ef4288c4319b6ba924485ce7cf5489b18c253fe5d4fb3672abbe46fae8ff38d97c3d42e620aa85b65ec7ce17d3309d15a92933405147cc609f3763e58ef3d183cfdc18493f2459a32772c05b074c30204e8186119bba03f2f5204a80069b835340462c3778f68328821af26149ed3577ce69ff270d14367938e68b24f098cf63bf142846b915183125946f6f1bcd08cc51dbb81846161aefebdb45f3d1f771b94cf71c5171fbaadefbb602ca2326a2d0538f6756aa4cffdb02eab28291c228507c9f7f3f1732282ca23114bcab5572e92357c980dedfecef6506373cfb2ca663fbffa6faa3f1b87eee2be4a3220c7d5865c238c94f7a16fb717f6fd1404c6f9804db630413cc524c64b94413990d9bd090159f2eac3c5915761612b6745fa9bfeba08b1ae869bf9fc01b40c04682039a71903ea068c2b2273aeaa4bb10a8178f939e9034a6562faa6fd554ea04d77eb3a7040fcc3936e4b6743159c36ed2cbccf87b81f158dda9c2168c31e3d22de5a524bca67a9c76b3c252c592f950ee4a1ca70a0a3e09f5eb2b09bb1a9d505eca11b2afe9df4e4a7cc9f37be6355af728d141644f934b3d88b9d435647c458679596c0df934d4a40384ee5299e993ed5ce9fc58c1b0b40a5af5b67aa82c1b2aa22b400f1d45f4fabeb9ca69ea7de87dcf97212e3b1c5d839db621d5a7e561e5581d7421a3543aa701a284ccb122a93ae6958fac0d0562cb3e343c50f95e48ab4df41e59eaed745ec2a20c0e1ed50a640dc9377fcc5f6441327c6179f7ac279759bcab3a18ae36b4eae1c7b29ad5a44082a79ee1f8310acbe7e19ff29264f84cc64559d02f2057af620a0e6b29dbf105e02d5e9ee30d7b312b6638d5f4aed8e9f033bc108512726e4adaae1db811d3503125a55d60b0e58d035324e41a698a842a563d95c69767dc138c67c23d580d2e88efb443248bfac563468d8ed706f53df89e96a01712d7746ef06d0ca842c9b25e0f61dfe9c76e18beae51c2dfb3a2e114b05775f43bab18371beae4e31a102542249568b4f97ab9cc4c848ed0183f16d69b65fcda1f8df2ddd97ab58a7b55b48a25cf4ecc425c7f21a6eb95563d1e1ed72e75bbf1546d6bf4c5ad82fe25ebc1213aa0174f2fd59e4e748151e13bb6270bcf951b27a4fd2ed5aa88d6fe648ab998aa2d8d6ce5a977ba04f6228cde212e99bfa4ff826e36409821047b00a4ce791211b03cab952271702d2b3727c89d692363d5aaa4cad11dc5ba8e04d2e2b84cc118f744ce468ee680f3b7388a7f0c505ce830f7a654cff674c9862c57542b70fbd4e4743632bd23c23f65d29a8e73250ba711dce418f2eed6e7c0fb64c6702e28f6ebb9d8e683710407b401b1e97e1ce255a74458d74173489905c50868b28607e997de23523c320b1b553943fffb06c82b76368d1a889e730293efc3228365163b05996f9f430edd71b15304235a9677f1d6476342d5ad258c99918602566d117567e9ce9779303d116155dd82342c9602716617b0fd84cf69bcda7217024fd8fa82c85fcdc5925ff4b4ff6c77c13aa43fc9ed4fb61543e268954eb3d345b23366302672668e5a2a322c33f7cc603569fcb00d25cdf9e6792231cfed263fae3f40df3720ce434596620122ee941311bd08cd805fcfc80308a58f771d80f3d17cf3111732e9d32400c29c527f2b0055d10b1e9367b36585f0ef2646dd6dc9bcc94edaaf66af356c7dd91d72ce5ff1ba5b8c71789e47f8844a275c976dcc0f47c7f765ff7b32e2cb8583d6f6c718d55f442ae9f21bf76b2d373a1070fe4f733a69b96fa2902d56de83fcce92b5633d84b9c07f490a81eeaf80d2119808c18b9ab5b1cab3b83092e41e4f4076fe94e0dcca617f8e1ada744873c8a484bf6f4d7df4ffdcfadf5d9d924fd41962bd061773ee5e0736cb24345222020213e63a9773d2ba1d6a5375feeb91af7d8a160c611511dc8bfc7aca286c8b99d7129005a88137a49ded8ba14430c627424977e3e05714bd541d824491ad4961c5a09a9ea5a7d4467b89140b7fd7927d73a029cddb24dc17fcd8cb1171eaca63df50e5eb15faa9e76fc8a5bec49e5968ce8ce5ee3add4db7f34baaaa0f0839c52e2996a2654a07fc417ad0d8aca411b347284a580d09e3a5fd94de0177618984306cf2f4665fc9a24fc2d13bd595988a839c684f0698b128b24b0f47f73b64f453339665cabdd5f50b766a4d60e94cef5ee5ca07f9b10b0efd46272ca44bd6687fd9145faa0017e77bcbd35b712d03646563a009631719b59eb356e4d347fe8289354c4603c27bc2b8572b52b363a8645d3efad9e1110cbea87ab070a08f28b8c1a4f47c3413ba5c6f5df055d16d2367ba9741e8b4f4d3e403f89d936ade0a2e65e537318185d96c636b6cc0fe894729298d5160442d6f22eb00a3f1551012ca2bc158e693c2c58ce94a21a86edfe9d6060bdf439ae33ed36f3a2c7bc7f6f35dbb87b559f6b46be36a3e96d7db1a6c23e66e1819e42afe4af3bf6138eb3cf72f3359afc69a402389e48f1ea0011b4eca0ffaa68ff4c13ef1ae1938f5315ca3b9a1404093538b674d6a6f737021ad980afb137cb8c2638dbb2d833919cca0ca5b8c1e4a1e59d51fa39f213b7882593399d95c14d0e6a79f9cce81c12b323e20f36c63e8bfc9e1fa1e3f7ed7eaeb3f77360c901a261e732d4a46cba422a073ccac2d9a4e0b21ef9e2197be15a52b04918baa369460cef7578d1c7c7f9e94c0b036b1c2fd08c83fb0fd089c991292ca8334fe2902347b46c02b2b9e23a551294e6b4b7c5a6f8432f02788be1d317544dfe306e61ead4f6eaf5924ffe13f5e8b7fe54400eed601ebf6c5207be378adcc7a5df5ab2454992df28f0957a7a007027a7c59ecdb86518a493e6fe2eb421894c79db274c5c44a324a92b4be2716db5ac5843bdd56bd67f7ce42251fd4fe17de8be358b33ed178ea966d4e7692242c9a1ed70ed91d479b37e52c89246f2b0f4d197ab4e732eadee41737d8fb878d928f760aea894cd029579968f4ac768ee83e5012ca0033505ac16a60a5714cef687a7e025d480898b1e1c31a5488ed9891390231518f21876fb12686de5470e84dbe74f0e8f5d317b9ea0b24aa3a0d3f7a6785b932c8d99ec18837b9fd83695aa5a723287da794be26a612218cead8cb18248be30c57c4279b5d5a655295d67a164044b708209116c790490ea49faf6164988bfbb30044156cb95d21817d66e00b33b781e31af68cca2835c42ed177ea5c675825ad152463455907e802869e9d34c245c15f66a99845361e977e443e33816aa8f8b4b4720e5d7ec172a093f8c804cadc2b42ce752daea37303783b62d0587eae3736dd70d58dce71b1fbbc138732d673af44d6d93f63e65e7616f3a830e95e0d10d851823dd5c41ba50631f1c8394612a79144039d24f74cf0e671d2f64394ba4eef4a4791d5b13035896d7775d781a4a84a7cc18194da6d7589a6894af66ea2af05f949e4554caff1a0c5b5a2923d938f04a8782abbb96cfe36d6c95297141ffa5ad7e9acbdd174163a38a2d82ecf4e3447b45f0d868ae2e1345b13753f9c94eb61ce6e309db6618e9b964b1438924055101a938f4648457d63b309460d3bc4093e266ed027e73327dafab9677f5cd4914f79c6cc2dbc553ab4de54519f80a8564f49eb43b09738a1648ef79f95028cfc6a039f78d4d54decac0de0ff9da3ec87a056dee933a537c877a0fb4f52ec4eb5b8b500f8b89351e6e69fcfec2fde50d475840aaecd853a4da80c72fc51b967310764d88fb87be41a8e72c8ea81da8da1c4524e6c42c910ab5df198e972f5e15217932cee99a3650ae67e62d4462f895db88dd7597729b4f954b6ae3956dd39351d237f618249d444c4c8e00332ecec952f20e5bef62aee361a63aa9fe63290a141cac9c980fc5ec9129d88f77f3674f13db600376d4de134954538e7cd21d470205f9fad03557f9c4d20323969f57e06a72fdac02fcc57117be834f8f93395be4af33be55f3da483072dec18b28ebe2a69e22e22b1eef9d2d85920c91e566fb3c5bd8dfc4dcaacf36682cd2b1fb30d4031ad66870ee9cd8b8cc143e933ec42da88d580c467a3b5e5a2424dc9f83ac532e1d9d0d1844ca32acb4783e8f2f5aa6701bc178baffbfa738ac1bcf28c12205611d0aabaf5074cf79b4b16e85c43639790b71a19091ad1d600a3bc29ab36800b8a975acc67f8c48f59e99f0c33b9904709fe67e3bc71f5977849c7bb2ac1a629cc2a87368409b01e34d1615f7fa51d4636af2ed5f44aba987d942af2630531698999f8ac1be6c16db16d83e90a249c58539f9eb743fbced52def0a67b2bcecb328fb69d49d380569255aa779e49260e390f1d6821caea6a9b8ae4979f942d5944331b73ba07b15a23e294991df16c63893821dd357bdbface4c0687a1ce3cb2e297a1083605ab3647b7b9d6c1330cf05f322a954b0994d398caac5ba86335d105cd31d1e39a154f8af65eef45c5e9405bea18837484307061cd6c509a38225f3142c3c112ebe91bc8227aeb2d7d53221f4a670a422521c421732ce986453a6f5bb673e2566d192e8f031ba8169432d66095e7abaa78e921a921f2071bee99e2670305c50a94dded7c87d88df38a170c967f01cd5231a8e97b6b0f5d52be739498499387ee7282bf92e3239282f1b6f81eacfebb84527464e413777fd27f7a191ff912a74b19c18a6199bc6fc7f0303de62d1e3cca342a87a06236484b48bcfb91c27314abe76ed64d66fcd5918d70be9141751b5342becb1683089e1d7a9a136c8963dd468f9c0578369a79b3452f7c63acb977cd43a9a85e3e65de718c693852732bd622c4cafe69529d2c9cd94a8c27eaa69f5c35ec61c8467631306b1a2830f108d742e7cfb045270dc0822576f2c835e322926a081ff6c8b1e04b193327e8c8fc14fb20c41cb58a353ff362e01b85ca8c474853cc40e5052c7ef2efc07375aaaebf9a417178314a46b4f51e0a6d1135b926c4b4e52e9fca0075f17faa5646e17c6b311fec6aa4cf65a047dc90f772426f9c04a055dcccb4f63c0084ba3506e3d74efaca8f8e99636a5f70a507d7cff2f6e22346fe254318923a22397c81534f5bcb34a58766adee0cdbf0a6938cb8980929423d00d653278df818dbd09a84f1ceb8166066a8ae4662dafd62c83129b3b7ab6128b1ad7e846fc29ef30ac4081f73d1f9e55b667bb96e2c6877b789882abb045619a7b95006dbe86f5632c014265131634124d564c511a477db18b547d1bc388559849263d4ec6b5d245b49f3be0355e8d42a5edd19027b3dc6b9a4143825c5d955739b6f42812d4bbce34b8cd51f85eb5c7b2fb5abf25b50a5dc2e17afc3b5dae0b9d358f54dfb7679c74ed88b40969da8d01564b5d1f4a33316d42411790ec910132280ad00455969ea55db4862ad9cd4cd633dcf7f204c582446ba7ea800b10e8bc5e7920a10387d3530ecb0c2e5084d87676986f7003f4deff063104f37364a8b6ef807f35427f14bfdbfc63f1cf9b01fc06de8e686637da3e415821cde3aace8b6ad95988d549f2f127aa643218ce054f3b63f0a8bac934e14808ba4a736182306fd21de959b1a127feeb54705180903406ae351ec6290f8fa2235b257eed0e0b8acc59af7208f8529f0a6b40ef374b87909b8d96999a2e637bfcd09ef517fc7672bd2a0fc6a377d8740620c0b61dfe03bcd98ade9ca451e977dd21b6e88cb66290affa9cdc5c6ccd3611520f01814049fa6c4a32889194e20f815811af59c8c9fb3f8f1acd0f184eab1137fca454b9ecd42bea3ce3f1e581639a1e666a75a10dff1f6867561eefbcc4221f4b6eadefc77d434f20af168b129b4c276b8138f06e83946daeaaeee4fc0c110744ac45abdec0b23c3f5699b43a4eb0699c4edc8929b5f8ee2ab868c8f83be3fa6f2cfce480bbcdb9c3d9d3224c24522b5c7c03e3f49a39f11e2d79b009904c48b0e952478acebac81b7a65da54dbb4da3a6f3018eb5258cdd4cd92780eff398119d3ee3ee15bf76ce6c4b0e8e127c9b96fae4c861825ec1ec6f4284d469f474e3c511b9b9701d1010b085e38a66b7a58cc7123f335a29c02423e354840b74911c69b46be4e2feee0f4a6d775771525dfb45be5454f93a8a42c72b51d72bcc557a384815ec124c4582db4cd0353991929e7eca5cf5895611fb2b72ad84728781d0e1903d694a21ff3cd7eb1ff5c531baf1a6fdd23f5d9ada9060019a5be73cede315ca603d4d89f0b85783b560b55e341c3eae880edac84158e5e8bb1dcb96a7b4f6a68bbb56837b626f6e6e7ee0fa094803b87c81e0c759e04b68d169879a4e88a86eac690a1b0f503d271bdcc0d7097047e5d08ddd5220883bbb0d5feaf06b4ccf0f4751f7b244282e45f4cf969e9a17ae173920fd0bf0c78d189e5b10bce5561e4efbe7431835fad9c78cb278c936db95db5a58710971bf1a5324e054b705340cb69ef44d11184bdbf46971fb96ce0c9ee72a6531d8767ccd0c85557ee1132258595ec6375e6cecb7b99a6096a62a78cbde0e610728145aee7ebbc70480d39e49cc8039b706d0e2f87030f48ffdca0880c95ff7df50bdbd39e410b009f7104a569fd4a3821e5d63afb5d99095032172cd70fb8cf50bd1c1d97f2eb817d81e7ec645291f9b5e3e85af640660bcc30cb0ad12e7f8a08c71ce86a2d9a2895341fa37189df34becee7c4d68da23a6197cee2d9b473e2ac68a99764a25d9484b3d4786b888860f962603a0ce64b281b9c2e720accfce375783bdf0a07176faadeef5e19d9c95c7c2e1f95143205817e686e673580f2e625df342e5894dc3cb6c6b9f3882bfc20ed16d717341dfbfaf696d1f61642560e1b1b6fd40386a6f302344f12eccf53ae47d117bddf95d86b4ab776de74649bd60ebf19babc9f0b2ce721e3ed4c3c316a596b94fe9c4ecbf8e7d141e31f9f7ff420eee8c496a40dfd0e3cd162d08b0142e2c5b9ce4f3f989216dc96101f90730671e4934435def5d4a3678067c9fbae3a151095be8c23c02f7a44e4444c3693dab86ea3006e9a41e17e3a15c7936ea14d04560a8a8af9b376236afdd450762e4b0f6c90c139a99ed7e27ff48f07eb4eb55df15a84bb8519ca612345504097e1f33956326fc8e1f73142b415e0591a5c0bc312fc9eeccd1fe9db37a8af06c73d41bab47d1e7899912f678c25f96acb4cdaec3bbd04059ce39507e367797e94019d10a592a83f31362deb6e78c6d46fc484bfc53e832602e4f3f458b4a34a73e1b886591ba5d68872264bf5cc3e889d56a4705bb83cfcd18690863917407f3ce0393097fae18584e2710f6ede4b6b8fe741de7d0f5c7a22ad479b21ae39bbab3746b7384ab2148e8d0a6d5dc72134a29c8943fe3dc52ecf5776631b35b094e1d0b7f8aaa9aeb81353f87ef83330b2da5cdc888708fc87f65b2d1728e1b920e24cf5696ddcc0460467a9b6aa6417d05be160871205bb247c275687bd0a96ee159db3c099b8942557de4e6a92004bddf15576ba6ff1f4538795fb8f42a89a05c72b225b5765c8aa834b8e7801b84c198989aa03c2593a6a03d3284694986654a894437563e9759d086b1ad100b7a80db6ae1558038fbd761f5310d883bd22874ced744e9109f091e5df0452c7e71dff2dac3d687ded6e2fcdd36c7a55e6db69ab9d65004577ccb32000dbd147059ca149fde2954f73eba045724e6a418a3fd3a1c801eff94e955c42b1df5ea27d0146872259d5eea6374c82cfc5fef55245936f441167bbe6bb86d854f4b3ad24c3eb0ba24c6aa1cd80e1c3441f0211778f0838238d37aceca6c5f3c402a475b6e7fb1a9818ca04725eb40c28a00eaae48bce85cbedcf82d5f5eca960fac4b72901f69ed70d50863a4e5ba320f876c83ba06dc2a0f3b3079b53fa9228b2f366d20d03a59a4ddaea0f64a9938bd856e898ea15be3bace397ca72264beb416228546b35e2c219d8ca9368562cf063c47a2f61342f33b62ca6e30c3bf681a4c2da6445382baabccc0fe7df36818467bf64b45a212c7405383806ff6cb81cb5c7085f2a54f9826acc7ac0b2dae8c190677d98c93eb12aaa8d67f1dd023a9fd9a675a4aff9eb48be021fd373c67e0911371313b49b1e3a5cb6e1519b01290dec7c99d04f764341b4e4e4847b40a158617b66fcb299b845cd18686016f2b70635791ebbec12670b444a07f553d8740d180a2a7881202749032f50d0b19317558979634a9f9e6141fc4d95522f645ebb023c0970e7a2f6c03d36040e49f90487dc52f915bf5360a8417bf482622d8b0ff68c1fde88971763278a81cd1e1f3b9dda5928d94b1835f113865a181e6a69c505db7ce7630f5c675672d8e89e81fcc65adf6bb52256d7087a815398dfef2dab1624a6c607c3ff5f5d47b18e0d9e8d2ebd3cf044a90adff8f74b3f12553f4cb6fccc1486ce461ba2ee7ac3c07d8fb7d585bc197dd8380d58b0b3d105ada7022897f1b48aa315303ed43c5d22a92b4782c623fd60d19bc14a13a691998c44cec19cf965ee20c9a388be2ce016831c8eba29e1e783655382133d995b212607f4090ab27eaa0d253b322bc74330a174e2d1b0eed65b3d477b33663fdef429d0f99f637745928693a6f0e6018eedd2251b057743fc638628f91a077f1e968bdd2ba13e91791d5c4ef2b6ebda5fc9829216a70b4b400f534dee69919cf3275574171dc1bbc9026fb9147e9d726898ba2b365d3538bc8a161c71c0e07c883403bc119a0c005ba21561649da7a8ee37e5aede6b30313f50e6603c1fc5e9dcc32236afa9c4684640d4a61fb3fc184c8ca71ff812ba43cb886bb12dd848cc44c22b1a4e8d23d4180ff265e41cbbc46ecf6a0b71fa7ac7183faef7c50e9462e7b6d1228cea4e21500ab5c03800f8a8572f72fdbaaea25510622457acc88f9141ca4407cc8fde3ae5a04ed602e3b2039bbc954b492dc6c45197aee22b8eaa94a11c1df0b548c70cbd2fc3580fd4730de5a8fc8c168153a8aedd6c05b09a6078aa6ceab4b3c21b48374c56d98f23567c73d5701d1e6f2cabaeff94134e9d69fed60da294758155f3a2de452996ab813b95b12509e4323abd0bd6b4333a09aec4ee2e65686de3172ac30cde5b9ca8546031690c9eeb4a0c74355a0ab44ff665ca9370590553369f1b53f42fcefd21c08c42630fa93d55d42ecd88dbea8b9103b9b9cb8d9fa3db4d4b7f35f5177ae2989c5410cf16e7fb50862a14f8f3901dcba826c825851a7d0953664d6af0b700dfb10a09ba487af17951d7e6cef824833a73f80245763c515aeb9fc029e1bbf73d72997c9af08cfdfe0f1376c133b171a2071c057533d16f7ec9b0155218734f478e4aeff2ad3519ecdb4fe49e8ac50f93a0d8697f996436e3f7989f80ca2ed52979fb48c4f9939b041cb4f3706f933bfe2a4155f6c6c3bcb6a946186b5c2264e78d9f3397264676a7109bc804b2715b31aac63ae5548259832e1f0b22fbe63d4bbdb9c9e25bf4814efd127cd90f58be08e6a3f363a1783607977c0f4661f6f22246e5942a7dc6dafa205e7e5e9b688cfe3a6f2506883ff5b9d029d3b94633db7be750dc6ddee94c20e873c9ab866147f7f58ff860a9b863b279e8f49a5f601ee7723b43c2800fd1e1c27ee31d792adbed3fc01e21908540de72ecae8bdea64c06a37cb73353eb18b1d25cb17f5a4d3ee13f91296b978e0fbe425e5197251087e2a0fcaf439db92ba3322019ac6979a2a5c3e63080737dfe16afdc8c3bb01038d0b03abba149b3120cf69174d187ff347b7277c9e0a241d94c90f58c6048f85be115c65df7409d953319a9d3c0dc566f8c464e4b68db8f0e56c5aee2494782592859c740e1653a073588170ff35b98f7b5cc4d681abc5cc47b5b003fcafa496a4dc876b06f0cd4215c10beaa2d3d02113a74a6129da964fc577157fd43464e93382da16ac8f8b8f390883488f12e789c410a61b398c0a7c8c318147e403e742dd27629bdc309b7dba4bd44691de4fea84c57d9b07836e68fa77f0c51a8cc03fe1b4984f49eb5a79592b76c426ea14094f4b63f4546562a62dff30830e66229df24313b1a1e68e05603bd605dc79a0bbba16c16a597843e5da5ea43c97e5e74f898876d03b7a2c0faaa12854b6c43d2b9166ab575cd5a30213410ef59e70b5987f0e24baf9f78273f4dd477a8962f2a3df336c89318f7d4549388b018a522b1377f589af5d9a1fc107e6d83ac11c94e6290aacf290214413ac0f61f70ff2c360ce6b1348596e9a6e86e282d8fc5e39ba7d8339e1670575253a4934809cf8c0bd7a92a8732892d93365b01acb95c32156dfd7cdca35f8e25168c9a2ca633b63142f03f1496087db4df656d69c96cb6e1042c53848cd037a0b10f9540cc3ac81368a59af1fa1228ffc9f90b8462cd8ac953a4dc66539ad6e7e19f9ced3ec1e2fbfb8b22489d92b7ec78ff494fd206d2bb01d351a7e925ffe7d4287c0e4a19789a02fad6d6387bd9f9b979d6d08102264a3a51a03ca545fbcf1e7db875dc3f60841a44206ce79a4426615b68433867a12bfdeb987487a52b91be7aea6572aaa2b15419eb724f38797a15a8bd962d4b7ebafe148c6bd38d0366321c8219d0f7c2fe9c51055f22d80c7c2d32ececec255173185f8298f4f198479ce7fd3c8c0ddcaab2f0e142ebc3e0ba9b87141b072c1a7949e89040b5054874f66dde6f6a6cfcd9b05f203c2d2c3c46ebdf472e957ec21247d32300c5d2d6f157cc21c785c50515271ff104d2ff43334c1dfae38ad81d289d327b478f88da044961df4d4b1ac9c8c173b861e8d2b31db032a92dd9a334ca5e0701d96180ddb92728b07c44f137c282cf1db3b26e0c9ef6807bb360a06da96635914de32fea5e9c6cecc5b1694ab989f90c6b05e5a5da29fd764fb486299b278f4690d983ac071ec86b0ef537a9c46c360c8ff54034f4ca319b91ab50861c0d5be260405d500b879b602fccd441e020f3b037d72e57962e6d983ac5114bc86bf46dd2af2aa185b9761c745b69aa6b72b4ea128c35be6ee9ae16c1598970e2016a48cd12d8b3d45320071e0842a2ac0d4d52e2215b44ab4c267fd19dd855d1073c6c8dad8e911b752fd6e222d20a49179ca9fbc04e2784f25303c97406749dd09b5b2174ffeec291691f6862926cce54563ec8ea7127b15a202d3b133b4e76facecbecb031f9f35756b4cd2d447a2de59bf047085beff096799b6d71dd6f2e2f98da27fb0fa61da4bbd121b7630c39895c348848ab9a81074aafdb8dfc84ba74c7525e7d2dc47585f311eae5d2bd2a7ccc8231cf5195ab27dad4b37faefcb40d5447ef8b7d3a70524e3da01107aa1cdc6b57fd329655356aefc557594f30dce9a1168e8bf7854bcdf2872f8c43cc8fa26202384c84a4ed253a99dfdac9bd92a6d4b9c42761888358ee8878fcaa26e82c3350455393412592e4b394f4539e5e1f521e6bd9b8285d3b673acf883127ef540545184fa2af30d354ba76194986698ed62934bdfa1a5ff6229f7ed1b9b750190cf0bdd3a3e2ea16746db7610c46013dbcf321ba3962d5a7d23d41bbe6051ad6c7a9cf1093562ec10cfa3e7d1c3719c458b00bf846c54d80d77ec9096c56420d27e601308cd15da811a02576c33bc6dc91c51da6617b1b47711d39cae72621bfc721990322b4dd61e0bf3c430c7bc3c00b825711a9cbd6ee8849ac8d8e78f252db57f0044b44989ed04b842ae8cdee1a93fa0c0b4b37d93989cbad73345d550f6fdcf3a27fd07997a254b4f3adc5c987a4a3649fa80bc36ef777bc047dc7cf221b6da5e0e3e1f3899ee7b6ea5c4ffaa53357db1bebe6f71cbe84d0dc3b2e483c5ac89b4396f5794477bd2bb24d3acbb61399c065c3ce1df15bded02550ca8bb30b227e127e5e07c34149e3e53a54aacea316b7a04df69f99686f19e28415c941e2598de3320298db138f678dcb5faa95a090372553352dbb2169fb0640b8164ac1ee187bd79cadfa1bca6f5e09e601983d980dc764803af5f1ff3c670a43ebb14a4806fa0119ccef43526ccf6cc68826e8cad76b87667cf623bb936d3ae8a2491f16505c0b52ffc9b061fff968743ec755828af16cb66fa1336b2ea74be9fec493328347604851c4b152804da104c535c040ed6473f2d3406f162d1ead92a043dcac4229ad685542f0ae78113387e3f3707b1c88e4272accdaaf248410709269f05cc2a5ff079fdcf2f027b8bcaf036afec705a92f133b3c87389d2c84dac107aaa94d5637a0487d4529ff000d77b297dcdaeec7173892efcfff94d7e1207b54e0f7c0d75fa96e8cd8d026eba33522542338ed0d10bafd1e3013f94b5c52f5b3818f0c29ce25b12c69f2f8776fb1aa1eacf16f93404e1062a29231c23522c710eea1b8591b18b887047d833d6ebc437fe94fb276ed95a243bc8a961ee9726dc413835d885623aa2af8010ac9214d0df719cd4a423a8d4ee6f729f2fb4fd6c80f111529310887b672c15883d1fa6cc880b85ca0a3837ff3b8dda208868fb79b235cdc5dcce0818b09cea3193a29f45ca14c936e71c91f353b00d10b24d6f210b9b330cfbc2ce7e0711ff9aae479cd5108fce44402e3f8f9d1fbc225f33356303a5a85698e675ceda3238249542c6857c07fdb06d37766edabec9ff21018e66e7a5dd1a8848845f0b9543ebd1fea10c0ef5ed66c7df6acfeb50cfb02b63cc71dffecb61d79e2dfaaac71b401344a26012991a281fe0394df9f6b8c5a200c5ff5d70027f009b16cdf1f62c6c2e68980354beaf8254d1d1c89f0f271f34dc8929207d3e45818ad95da3e178ce4d865ef99f2f1bc8b406585b52a28903a87ff0cf57bed7a24974267e6a8fb91fea83c64117908a8bbc6d8e264015f668fb917180197cee935a94155d66dc417d975e539a286fbe6116b63e2589c4daec6a1faebff466696011025c07e1ab5f4f430e40ef6316c12bc2c8e4798ec346ba9c62c441bafedc412ea475a37d5a89603c1e5d1dbb0a8d440a5c2d3f0100fa17343d9b2e45fdb3a85c1a49d5f8a72296cf4adf3c498db81f57e5cfc6eca75c856629c6cd01ff6788ceaf73bd7286654c843e12c46afb2e1853b03dce6003338f7061738b543c85cec315f1483ee1c24686454e99c4adf6b724a06426f57ad29ede894c35197f5de80dbd0c2cec4c6a6ceaacb35244212438d6fb59fe4d0a2b1459e4fe2c4d53512b4f0034f23ad9a792f3d5400e06e554c8079efd86584e3020176181a11b2f57c0adfa3d9753d3806f815a196f6bbe4da464861aad361fe9494227379a06fe0659966e967b82c0c7746d6159131c7e88aad232f6d5706125567346be5bf0ad4b189d8f21665ef097a6c6869c47d11b50fdb61027ddba033db5864aeed1cf1018098a57511fa634cc171720a676ebf6becc65db56a8fe61dbb36aeb5bc4d4a3ec7983ca39b788f6fd18f98af52bae92848bbbd177366171a6b463b07e948d0bfd5ee385b6ae369b515b68fbd2dba7d1751d8098566961255e8c11cdbc3eb37d2ba15e10444c6ba476206006d53eead11ec9192474b1c7b123453d6ac4075293d5019f5a99aac0529055837da8476f2aec02ea7018a36ec6f019eb883b80bc961cf1dcd2c68a886a646b6ab68c89999e10b4c1c9760e73daa6fb50bfc9a34cf509cd5c7b316ce3e5dc2df4d246042e88505582251ad76d91679a1095b965e315b370c09a4a8b7102452c4ab0a6b7eca840f176c36e1ef1d43d3e0a837df575adf132088c5a401ed6c5c0ee36598ff136c5721a5d10da0d4eb58e1368207d4f25469816e6a84cd0eef379563a34f9c509e0d6138e08f9db5beba89c2f443ed39d9f135c6b3824048acfeea8150d2cf247cd79766a3cd3efc1720b15636169fedbdef57784db5025fa39aced92629353e29acd6accddc9c0b2e95a5043b1149c3dcc1939b3b48c278c10eb980522012d4cedd067b46a917235da0a6beaad42f70b1c4ac12938faf97e4fe54085c32cab77dab633a3e63d222851c6902596bfa417c3cf77a376300270142ff50600469f5115afca34c903560ab82975317f03647ef8add6af8e16e97bcd94c2c80011c04ba7c356793186b8a52214b866cbbd30728015d00a84ab50b0f0382cfb1b6b68603c845201f0ee29e3a5c97d9fa63a5cdf73487bf04131715be331b165ecb2c9c8f84b99e00bacded7af3ffe19e8ae47cbcde02c190c5d87fc2c0469473d619c0d078ef7bd8afa52e287dfb4391c2adfc4beb8730b078c32feb996579d9da3b3b74eb9c01cf45a4a4f8b07a3e15e57e2fb49b631ba9e2a30c4fff90426ce9f4235932476032d4485398e15a057ec7be7bd142f98e5e27cbfe5f55113dee41caee361ccb35358dd6429f03f1eb949933e4fd6e3e07e17d8ffbb044e6d8954fe78e5565d9638792d5d45bee875faa298ab05a143b2d2eb5c921c12243cb23099926f9517336c32b8f28e316d9b1dbead2a5769a8fe5b97055e65d390d01ab8fd347637b72dae8a5938f19e298b357e101cf5286ebae9aae94506ee25f617462abc48bd6022462713612a5cee81eac876ed507d8945073caed4639960960a821da0ff3fd21a548890d04fc23e1d1655755e98d19e4d873b948eb78fa0c60a8a43f2d30d22314e80b74a8018812d98dde563a698ac8eb0bad9107cdb81fe6411c5c321c973102b7310ae7e2d8f949cd32f7ff3cca436128a33b8a5e632d9b2514d756bdcdc5e13f7d331025ed8ced41221d1287e2ea3316acba12c6f6d51e4c6afb94532d2f5db41395b11868b3ba4db99711069667bf64d7088e8798e9a932be34da284813fd24ab9b7a22a5cb20eb9393208c6af74e61ca21e3b409ef80196d97c7069be91e9760f5308cd8242250f8272b40f8ebfb05fc86af89e5a336aeeb570274007c3b731516b5cfe8843eeae7c9bf8fa72adc7573be13122c73becb3051141bc36c7d05a9b5168e2fa32dfd2ad777532dae8060afd18f2ec9024f3af081dcfcd4aca70a804cb9a445ad696eda11f0e7c203c4e60dcbef86c0a51499978b8f5003044c83519c88123ac38987121a513d4ecbe8daa53f483e67e9b3db5a334833599051471d273d9f177a6cb948fa671f6c3c087603c1027a9c0b9c0521e52789453fcb1386b5b076695a32aa359f4e3895a0d0dca36bfa0d427e290dcd2e07b282541e0d86d0422a836008186457a48f64d4930d5c59a8df4e6998103240b6f330e978ef99474dcaac7cc25a3c5756885cc9a511112d60d2b426c746fd72cdbb0e6798670cafa809f814468ed337f89e65070b8c488b9c4efcc5c85503be0d1b3c10e04d00dd409d6f7fc4afdc74ac6aa82577ff219e5bb6e78747f9a942511c8176bfbd356db949868bc537d9ed9ca252529b653a928b58a963b7229abf0bd9a00069434b3f225127f801fb0d67032a4dcfc4f1d41787b5368458fd811d5b6ddd6cd89969f4bb328efb0d5cd009c264e773cb1865e5a1260cc8098071e5a72d734729b3dd75ccee17bcd31777e08b56022650383f32c53a4443eb7647c5d5bf7b773d05226d834094d8dd36995c9d4e54664b77c63d1c5a3ebad02eb81004fa5bb2b3543dcaee7f39df71ed4cd10422f4c5ebb9e7c7d0823029842cb23cdea899042e782a40ff1d42d77a74c7f87a367968c4c21295d730e83dd89948627c9fb84de8cd31c59cec66b74325166bcbdfa38a4bdb2bf0ccc2aedb8a52d6c1d3b288c19e5dba16cba4dded0162a82ccdd1aff7a89d2124e1edbd97f7babf3ab7822066c4e01d31de5c3fd92f15091feba82891d4b9eb04d512f13e9aca14d567329b99092b78d9bee0be50b7d1758b3ccb56cf04d0b8640686684d61f4573cb572918932ad3f163f1e6319ecddf89d2d2421396dbafa9e8c1ca477d0d6d93bae7f30bb3d0cb2b27e178e7356b34ae4b77c8eed4ce0d10e65903222f3dd6c46e1f3c384da06c5bc5f0df58e4dbd06d62a72e71d4d6c1196af8d73ea1e41aff32c9381922f2359090bd4520d6260ae4a844dd88f2f80b71bfb7a121d8606cd7b206f6864f1dee0ae8483898a98948cfc583dd81bcdbf3d05a24ed03e9af6663eec060fcdc85b8c8750f69c12749ccb0d4db4893164cd97c33603eb833ea93e3ce2444e7509c24eb426c8eba5d0a2f5801ceebd5137910e45007df157ca16639c867bd158b8d3bf17b8c650b78d49a8be33dd43907b66688086f21ad8db56dc2930c4b545e269f1d7820663b208737a13d802b1199787f7ee2e2a495afa7640cf38618d6042d6cddfb60ddf7267d0f22ee0f3e0aaaf53390be91eb5e208932e23be054ea03863795f2ac75fc7b2fef4a5fc8bfe138947aad9eadde5ab889968d9f124eb51106c7e4a5e2ff2f7c03c3a6f901188bbb3c3535d7daff9a5ab65274bfaaa9f9496c740d792edba39f003426819cce51b0bfa6120d9dc79f1361950f962c134da007f5201ec4ad93ab10da6d028cfd840849cc1ae12a5e96635e064031d81e56d230bc907a639ef9ce18fe4ccebfb3d4d4c275e1a1733010f7a8866b10fb4d9e8f7919fec595e3a72f6b982e4437b5ff736e01a78b8a60dabf2965fec380894b973de87f8514df6c6f6bce0846f3255077de50962ca823f2ccac4fcc25d561aa6ae0c4935ee6dd41446e8377b42f4c2dccae5b5744e12c9385b0e6ae54ade10455924f0f21836b2cc3ea5027eee7099e69e5b8cd0b0968eabf2bda62da59a7b82831ba444cbaf5ad39c5b7da65b265d2cf865979b1451d213621ca6124a0e5b1db3fbe380a17a0c02b88806ee10d3dcc75498bf7eb0b8b63362b1dd32402de4294a2353c37fbbb1a68e93e3b6ac2d2f65005cbf263b70a980c53d55d13743a58f3dddfc6a46ac33fe9f275743ede245c7b2bccac467ccc9a77ad9e7549bf983891bc9259a96bd17c19620053c491c62437ebaaf8d72fd25055fdf28c52fdf222762cd56b9a67ca095c350572e18113d420e7bdc3bbd2d3a2de1904a524513e7cd258e4af326af009967acf50a4b64c5c335f4498a7b277566739b77ce77f688b307a2cbb63557cbdccea36ba2b450e8d6cde9c2bc6f53a62bcee94b2fff02d5647cff5e366491afe33ab6c32fe2e5fb22e4586016b72221a9026b7c0e902e043f625ccbeaca7ebb279d8739ce83d7056139aa5d60a63512445db9482acc77ddf9e7792b0d2eadd595210322fe64a148de0f469869b6d1fa1218cb04e54281a419c3963e1e32afd80c8954d023f50137fe496327818078875edd16e4c5015cb37bb6811760a0d6c0e253c28d07600d2cb4d82b7afecab1fe271ad632f5b4e37fce6806205bbb42a860bcb2b154365b0c0a7a9053b7a55a559e204f55c1574eb7168baea359a3b4e1418165861aa610439fc765cc8c960b8eac1a51381e75d10c2a4f11baeacadb5e37802cee4f43c9632495a43ddcd5676969cc4b591ff87e52372e2b76607b0cb366202dac4b60a018de9ee7950d9b6795a626ad2f818cbb753bff77b55369926fe78cf4ca48f6dce9278880260779ef7964a96e8c208ff0028b5dc9c61f59114b47bf68dae81d02dbed29be65b9ca46795b96fc777f8e5be64d5f136661d3a581055ae0a5ae1746cf32313e7d932f3ffb74f3e031455b1d3aff9ba9377ee9b71f17536b9283e4b1258a0d64bc0b181fe24f5ddc38baa8ea13ee82a6021c0ab275666e960c336e6b03a02f4f301862a118bea956ad4e3d006fc0017767a7e50dc74d88334cee3fec50b197eab3ba059cc0485a51f313b33c81eb7ac4b4cd6269631eb0f03bc7ce26850fb4efc370d11f2aa7de45343ec881043b0c37ca02a395bc9d12c8813852dcf79f8ff643c03623234aa79944aa3fbf3f5025caadfd16d2b883b4927c9c2ca59a5c50ed2ab2260a60d407927b545149e51d6ead9539a3a574fcfc478ab4e36da6d20591be55fe9cb59be9f13fe0aa5c2e6bfdebad75d407e42910b41123a96a01a3c7c19e3983409d2a4eab661300f2f554df53ce3524cc469ff9ae99f272c3df0624cc62f4ef8004bfa14e04572b43f7c8eb57aa3f4d8130e7a61eaf0da47a6743de01f49e1042b9cedd21fd3adea3be0bede5c11eb036f8fcb14d7ac34450ee38fd3b22546f26dc7c7b0973efe18f4d35166b7fa2c7d9c0d0fb60ab4acba3bed7292028b890ffa22436ad2bcbe710edb0c9807c5dad1defc616f6d43874962ea55d43f66f40595dd69d822cc9e9dd2bbeaffbdb28ae1ece398134630b2bf03a3de18325a2ed711dd379a715071bd68ff11c57059cee3358f4afdb1ca30289c5cb9a34627e1732a012fbb5374768b914b1c8b6d29a1fa195b9e81783c23d390d92acf3fab4c9f40ebfbefcfb6f88402cbad3f86fc4421ac7bbec277493e791f3666346fff92833feb6a0c4f9099df6959b1e92be017f4b9e23731d2c20fcdf2a33755403870f659c0c5e8efec462760f37f02e0ffd04d71a8e73421edb203c75748f5c2f9ab9a85c87077262f1f1ec8f48ec5167a6037b3e8d40ed2816eda8b3bc35bcbaf581038921440a3d060f047cd7596b56b0ac51af2372cd548ac281a231f0a4535bae4d10896395721e0b9601c5b9229312495d8b4ba0fba7d435bcc60bcad83d154c1b94a140361c7834cd9446adcc36875dd22470ed5594f306be3d1a200824721c343301f47e81f4d6905a612c47720edfd11af360f32fcca9c9d73ebfaab042916ccd7356afb499cc6126710cb6046b778546a911b8b5a65e4323ec526e045bd9f185755351e6129e0894a7d6d991a3d9cf405a2054c3afda50d5b023940915fd5a19ba0b1db02b33b7af85eefa32842603d9438237697580954aa525d874cd0a1135a9c74eea591380916408a3421b614fb1959b44686d280db1ff56bbfd6553e05ddcb9d8193036454befef9e703bf3da59f4a94be86f7947ca816e4bf9c2b6c41ba3911a29ea63808aa69e02a1fd41b7aff8fe988937d1851b07dac10b4d0043117fdd08e8e72587595ca229f63a5223f9af0a20f3e6bc7dcffe0c94167353e15ca73deec327472c7ebad3edaa137dce3419360de68001c283e71ecfe125a8902386663042d2e4e00dba5c8e0f6a340d56ab8dc86e6c3414bad8b9d8405957196b3af3c20cb00eb7f12c0f011166d731b4cd3bb302f7e320acfa58ef9b87b7dc7b170832cab2f7e73e340ba82ff3967bc75cd32939d14f68ce0cd703d92cc97dd922c56e8e67b6bf7acab5a5af406a4d6a286a95558eb15565de4cc6974af342829788c76c8795101b38ff481a3f139f39778395072615c72dd73f0096e6cf428d6401b6966cf48fd6a0f94bdd2833da5b81ca8c9d75c18740702096da8908ae6fab116d7e94b7bea274bfdef1f66e95e9f19c1a65bc307f06b718caf6c042c829448150a721493f8ffca9b8fae8a3df6f6a654b1c1e999649cd2a9a469d1c625c3880c0295929bb08d4d7a935022c0d8800c689252405972c689f539a3339e6cf47f9e272260fba6f6a0367f28932394dbb929aa73afc811a52c4b51c1d5d4b4663b4547496980c5a84a989fd8b18ed8b189c2de22fbb268ce8fa3d9af79a7d893349ec4a5c8bac47b97ffe9355e99fe48749cdc6b9fcf5d89b167787e48f108891e1f85e4da5340ef33a5c00fadeffbdee033755ef515626730b664507b04cfa0ccca623e4fc51c0f3a5dcf8d3f637d024706651cd0afbee87ec6d2c0aa3e97967de73e6bff1d060cd07f231fa95a3467779dda8b3aeff89863a45d79ab0072f18a8dd9220ad274367dab25ecd3da2f1272cf343e604ca8f3182d600e83c3a35ca74b3e96e32a095ffd0950571507dc6e85f330f0eadf4f8de4a26092955b5a3db1eb66eb1ff8333a22e38254de591f7643219c0b647c459f75498de174cf0b3dfb16f3a193eeb87b66c28a28bad3043b751ae37d02ee9aa79282e9a6f664fda1bdd6cf1778211fc7131efdb9ae8e5b3f116d96c2ebe8fa676f398967197a6c40b36425dc11d52265f96d094ae681bf1987063770a2c01df93b8382b634da71adc4a078793fd2db1e0beff8c3ea7466b239d04ac7ff1565a8acd627d0e4e8b8cf28431bea91cfdbba7be3ecf86e9142380b5f7b80dc302ada9a9890b3fa38e3464dbf8a967832d493cd024b2037547878e07e95919192d43520420af48970a907b55b05e51d831287bdb84fef9d331e9a44044896a482cc7794351d37cc1435d375dbc73938081ab134cae80f0290c5409682d0cae2a9659293ae987b6223d7bcae1104279504c650838828733988b2779a9aed5f356ae9dff3605e9d35122811a4a61c6f8cbc31732d6967950b272b2a6efe4042eea3273ad62680d33b81a2343376c1d24f71b00bc44e3b59a020c2eb54e3531b7dfc994796e8d4e3cde97a213170d34b8b6a5a5a59fad6aaa9ec0e7bd3ed54ecbf99038bb92cf46d7c770788d86fd0f91c0a4604267b5eadacb3c1a4ca62fb5093a923e62dd4453f1605c1f67847009331cc3deba36d44dc9b3d4f3e1b4f0b7f6e91e6f70d023c0fcf99c658b73f1684233440589ca8ed7da281b8c738dc2d2793353c97a50ce076bd4eef499ed30200a7259b3fe5650b3b47adc16e8c0049f5f4944e829e3dbb539bf0bd06465a9bff9773a11bc2e4a13c35aef769f9808aad81f9b13bb3dbd9063c3555d7199455e1fd8e24a3db931a4b6d08d6fe7309cc9f8b49da68223d3a569e07a367bd672e3b56997cf4ab95695ff3e28178563eebb37e9e816649412b5d1d8235df10949079328957184e3cb295677e4da328215b7f1e75c0a594b9042a40e00a576a4b267b8a58b0bdfc49be436df3a561be1c20be71ffe99f18c8d45ef6fc01b614cbe44c507c5a321f943e75fa4637e6031ed25bdcbcc15cb45e0054473d3f73b6084d1829b1d5f68b1f0065201862595ab7b057a0ad55b1f79d0393624f41e43494a8a58f42cf8a18a3272d891dba5430a3cc00b88f91a35ffc4dfe03fdea5ed9dcdabd69c42d911b4287e7cb739996854adbdbfb53a28ec9359a05f61b9c25e14e1ba2d57186ad06559570c0eb3ddf3776924cc927c633435b8be533e0d8cf244c506b4ed5b9c49a7b2df1cf042cc49b43760c9040d8794d14fbe63f35086b73ce73364937244bc656df8820bb572c3d411bf32c06ca3a54642eeb6606772efdfee275cd41756bddaccd3db053bd62d3ba6e2e8c01cf794af23fc7a969a07921c47235e16baaeb242f4af053162c049ba202bae527248a403e5886051e843c9ec896f50a9b6b158e34c89a34f5b5c4a40444fada9b3e5aa04047c1e87b1c574297acdbd67d79c3e6236e89d69c6f65a511f0df00afff58f7d5a7aec021f32a62c75b852e7b325486bfbea9db074861452e53de984bed3d2d026770812cd03234486a86139d8a22da744129274d2400aa2b69eea37aabf03d727cf3240903de9f385b7613bd3bdfa0184233128eb604086614fad2e9b59490de6be30246565a408ef706b778e6d8e4112aa6f753974afe3655fcdc6b0f2dd13844e4b3cdddb387a531fbf9491c21cecf83c0862b5f4c3c9c5c04ba98afa8d7cb0fed0da9a1c3b1e9b9d3d5813e1ab7ea2b4fb83d2b3decc6c9f0a53997f3ae9929b9122aeb4772a768ded34fa5e99f3eae80c46609ed330386b522dc7f85a7ace9b6113e5c36d87add7776c2e1e14994a65413c7ead6be3e9d95136fc54778df35a9ca660aecdf23a86acbfdfa5a03715be6688166de34690c595393e9badcbfa4f9cfb003c00f2525bfbaea0ef948807d7894e905c6ccc2c1393986f3c3088a31945947ac39e94ca03fc4a79979cfc9c787e39ac92aa5ea3bc24a3ee95c46ecfdb7aa12627f8068366e994536038d2e7065c0fbd79369c3cb00280970a438235f2a55fe0598ff4c45d206f63083c7ce509fd55ae2d1ce2447750a5ea851b6b066e338381da9abab51aa31d6c7b4e58897c82078b015d26927c0fe15933cd155c2d16b5cf14856a508e9756058b6cb5f784cd5676f66ea2fa5aaaf1fdb16fa18e9c411fedde6c9c8c76c8e24177632aab4d0684487ab84d06d36bae36132b9ec85a30099e5aa4119d1924ce59bc593c7e3417c14198162865e603c0f780be9b740c5d84f5593327704c3c283acc4355d369302685e25a54f41e06e9c37efa075e9fcc283b075318dc1cc6541c956f0a556743b64dbfc8025c366cde9856445d9f2580e11bf8f310215b1dec86f8fcb2085b88730f71982b902a12b04d6916a1b73503c977043610ab49bf2b5aa05d4eadc0fb83f6247819be4a37c125c87e32616fe2155f31f19a3dedcef6c9f1dbbaa0e8d965768f09c470efff509ab39ee8035e2f1710c1313ef3d290666738103cc5182734db225efaf55ff4929c592a81dc72fe400801b3e1baa4efea9b856c5121492bfb0eb8805d4ede8057d273ed5e333ae8487abf6bea3c90fafa372bb3dc333bf11c0596ec2a32f273448f3444983c408b52bb69fe2c52576255f7297c9836d0c7c57d2012d7f4d2839d8c47a1557b53072c79b547b076dcfed01c67b4dec80fc170131bd877513fc79ca1890980c2f3829acb5ed63fe22047bd87fe19bfbc45367cc45916ff69293c5bfe8097bf2cc6a6b672f56cce30f5ddd2e490c06500fb41ce6aec1845835c4049ab8df7c9a5d71e59d4eb80a8c41000b4dcff90d1dd524189d34e1fe347a91a374f40eb00df501c978a1abd764cdf17fd61a4c5da0a8359d068ebe0dd9b3470ed7d43ca60c9ffd64e95b3821017172de580ad6d8943b2f09b9f4a03204b3cce5511cafcbeeec304c48030601e1f166f6e5200eebb6886339e9b9da5c41c0426334f837f58447c644c131fa08cf816af26f5a37e1d5d108e37bbeed63115a54b6addab65b0508e370c54d0bbcd098ae477ad7eaa6c1a91377bfd261c03f12775dac6f054bebe7fc1c2fcd5dfecba42a8888febd011b9c8a3437443efc322abc0c51280046db864cca6a8ea717084b3b8ac547aa405641d0f57348a3b7add53acc9b0d0c568a21625535259850c082778653efdaf0773edf89b754ae100f6709e67bb61f63d13401ec3f23868259dbf6df228e3cb2425cda35dd5b73532a5855e1d53ef8fc71f4c0b5f53d91722fb09c0712fbea88db54596a0ecbc81e274e9980d946198ed5630c518658ec58eeb1ac29083c14bbdc4e81d87ac3581aae1495b386808afb306da2a98ae9f590f2e8362cc5af38aef4f30c6c532dee6817ba19c43388d8797a4c09a77a381ed21f8dfb94113ca91e7b0b0e5270867d3298f27b4d3bc61633f73c07c90f6a650b1ba717f0ef2ed5f0660971124fb8b6367a252de7372599f78bfe9b7573928857717ee92411de34ab1724ea58008bf210e4d47ba0e728928864e62394a5a38423030b5765bb37177081062400804f58eda38f4cdf6af80febed7cf7469d4dc729bb9add05e22bc63ed5572454ef3659e1c52abff7ee368b97ca303dcc7a1a8b44f7c4eea7020165d5968115c834c9f3b32733f1b2399a6015aa9c86a254349d95c2dc7330ae8d80904a5eaef598b1ac1d1148d5240311d15c1c7c61c9c6e8d69bec8cdbcb699a3de593575aefacda68024cddbb85dcd5145caee66725c372d1f9ed750bb66e5b0a9c278c10b096c583eb1a7b5c6046e46786a5885f379c00369664c320c4f5d2d083801a6bfb7b95db216a0a8d629c30ca7f12788a6c7cc9ae9a4de39ab5914c4f02b1cae43ed680c0a96c9e5ab2471be60d90a2299c4bde0f6bc10a13b266ae928bf21b158783ab7666dedd8e638ccbe44e3b4a15e98d3a11c6a49f6829a26c6cc4704e78054f15f5a02aaa291ae4ce889875130891fc0e59712ab97ea024707ca27daa382feb92570dc5cd6997a3d8e78b344ca3308dfa2dce992b102c31e4168f35ba1ead87764a46f61fc4c776709f0d7d40e7afea45011e3e97ffb95ad06bce790e286a0aad5e6b66e20df2cbbdce147983cffe817c39073b18c2c5f4d5032b123cbeaf0dda79c9e1d23ea293b5dd2e6bfd166356261682576c98f8a194f0b8219f9edd8d46d1d3fe29ce9eba82e5d279950d934d339dcf991ac41fdb154d878e5cc2a7fe3fa36e701e0bd57b6d4ad59e1998688deade29b68ee06963b090257125180d628bea9f64f0e29c2bd0e064ac4271c9eec70a462071ec23dc0c34687fc5f92629af2aa88bb46578842b23e2fa035523c0e5d0324d3070188507b3174567b480c140660eef7a55038bc3e3ce9e889611fe5300fd72dd470e38871a82e71c64c06deed6af9c93487496e73808fc42885a553a290063ed7615a5c69636483f98ec15abb5716c4e54f654ec69b4f3baf7f8bc551801342464be346f76da668f1c1f1cea72c377b73603f3bb9b29b147816b923128f47196a4841493aa6ff92b4dba175fae38d7e4756fe05b1ffa8bbf4911dc5a99eb7ec719674501741db2863995852a0e6169c9132341020503ec5d53e9ceea844f621bc17bbeae2463b898df94c730998f33b5ed766808c86d2f0b7ef08b4324c960147d0206c3f113fb0d9beaa95e3cc94dcc98e284f3818fbbe0b78344c9d357dbbfd6da0e4d7d1f935b5efe65d964cb5f96683c0aef78d2af7d632092a9fb105b3c1bcd3898a0a99e10e582496f95324e88a45638f1005bfb6c07bb76c5626f23c632d34768714db0ab807a8249a1360707d4afb41d4b160e39134f278d8302f393859ebdfe299d5431bd05dac063100327668817b0257b01af5413bcd1f1d6632a27833291c1b0c056bca6c090944f019ea5de53aebcf4579ca0d40631e6f5feda0e9476c256bd65548d0db1b66597a75ea498653b388fed2d4034630735e359281aee564893317ab26014692b972216c3e2f3b9e15049c36df16a796c15456d9d5ed5bd9676e42765f419fa647024cea6e78eb127957b2d6ba3a5edaf16f57e621702ce522eab31efdb608ff2a73be46346fb74c3aace6b19ee8a3eab10297ba1a263cc7c1cd555fc24bb52419a0e52c94c5b03a95c07d94839e9a126614af69e2a09315400d9742d830de42145bf195850f23ffb1653a4d105d0b6359dfb7796a95af783af6b136da58c71fc5e8beda1f436c3ff6018c2f8b2a32e765525a19085e6150aca842f4acf9d381e4809328313f6566e20c0d3d0f8d9af67c90b215517a3fe4bb972d31aa10ab720fcfa6e297425a9b70955190f4140c36706f02ba8b0ba601e509d65930da6b129e777dbae171936f21be05e3f4d186191cfa3303575847292c9ca4ca925cf2ec988b138982536d84a03c5c4916d84ff99cf02bd1b3494cae66bddd43bcdd2b03b514d7326c5601ac31d8cc7ccfc30d0c8842ecff42ee04690e31c89192677bfa52907220f0485e46e5f022502a2ef5d7399886925ff928df899023a34f492e24da6e67169ade50dde8f6db26e23a05ed2bcdde59b73fa8034cc5ee879197004f6c9378752330bc4027f0d5899d0e54faeb6f3dc1273a0bf0b0907e12d8a8b194dbd8e42db53b82a5d1f64d336b516b90be40b2aaef8a63390704e4148497a895ff8971ef6d5efc8859b82e2c9cb2b42230b74685ba6fccc2c709a37431c4a8d403e7446089dd035bd9f6aed4ed61f619d7800007a4517b2f98dbb88e725096240da7906494618a30e80540f2b793df0952f8b334173960565689f2a588a44a5aa0720cc1d176c2005b51cc1c7c01a55b1db2a6a15655565915779355b2ea78b4886340a2510cd82e84d54525c569adff33f401118f77e441a7dc4b420e92a2ab988c9fd245f7386218a4844fbe0c4ef88e3a15e390978d4508457f83ddcf1b1c847172a9cb6f6deffd6475794b2e97cf7fb268f8af4db113c310db7e261820fc3e8b91fcbb918053f2ce9789b49a304a92bf9ff4d458fcce2ff23c3056a7f60fc0c6ef9e10dbf8831f30cdbe271b46e919772b0b7e1969944172e91783fd264ec303f53516b4601571eb76bb6f04840dd025cdbf4818d0b1706b1249625cbde52b97a86ff0e25e7ab7f4e4653ba923cdefc7e844a17e08ee318ead52d44c9f18f2caabe3ed125fdd6d5714f83ee407cf0c90a7615f9a988455dd9afc2c17f05b1d590429c853ca5645b0bdd84691e9bc7a68ea096c5df6b69a311b13fe4c857027f5b6ea339bcf816eb0b3170e12941e7b670c1206dd5715bfb696ba3a3f6b6549c231051441bcb1f89e91a74bcb31b08f5e75a8dfd513e1526ab305fe8879b69fd986f63981e44984d3b9aef2a7a8853f4efc7e1924922aa6a1bbc71c80f7f8c6c6762bddfc1bfaf33da4891391068d55ee19ce134acc8c348454ddefc750c88e0a4bf3d935bee1bedd7c5824fcfd411f29eb9b28ee5da80f8d846571c1d9e4a49a8024581447db03491232b95c72a47a9e03f1a55a25c0ea0c0aa5dc14a4b6a65e42bcb0677b72842a1456ddab14c4921f10e5c9fd731994a5018e7d2b63a6dbb3b302495c21473cbfa1f84fe50f73023249ad15e8ce01dbd0dc2542b660fef6ea0afea00949de5f592881e08cc5f1b493b51608523f9c1b58a843ed288145a4c67b3e02e524242cdd915607249aa544b03c16331433d8ebdda2607f3858464d02f24c134b72201645c392a1b8d12cc82b8958ab2eed33af52237587604d347dc99b4613fe719f981f6b3767547c61f65cbbfb0340862c297fcf1b9361d0a0803c446fa8a070d1130c20be4c88721d4acdebdc63127d7d807f2275e2bd9d3266f07dc4fb39df22f142cf1d82eb61d156bc7d23c50cd79187434385d93c1351506d9ddb52b7fbce830b7b9f2a96e940cf9c41f41abed0c8880e2b06666894351cb08394786809efec44491c3aa5c6f5d7946776ec5a1e7047f81be8b510ff14bfa9386b0c811908a0dfe17e1dba103c1ffe4194ef35bc95606b67fa27c82f535ebe55723f9a635903611da2f57a86b9dce9d721514a4f13b9a98476556896f2134740409d302e6d0d6403e7340deaf14a984753a3b61575304377677089dab1841034593e2247aeaf16191c61a26d4a6501f9acde3d679e62a38527f80b9089e9da1de028350c0454b9ff8f94e92f5eb54fa0d77cc4c54ddc843f6ef292fb7bf2d94598b39003b7b12163691a97c8b5c261100278d57de2c874d4a86c04e7e8f437ca8176400420c439389e2be6e4334848ddbae41ed705ca59ba96060c86d72b90e99ab3b5eee3bb7e4b246312f4c8aacdce6beff2d4195ce9a364ac9b6ae86c741d902944f5bd59821f35f9dde33f4de822e141c6868fe430c7bf736189e198e90465f1a3741a406b1bbf360bed35454aa08638b5e886490e749bc4584e063be61e605f6f5c046f71095edbe76f8fa5d47844dafd535abcb7faa2933ac1ad3ed954c3dea927274c63e57461bde0fb0a1efc2539807ff73231e2917906bac85e22551b673565e74e23fb42255dc45a57f11934bd441028e6d96eecdf7f254af796bd2f795ad0d3a625550e759fbc1391cdb42ddef3b92b0dde78d250ad59a32cfd15de8845f9f0f8f47abd79d9a4b3ab64c407fc5cef45f9c592e6345a7ac6f1939a6958857da19eae7f7ab86abe60971d485f59f02221a6e37f83ef95ed60518bd6091190d02ca7836039a733edf754920947b6469729cee6fab95633e03f83870ec2d33d94263b39b33b6f1904e5f4122f1bad97bf41944dccd0f949da10c3227bcb14488138e7aba4eb8185210857209e44be423072deeb304186b363f09166531c53d2df10c24547a815e31b3a3af22d16eb491f778f689069e091b8780856a7d5a4de8f77578a315df926fe41aaf3a92ddbd3744de7c0837032c48d4223660aa220d5a1cbc08f6cff6e43944d2341a402cba86b2175c22b124975e50980fd0d4f63037f6b597db249e6b1073fd22f188dedb8c1402f91d751138ff3e8d5fa8b2b5018a2c90b12fd5182ef8b6131c4dc2d7e9478f27727fb49ff40125b8f2cc1f4b1a7df2f4a2064d24dfe1a57e13be7427bf3255610cd75943b7cc6f950d9375c91aa3e73640d4c3fe3e3415106815d11e52fd4ac3ca528059a7ad425e4388270edfc601a1252e4041727fde6a1ba78ef67c572ada720ddb0ed35640ef1f7c47201dd7bb8b6fcf585e357a5195abd37d88bd9d6c6e4887b7a43ec35c82fb511e36a72e07e1c2e02b66f83eb5ade93e1d2deaf4b71cb7791a041a2e6663c8991cd514314471f9db0688ac3e5a5a3fc57d81233b3f1475e2805509e444a06797fa22cbc799708138eb05dad2bfd00ea3920b3723630a800ec29e8edb311e704f322d8ac88642bec1ae76ee102e0cd03d6ffe71f8684ddf4a01a91dba2e7c9997a2c83c89690a49c2938397e9b888423edc4bb0fab99a626ebbf987e0e17701d866d5d29306c03268dcad1a95c0592d8443676bd66cb16566454912b171dcc69a14c0fcaffb803a617e00d0d417feee409a4efc7239f7a9702f6f6fea4a8a0ca7e8cc00b48f170c9e8869456433f50bf02b22f6f763a2bd2ce8d26571c3a0d80d176da31ebd5c0500cb61eb5da444c139245471a2df97c41eb87fc582255763ff897c1e30385f19168efe95f877f7f3e894118d5f08497f4e9150768717643b177557aae0d9bbdf5fcc00a0e6ffd6bd3caf9a8cb7510b04f73f534b2bd3f41f144e98a839d20477b70bfdd7e09a10d19bd88b4d03b98d08a3cf54ea7a98b1211e08b7434aa46f034f0d34ef1dc2f4a8a8c7064d9de4a11304eb25778ca5bff953ed7ccfa643337c55df70fac4acc48563e328382f77aeba09a47229dbcd29cf685ac57f2a16110e8cdb421786b2c6dde92c402878bbc178fe4e90d5048ac3d219626819bd05469eb560d4122fc1f2b39b814654b0e103b84f5a9d91296e5d6f563633e5984a3332f2f97e7fb593727b2ccfa6f54f13c815b24d7f026738708c8232eabf68c9a96aa1bf28b588f9d2147314c99ccc4bd24c32bbc6511a6c446e343d4b11c9ffaab662eebb9137077f82cacf5c202e8dfcec050b00d2153deb30137afa9db8c927a00d5e96a08c659a7465b4732e7c9f8ac29d9b6d6e2708017d40e2d20290eb8b7293780c0998da889b176dc2892b9d2112bd2267caa4c888480e661ef5e19e13629e538e42b42f14d3be3c828bc46b37cc42615350e1f67aa1e1d6c5cb4cbdfd457d6fb58a73a92f8e694f6b845329de949067bd46bb092833b603537cde1821aa2b7a47bf915410c6ac1dc71adeeaf60e09a52185e0db60e56865adc2cc09cc80b8aa4499437426fb0e2fa359bb0c6d4071024924c4b940465394489ba0bb3fd2891c8c5ed5363ec5889c1bc5a0fb9830be9bca8db5ccf085171baa943d2d8aba391807cfc0c0b3f34ffd2015735df3492a70bb077f604a875be7ca5f74275a3b95f3ebabe9eef71cbb9c5bda4a0bbe412c259f9ce82f3912364a93be9f6f88dbcbdc8ffb59b46f8d79cd2c87973eca657ea5d0b542799e21f43fda893c9c3d1918a9b2166b97f51c70c192bad54c6c901a4e0e6c0d87ef9229436ad5041bd7e3340af48daacdc373ed162a941e03a91032bed598d7fa15258722403e18761112ec241bc1e17785292c791d0d6a7ec3025aac8e87a2cab10e2352d06e9f24410257c7af5c3818d234d6521dd31c3920783c129c10b5d6e6c559e550f711c50464cd003ff5e6b1474b00a665fbef00d800d5d6a810550d5093acd128b658b36a2eed5f38b096400ce2b2574280a46d16605dbad5af669b7468c1071e814ece991e8679e82e039b8aee621d9de487080f48482353aaaa946f43d7d5949b4d80b072c832af1853596fb3c309095cd84e548f4495d33b72c0790b159004bdb31a02f40f504839d6683ea3ad4dce29561a09397edfa58f2c382fd4058c65407078254bc5a8a97510000012549f53fbb9d31b2ff551992f8972af6b9a1169b180f5d3dd06d820bcec52a04f1b3a6ed040a439d944e4abba7dd18e1daaf0b6bd1b02df0bba446a0c58699b4ca255d3555c14f99128c0893a19aa488d9f7e4625a9b89a05520aeacebab14f98f6e39a139c90dc10803e484d079f92fde6497f2c0ee322986ea186d7ad387ab52bf1eb15c423b2a598e7264dcaf25df86a202df74f4b778cc8434d9af7db993d91ce2a51229b197bfe53e8eb7ef4f5862379589c86721f0b9c6535f4ac99b3b20a20537d1a5dd6543e42fb2126f070d766f696d754ef1d0730c744611404831d592a96ab2ed0721c61e22c0e5fadf96ba16259a1861f47d33b549c14db3f8fadb3d9d52f4b2fffe7ccaad0ceb1bccaa9c0326f4b003a880c6f1eded4b288f79c8afb92479251a486cf352c9234be73a7c71c5a822ce36fb0fe30717dcd47eb66635f0ff48839e95f10286585036a27e2f2bbc94b5a44d6d83088b8cd52b282235aeb6b2093904ae3c9656db7a1483e57d85f15197604b78abfe645390f9f917e8aebb028c92a4b1cfa4aa936b0aec8ca3293f4156088d9da68db4bc6a90e915e4730c4942fbc13c03f7475b339f99add2aff32b6798d8c7a40764ae43051475c4a316b1c2ce061e83ec257da540cb99ae091e08a25b28cf6cf74008017c9a99d7e12f0c46b9dd42cf92c1b3d8d74751cfbceb01c3d3580cd71eeb190d840b80a0308df62ba84585ba0b6eeca66db06443eda32beb317b8052c5348fd384eb465524d4fd754a0319ed43dbb430dfd2dd0a38091d1e5fce096056ce91473a2ed361e5af944821527062f94ce558a2ee26995d2c5ac394a09e165057205b41b3f6e30214e270207792e9113c2f459a1d7758bcf99027859b8c9e812ec48ab417902d1517e6daded608a6415cb5e3fb521e249b0b9cfa133d49b4999be051d86ef345308f01195771b9910140b76042a227ce438bc0d09e7e762c052ccfb0d7b38b6726daa8e237e53ff4d514a8b033c02c99ed2acca1d5ec5c6a1d5ddead90060d819a7bed90c45c98f1e431bd0bf5efe1c077828f4923b242e8a6ed10d17005c0b5e27a95a045063afadc721e6a2fced5eef358b009a655b5b49f702da961d666dabaaaaa3609fa10572b99670d44ac1cc400a5f604b83012ef5dfb4646c5b0fcfcc644c797f3af21a3822d17f7906d2356f44d06c307dd574ffe0869bd82fa18d8e582e8ad9f66024fb19bdc70c518b2aed6f7eeb2401d919d22ed79a8224a0f6153203b860f1ec11ec8827482468f3a482c641aa04302f8380070b8a4fbdffd3e9bec28d5ec073fa79073e77da31cec949031e943ef4e4691be6d72d16e68dc495bdae970598f84d753978c741ad0a8782004bdb2785c86ad2593b35534194511d633fed04f5a136cf0f826be2cd2e5cbb46046e953011ea69c91b4d9b77b225955df429493e6c57b416ad1754328b4dbecc4ab48ac3cbb2a7864c40a8576a4975cddc3fe31a45eead43f32fa6398abe808c686146da840fa98813e19c467df76ab6a0c7492af82f7f15c9d6ff49e35a69440c3f5d9f3cd6d5a0fe23cc662405410707700178d7eafef49603b90a8c2bfe1e22376b7e886ead079872ba2e291c40a2aeed14c88dd78db4e718aec01c84f529c8e8903bdc9715801c3a3e083a40e3e3296b8ac988e886fcc1fcbcc2f016673bb4a003eb31b13731c7ce73897cfaab3542bacebed0b6b594d313007a1538ae7c40915414b3175f7c6e23a5d9f65c9edda2446db85bc80d0090c3199ecdf32214debe3a9851d528f187566d70be9ccc9365146da6d917d7cd72c09cc0d404ced5eabead0fbe1f685bcf838f5c245ca3237460cd591b2e7abbf2b386950aeb8cd38d28f52ce4fc3e6246501c54c940fa430df1fd63ae99bdab5fffffeded4ca236b0b9927ac67b0eb92bc1622b1f3977bc305aa269f5848882807e7342552cc2aa1c1b64d742dc5bb4ebb51d15ad7971c2a2dfafe25fff25e66983a30789279c29ebbf4ff3332a079a8158fad91bef5e1383b01c7406d71a911828808b77b84093dbe487f83edf3a19cb630e45dabcbe07915d6e6fb18f0191cf1244cd58b0fc86f260a6b835d03ba35129a41e6d6e87a0eac3327a9b76ee004a7a594817734aa9d0577a9fc4a2b4e0528b68d9b114ec14027f6a1451140115348662b5a0a804ad5892ee064fd57404e4372f8c2f5b5a6eecfa0427dd38d5b86d005be6106730ca962b47cc9208966badf3d96319307023966d9d66d04d10ff8e5e722a60401c7bb62f04ce31d5879b55a8dec4a83e26020ddeaae8bd75a7666ddb32d4be5133f48d5afa7d42a6c52c7425c8e946419aaf9dc41ce49d1ce7524bad3936b3107722dde212554e9037b8727a7c870953390e401f1ab6758a389275df6e081ed2cb049c25ae7457d6f0a04c6ee5ed2a5ebd3ad862f83060ea6086e62917b4bbf47872e25d27205a596d4b54eb757fb5fae56c7efd633e88775bb77e159b957f6127d7720e516e28271e85b4b0cd9987820fd3f55e4b2795205c3ca62ca03b5fab7994d9854e68f758d5e585cf814ceee9761e51dfb0a8b721ccfbee1a30c6ee159c2e157c33f8f938d77689e0d4600e59e56f958e1ecd6f8d7a4089f6812c96a8be3e74f39e6cc1ba60ad20ae1bb75437439256700d0bb9b26f5bfdddf347b7ad6eb5cc8a3a9fc732144608d8cf719c68b14954ee7a2c8b54b83d96193d9412e38962176509f701bdcb3c9dc2bd8f82e099264e9c70bff795f9b4fac55209413a004342c76ea6e4fcaab595b61d742374abec718f638eda8cdd8727eb5675ea1732a924fac1448fc6eeef666d155ec805c9a994a6f410194d53c1d238de1f9060a070f4c6ef6affc64ade188a5f7393b817e3aea5115b8c60a95c1597737d29532b0a4079d2f1b97d9d7c2e60a11cc64b14ce8c85cfe893fd5319184523f6b87aa2c0db9fc2f239f0eb9e62fb122fb951573fa2477b0b6bcf0ec31e8c1bfb8a5a81eac95b4cf329fd8e6bc02506697068dbeabba2cea5243eb7fbe60ab44fa67553e1382d077fdf42380f76f1f6b5d122bfd5b342aa539b1aa2a0bedbc038d6a4ca4ab4fc2da82d96abbe3d15cc2b1d7b6707363c2d6c2dce4a968d6e3d065c41fe44d19dd3ca1e341c5d5a7c7bb75ac281152e66bc0617fa67d5f08d22225a1d2f6d2ffd84c78b2cceec1862632070d309ff4423fd08ceacf7f1d618227f7b25ef5e7de518c1e13452c5d79c267bd59b18639097b11915091ca8f60ace8b1832fa3ae9148486b2379e35e5be9f2742f4191ef42032d333df6ee62a15080e368a7c8f405a021d7a94b0abed46abafc67ab681853dbf93fb4597b63f4325dc2e2995d0caca125b5183095039b1c0276d76de90869af9d6e4ee9d1f8cb5897317ace838ec879764679b2350c885787f9ab3b4551d74c3d3697294fb4e28d544bc6835193ccb6836ac9ee68d3e679ad13ed7af596f1253d79e073c02eade23c231ee96b2820cdd2ed2c15d6211062bae9f8c67dca8fcf1128bb702aae3428ffb29fa47110ba9757b9b1eefc3812f8221a056cfe62f885a03d46a1df4d6d76e522a14d351577ce5c644c5334d6a7a4e7a999f19689bea42fb78fdae6a2f7a979e055f0c7766295fcea20aff82634d40091b489985e1430e76955c1b11683df39356d8f534108ed288b21878a88d277dbb7c4a088c22c4ee1d78d542372b3fb5c4fb49dc5937941440a8ceedc2cca84e577f355f34c8bc8ca664360e6528125a7ee51834921e2db49e5d30ed4fede732af143fa0d5b8c8c139a760a14858a474e27061e93776d6c862d16a6d35f0ac2ffc6e7962960022daf32ee4a2a7bac90eeecf43ff9d3c188e0134a879d981f71c115c963f5e180606556166b525e90f58e57e62a60918527b164ff168017c1869eb3b15091cad081b220528df77085abb5d49317f4783b262c76a2ced4d5d48a750ea87f70fa140c383f6d23adbd23af8cb68db5b37f41c4584bebbbce7e570bc13812c80b445924bd7b2d76654cb63e16d380a2cabae4259aed53ff9831071894f7ef35643ba10fbf555e1ff857595d712528f4bb944cd3c73e3050c5f8dfc8a6e3048455830fb7910d1391f83d0e522ea302b35c8d7638d6c547780a51cdb372ef1ba8b882692b10c3458ddc22c428b3b7f95c9e0e9b240f38a0d4525945775fe4a54856ab0e1cdd896cab72847930b017816029201b4ddac07dd61a86bb34ffa8d43fa9bc7560339517d8a834287ba4de5ca2c70386ca154a7c841ff2f9115b62fd1a71ac657346dd2dd2a2762260722827fd9e045d98fe460bf65ca679b54fc27b9d141f6bf132e0e7ae2038e5fc0399e2bc3b03fb15a9c383db5c73c3cef914893ed4997e1d7f603a5335a128adff8363206b886c8809bb86f5d435bc8e32df79a9ccd86a5e2b43fdfd4c7425978ca61ddee56814978cf54f9895a080281c209d3ac9f245bc52efbcba83f89a1f27a5f09eca43b0ce3ff22f2fc8e51d58be878886e6343b675bd1cef04b681e7285bfe17de865af1f8a9c6db77dd905d8b234f5d204e0f631b7e3f3d5641ea2ff8b6c4d8cd9dbd55d74d066438d6796ece1ca709a0a32400f345f8ef620fbeff951d0ff4eedbcd4c081efe476c2766dd74ef50e0cf764b19bdcf431b1fcbb07d6353ecf3ee3f8d7641808410c5c938b63f4a4f78ac48d0895683320ba843fefd20139c847753209c979512151dafa785eb19e262c1b853f5d3dfa149a8cef876d20a3b64ab270ae544bfe4fa81de3b917452af13cd343747bfe1ba12bb5cb69e9e010dc9c28b1dacdaea76905d516e0e55bf4eeb8475dae18ae92331d43ce99cdcb6eef85e0a49de0ead2e7733c4171a0ab75b3d37881b9825de7db7545a52e64076e16c126223f9ca3535a902b83aa2620715d74fe30a0ac6dafc2090cfa9b3fcc68650327155af6a28c780c742be3d8175b62b5ca0ccdab5f414f05eec069afe9b0966e56f9994731d1112e90acf11ab0bb2365bc6b7c3a3503f468604b924c5a7a161aae2575cafd68ca4fa1297fd621decc870120f33761f4a4885c169f71e8d33495b5757d1f97f8005cbe34a7fa2d5fafd14996e22448550e8a168fe1bde25f4b5f5d19c8cb73502e5892989af5996cd52ec9b87da7508ebbeb0348d293e0bee86ca9957cb68ae48fd84a473d3ebcc7c83c0a40050a84b880fa3158b69b96de9fa62b40d2ab7b6ef385d6c8e0cd18dd9dcf905bfdbbbe69a35becde273fbb9eed694ebfa2db477c089d73c24da00d832a6c4cdcd1cd70a67185b3e5032ffb7dc3056f593f5a9aedb2738e7148388f31f0cfda4a6976ee1a3045831a8dddfd40aa40c81c66a1a0a177ebef339d19f5c4a9659a930a8f50fe8a39691c48027ac51bd12c2a0b0a73d742ff3b778b4dd61d9dd28c320417e540ff566b5433175a73a7e691ac29580d03763cc0a916e2f52fc96d0909b7b02fe80363be9481ddf0fdb09f6947f72cd718614592065e673f52c9bf3f4702207aba157b4cf8ebd53d671d90cd05a491bc91094ffe569d73d3e1ce088a1391d2f49b298f8bbc04c941770e4379144871d6a967a44503e21e248a62ba8fd93553673f74e2a30fb2416319e1b494d0bf1da39b1020ad0cd18621c4b6fc376b52d5700ce88be60c1d0fe351ffd34ef5fdc9ced99ab7dbca134c765e35286c5d47af585d44f69361eec460bdba0648f2ca0d8c89e7ed88862214d4209a4daa4a08da6e761363f253a7386fcc50a4ccb4ff657cbfe25c7b4b2d0950dc5cad67e53d76fcd87e66b4264306392d6e7bdf6d09b228413a32d78bd456cf7a65404304d243c4d37365af12120d3515034a141febb809cd00b0bc6e3cae83df043008125d2913732c1ee250ecf0ea5cfe483af8009e202b32ac36e4c6c577aaf55b808bafb06032d31ecbee7097c79f919bd6cf80701f88921079d869d42b31b8d516759d887f11a24a0f6701ac01d2f4e26e4012a021dd71586f202819423ef5634af244035eef8f5a7038901f8efe3baca334e081f46572189bf9ac553b6ab300f0684496564c595913f525a3241fb6ff1aec34cbef678f7442376b4c431f135c45dab75c10d673271784352e544078d23ac3081a5ecb47a7ee76ea480d9af558d35b6092ef5e46f77a8f757da7632c92b347eb6f7549fe7d3af61d97c0796b8b20da0b1c1623d1d15458fa8dd9d1f7a1da6355addf3057d8f0e3b908f5b007c0338f2efff37ccc11586bb8231acf50d2d4aac21dd3029a2e7b467d72bfe93b76e6d6a2e5676b26e6abef6d4662e3e7ef900fe7ebba6ae309a0d14f2eceaeb35e074baf9c6f8bb1d4118a2dbfd8285d339290f5a66f5242869145e49895eb3be14c51c6976867ca325c0e6ba0465256d18aa233c4ff14b1d74b3149918c9524981785ed6be03db2ad347977ba9b5bae6bc663041b08123ac143327300799032d204a75e54f5a160c332ecce258316e49b64e22d699c237d1e650b554ccc1f281151fcbead4697974942a678bc0705a16d63ff59cebfb8395d612d8be40f77a28d0a483d7b750b18e65932fe9e48342ee379b8bb4d6d9677fd4d8af9b7613b8baf13c801e334cfee9681462caf3d09f170c2e2504f3081661593cefbbc0008a597f5df9eb0ea70f427290de5148c31967e35a06f1f4d5113cf2959f16ce947ca97825b9ccedf9ac4bfced2c8bb6a2a65e8f3192e6e79339fe92a8dff4382f64db64873cad100d7a354f5515fd1960f53806149bad12418bc33ad56de4258198e2fd4fcc46f2eda04326b17b94958a4cf8a0fdae63e5193cf13f4fc6370eb0430efc170f2021eb9a079e0b8355c96c8f33f776e0993fbae4304ee9b4e548bbeae1aa2b05507caa85f9dcdd73804fccbac4c84d1c50f14723b1203df7a7d430ddc86aab6bca1722ca2e4a92a1d7f2c51bc50efde88d7061e175f33d750fad70bb53e399931162ed3d4eb7106a2d207b3e861682c4572d464f2a28506b1e2d14d7300278c165109e79a227e226544ff1d868891bd7a4eba198da3c5fa14ce9be913c0e9c9005fb8af464ba990b7de79cf9440c94cb2396b1eddc09211e85aaef9a523878e39f2d62ac8ffc62342f2482d0dfbc4f25be8d78ede6cbe3f56a8cc669205578ec7e1b85bb5296abe7a912ca7969dfb367073876d15aafaf5efdc500db7f3959206a6ca949189fb43e1136894d2ffdfd249d77f529a96d87c8ba1888f9fb2c7251ede815c033220265893667f986debb1ed73bede79dc1662bda221675eb6e9791bcdddcf3a0c70c8b1716f91fd2978f85d8b40f6a43f40d11a12a60789ab75f20dd777a464537c34b9ce6a10c5e66b1ba4c89b02ae149af139250924a2f033fc4808242ca81626d89113b620e44db252f5b626c015c462a30d944369cd17de1cd98d21fcd8b94448476a083d177b87f0b345b157f84e6036cb1d0040006791644d39db19b7739c41cb436716248192671749cdc681c2bef301015867035a11ab310138cf52d124626dd12ace46e3cf09f4faa6d8aec73e95ed859f7a811fda42f9807b88eb319f540d8b12bc751a4cca72756d318616b403567142b40e19030ca574b65d6b487d68e5ab69329b12ae8b88394b2e23fcd3f67f94b07f8e5ef110233fc729739c17d0d0b23bf2a0ac91c5a3aaee9ba2dc5350cb3e5aac9b38da164167ea50c50b34ff5de298412d3db28a5691a2853fab574c9dc4bc53a0a815cc0e16db98bf43dce3d2c2ead53d0de539ba9af69eb47230e11dff7ec41ade407eb79a83b47e4784f3859e580461dae363e9d4f70945d06ae820cd9d0425c230822ee63d6538f0804694053cea2952e7af3335f97ba5752deb4c8fa8bcb46fb4b78410a453536ace3b0a49a57fa09c11c99e14ec025da8e7ab32aa0d9e31b227d360d1f001c6b3265f0902f0c5dad0a89edead48ed1ac24fc791067796f08fd556df08f079501f688b538c6784a07a21812ea18696edb1b10678923662dabd2a6612377f9836d64b83e884fb44f71bf9e55b73251a3c768d3d42bdc24f0425b8dc74049e183b556c7a104e0483b8a860dce20b452fe3a785ccc6ff42464a660553e7ba0ec0bde99b484392173ee009b77c3337ceaedbee9d231b4e2c61095433fb38c5864f09378b41d9bbec62169bd3dc16252b865f88c9dd76a9edb2f2756e7fd13a6f1810b7bfea577751bc11af2053b1eb8f1898c6c94fb091ac008e83314ccc4c355a1034da097a3a2e5ed7098612cf00d3507f8f5ca4af8b11896cec57f0b8d82ac9db4d2dc5c7ba9abc85612369ffbfd5e5c2dcbadffe9f24283c55007d3fdaf6e0c7f8a3d0b07402e14ee5da2b82eb7c93273b67644edc396ae3133f0f1bf695e49efaf7e09f26596f81b5ab183226394a204e4cd35ed2ed5f8fb0a0ed37f0eaad436f38df6b535f6d0466dc3966781f970b492b6142163245246c7ce34534dfae1c48a096e0c2f6fd7580f5933bcf91c6cf765afb50c1694dd2b916c4ba4f13c6c4c8bcd6d5ff868231008ca8d77977835cb4d3d57bf19526a8be933f14731fc9f207e44371d971d4ddd7db97fbab05f5eef4923ce6479205ddd6b97323f763e6026ffb4fbce29788c86832ebaefe179dd3bb44c2ca68614d340e84e24fd569400f428e15adf3ec6e3f0e2952e0ec1879c7a44426bd7d89234daa84b80a2bd3640f11340f1ea51cc04d94afdc99577a2708dbeb8b4ab28083e8a0178d1ebadb80084647e5f5d83eabbebbe67bcd03be1399a22fddd2ba29a3301b6506236a6975f29cebcbed309c350bb7cad3cd170eeb41d08f72374960bad4f9330b1fc4ac2c98a1642bfa93829ee5b2ada73ea065db504c555aa499957f5c4e8c6c4f7651d3633bbc8c56ac29b96848826ba70e495f399720d94ae67ceb6edf29311bd33ed7b920b9049b6ba7ee8d0af6c3d9b60e98be40c8fd5cf2c715e8a8beec7951e8cd3f5c25970e83f4d970fb5bff0c4eb778766d8762fb82f33c825705cd73092ea42ecf644c9feb00b0131667d1e673564319587c750fde234b86cea346ee3acdb999ac5bf3855ed8e5cbc5b4dba8260b1b3fd076e23d55116a70a5dd8babc660ca66fca4c9a0dbd6c63a6b27cd614b88ace3b416f835b70478b5a516373eb3f1c58c191984fd25edecb9dc102250292d2f467c3a22a4d9e36fde4e25d3b5ab17fb1133a471e043f5cc6fe02ccf9bedad4082b0bdd3f6154e16ff52752bf47ca9342cce6cb2d30117c9d4b46b612b264c5d8c6e8779f6c7c87d2518fa8dee99fdacd7424abdee6e3c7b4d888c5ca04a9ff612e8e850ead1fb33c03af1689998baf8333a8bad935c9629a30188e27530ed6a23e44b3ba517fe29210775eedfb2374bf8360c1355e430978fb9caedd394125f583a31e95e04b2a93301dfa7d4476db65a099e3e94df97a245ba3fa8e897fb3cc55a7ff6ae0432ea3360fa223b577548d85bbc44ad6d8b64dc072ba9803cacd8c8c320d258084d15b78e0623a6099c28662894303372c4c0c614d42326ecd87d6f8df28ad80513d09aa989ffa8280016b63e5a21d414c99a0d48005146f30ee812e002dcc97471988305f31a06a0c3e962c3f14bfa3fd120d7823d9dbb016de7747b1f1254d4df8969a70d9b5706ccfe0baee21a2421bbdee191fc65a2a9ebb3a72eb4a4be241c5b53cb42cd6a0e13a1a049a0e63aae0f8bfebb7b4f8de53531b560f99b3369f7a6c75eace0691287e65eb61f00e51d17c03f37817d059011a51c89721e7e2e32d2ac84f7dffa927b935366be07ed68fd128bb3e538c2ea57a2ecb9bcbf44ff682048131715aeaea9ec7aa378ca5dd0063b73ceeecb2a187e2a7510ad6ad8ed10cacc17c64071aec098fc07701fdf58d43ac79f799f84a2ad474c44bf00320d8425744cda4f407b86f35e03c27f3545b8bd0e2cf5e2a6f46ca1a51ed40822f91d3568a76678f7238874f85aae5283a41a1b40f42c492cddd7cdcbfc3f07074d682ce2ceade8fd585a0b58a30d2810c4edb15866e3a3e2126861e0512959982b57bebe9be4717bf26993d7ca175be7b8328e24683c68a7ac4dffe52137ad74b89546729dc4d8ee695cc10d8f7ad794d68d8a6ed1f765f7a657ec7d868f732c9ce21fb796eeb5455b002c5f05c0faa33aeb5f06ee7b3b37606ce6b02d26df71867cb988fe1c100a0cce52801405cc494433558601b050211f924291d95a52f1a69e3dfe7e261ad5e1f7c354f9ff03d1150f8186bda662d58a879382dfe75016310ddb5f77d0bef9e40ac439c1569ff317bdcff7cc18eabc2eec5a79a92051aae1b103627971e444441f1975d145f54a72f53eb549afe6d6fcf88693348006cc724832cacc6392c4d198db3261968c6497de01f6461ed904c6c8be009187ff36261b09e1ea4762c284776e37fce428d7b9117f2f470ff6a1c530bf4f67ecb03442cfdbfd0217e780f537012d0138cae84faf5a22c5dbdb0416294adc6f1748d98f41f6277705f4d6e3433d3e25e698f39483fe41ad17558b38c56a47f7f2de5fda7232425f35ed76b0f20082efccf7bc073b5262deebf1768fbc0f9a6009eb927484af7505398c8be2194a963bd7365feba6959baf408230e5823ef0a0f3012fb2c4bb986865c256a42db2aab8d39f3bc8118fa066b8ba7fe4f06051c1174d176ecb431fbaf4a2aa42f11d256a9bd7a7e8a37f57c2798ee39d7c6fa860b7460484a658bae1f87e67b1b782fd2888cdaedbd78f6272ca077f0302faa42f4e9a0e56169f2cf91d9da50434e3e71f3bb34af4fea5689b79a9fd711b51ac7340421949dab901cf3c37d4f8d9e5654db3b6f2c9d5d85b561aa369200a52e886d93ca7460d01c768bda7e269cea6e306b47834b628f0ae2f0e622db22e97ef569a721a3a92181c78c9f07bdb67a715737b79565b88a567a75f4961aa110f79e4d7a7b296903772ec4e0778f23efde70ec9f5ad1a0e973fb36c9a15f1fb3b0bc7a993e4cf00339961d28be9face22712834ee679a301e923c61d87f531f9e9da72876cadf489d49aa11db9960a60b8564d2520247a87ca99d8130d2004f0ab1c2fcc780c2e88cb34d76a05836275eb7392e8d97cf63111a2845b10a0b48ef796a3cdd3afa36dcb90658543fb3d7f4d5738de26ba68c873ea1405a37ee89cb55849a95bc0c2df4e9440f50ecb1420927b28768e1839b3194c1e04e8305bf55eb0ae573af01447470a7d761c42d68aa00e9ec8514e17c02abb4f4b5e00eb519038ad3b6fae660494388820a7dcbbd60a1e0fb39e87fc5d6989c08104aafe58b5ca6254ddf4cd1057a71f9ce8c50bf218cb3e8a336e0f6ff630c900e65f08ed5819211bf19c8578c55a09047a2260ff50b52b3842fccd343a58de035c8bd5bdbe2990267b5744860c88929d7eddb26ededff4db5abcdc4c1b51cfcd343659c404e99a7938d6db52c69512534dda29755f2d213cb61b34c7019aa2b16561e0f693582516db253ab549954272de3d0da38f0388f6475118d247b9b6947617e38bb06daa1b808cc7d9e519fa7530960b29f287090d8cfbdda557b489c8152034ca54f3c89d08400244c777655765470192cd10d94410e54620fc77da05b268da42646d49571521ea532d4d0491578c2c4f16f77791f96b3dcc168fb235e20dfccdfcfe5913014d694a2ccd6093af03c33ae3c6989098f7f53e708f74f49992118eb1a4372b626de9f33b668595d5c684ec25d74cc2a159ef5aae0994375ff60caff62d9941b7d6e28c54297dd44e1f59a8f817bb182f5bcfb06757e5ed7030dd1a18b254ffc3cc3f245cb2c8f026b34f91a07360424aac89c0acd375b76f57a0b60ceb7cc20ba9fe1146620fd7fe39bf516a3af33ec1974123abac83957fc612363dab63dd2d2c7c20c9d225f3fd546a9b730c7e86cd4b8ad3af629fa2c3c5cf5ce685a5537f7842b8d4714783c7d712af227b2ec01204ffb3c4dc82655e66d89b87773185257236512858e481702ff8c0905775f4d3538f8d6c8062febe9789bb213e1fb78cc75692e155a91f261f6728bc7713e9470365640d3b591fc3fc4b332d6195365144feff451f625b0ab012d1e91d03ab51651d02297a1d727c97fcf5005a70e31dd0b872ce2150f7543ec6ac25619e0b388538f7074045e30513799a38fa6caddf206c7d6d01e7d9f452c7c6a89a20a12664c9ef23e0ff1509461313bf5492d3f982088e117ac8a98965520099517ae712b0f6f811f68365711d586fc8c569562b63f3213d40ccf2bd1a1e77917f8eb3850b26e195da6e7d10a503fdf7ba618ce16cc188448c2c04e107023e5ba187a32c312aa3d07d7dcdb58f02fd021a7d2304e34e7369004e88184c26681b1205964eb4d825a77fce64d94bc653ab6da825e56681da25c3de135e0bf96d4ea09db96ff46711ae3877a61b31abe784c557bb0175d9c3c688c2fdee9308bd63ca6f9459c24dedec5e3290e67570fb27e41df254b8d425f5442df9bed033f97e518fdf6fcd301cfbbecefab6723e1e18b5d31dd2f75980d8268f8a95bca904afc57722c2381db2e85734aa46612b50fcc7932c187158d771536c1f59b3c93dba2b07172822235f04cbb243c8a1cdff6fbfc0f8330734246bc9dd08c02b36d690b42353d7eedb7aff9ff91c02f1deea0075fe23b7718ea05ffbff0cadbadcc4faf80ff9323dd109044ea57a02f56a4b4a9b07b4ff691b13ede71d171ba4ce29c67b251c20d3225a58d1d493c06ea7f7b6b10866a66efbe0e3cc08a148dd6f6167173728952ed56e1e9ac6f781362d3df4f35f2b54c483d571b69ef8083358933c834448ca757d9b6b2a107589a2a6ee5b9e9b599d93479ce0d5faf107877ffa1b3b86befaf7a15dff378492e4911d55eed89daa89c500d8942484a0c644fe74bc696c66786a61efa6fa1042edacd0ae1de86db90ebbe1ecc8601d2896192c6ec1bbb313125ee36f898020a62cb75e837e6e75426fba3a93a33167d71e0bc7ba315e59fa742a9637fc5726658440506dd4cc601e9b2a8ddaf6f4a0992ff692e1f6629807c96f111bf203f6d0affdd3233a0cf0a99347a8a587015eeee6ddccf49cf6d74c24027cb634ea992cb70427e8825c4a4b7ea4b9cdd4e215fb2d37b2b928dee5749396df99c806516497fdccf20ccfbfe0cb24ea8b1eacd1e6d855087370fbc9504432abc66ca1a7ee4c43610cb7fde818d5b3e89d3cbaa208e2f8ad4384039cf75dc8acc89cce7dff2c642e1c2cc6a5d3855d80b985b00fc3b3bfa6fc9659ed2550bfc710378dcfcaa87d2e6e05bb167c9c15cab4ce74a2efae897726d3e77e1f5aeb286c7847c2bcc8c209f5bec584b58a8105ec5c8b52dc480fa911fde2147133a248c0e68411248cb47cb9a1619063f2ebd7127c1a64b8882e2902288533f17528dcfa109183279e7c767df5142b7e1fe3078830a31672c75b23f609a922cfdd66c9ed733e140b0e00535c26c35be311eba4310e5a26f86cdee0c7878bb1e5944058b31bef053228ec8cfcae6e6280664b1f3186df8b6f269c225df467da93831dec0cebe8fda814abe59a15f6072a3c7ed10f0566514724a7b26b430dc0ad8f1426924a37a68cccaf5b19c9e40910c744bba79c6d0d679800b5b28e5b0a16503c11faeec61501764623daf8b3856f7562ee11ed85387dfcc898fa0f324b44df7ae768872690e65f35d3ff4d08e1a7e7df9953b99bc7b8a05af70efaf3786c808a08b2775ab82cd3123c25fc29abee81a96645a1e1039d5275a7c4b523ec0a464f1ab9b85b4fb827e530f53974bf82687f2c0ce1d04a6c8ef0e82e218602588704209163e04117e0f72b817f8a3f1d5db7e8380f5830d9be770b32c4b189a200cf57d9f0d8f4dda08672f835f3637eb17b075fa4a2bf11c26c9446121b77affdbb26133e53faa2d32ffee44d7aff65edb80ee0d85cfabfd5d8a9daebce041192be36db0686fd864e06a33a1234c8d6fddeb7e9d36f6e31173c218188f0a7f23d537b25162715cee31bc429a895d66583974ae60325ace0586aa551bcc021d4bf92299a19b0a783eea6d45caf8c829968e9ec7b2676b40254a1a187fbd66f8fce95cfc18ebf1e4f510daae6f88883a12bb73b4bed8ca92238411b9ed212bc6baeb5e49dc39535f35f8c1356ee52a85cfa600eb99f2ce530dc430273d0d174a36dae8b548815200e874c95c46854e9ef231d669205002cacdc73f77861c47dd2cb87e84434481680ee3f88a45302c313fd78bc4a378a546f276a8046f32ab44bd83e6587dd46aba54073bb470bb0cbe64fe3740bf7b28489513f721455a85d73166ab9c1bc808657dfee191eb98cda8b44af8ffe263b46c9b0f6059d2e2eef5787b9df379604dbe6a32206bde3b5f971b6c6e416859a234acf0393d053c51cfc5fa4fc0f5469d6dd1bb8f6d1c2b492078193fa940f8009590d07bc8d4a4ee78137f98b8f5f051a6ddaeaa950c0424c9a8e4949f92ca135ae152454fff405854848b138a9f30363255905227979d1d55eb899abcd412cf592f5c59ff86c0f8c5d72c9e8eedf30b4c00984ab619f17763673503453a1061760f729df00d5d873b70c6df9016f8064a6374bbbea36d075efefa82b682b0a670b46e9cc573939882a3aebaa07943501813fbb0cdfddfb6b7ba6552647e32b18a001e03dc56aa0c6f4f3bdcf77f8c84832e909b393d28037b7895b023da9449fa386d240d5bc89bd1ec4434ee26503453c616cf6ae68b7e28a1da5c0804cd682bc51176fe571752be9af374fe233f64343eeb03b4df914ee514211d313baabdd2a3ee1202d1330f23dcde94dad4d40d74f0bfd5bab6fbacf99ac43aa41cf92bd07f0b388758f207ae012650906d5f9c899e334570fa9d669f739a013c3ff448e18ea3c81ac15641047a2ca5bd167d707178a03699ab8f5d20e51443a3b04edf23a4adcaf73d731948821258d5ed155fd7c4f1f0e605670745de66d661652d6e682042e727d5c7beeff0d76f73192731117445a19f2300ed12087712f9ed34a954aa6eed33c5a4b3eb1454b5c1634724e927bed7d63d982edbe04255100369f555d85a4e64c004521de3d23aec0caa66387a3107368bdefb98ba0b7fb1bafbed63361ca1911f4e441dec34d1a5c7889e518c4d0a55fff919a0b64876957852e192513b20856e6c0a52d4e491bc114a643b29689ba26c69629b18e2a480eb765276b3b629e9f3bd785648d255be5a6dc5ba0469dadcca1144db8aab74afa9e27853f743bec9166334b0a7e73cadaa42c02de127cb19a4c440f42d6cd8523e14bc2363aa04a5c3973bd6df1c29c4bab7cb35a626d85fea066cad8d04c61cbaa43e67ae55c70b33e15d87315f444bb2104e32b2c64bceb7e6f23ed56bb6dc18a550c7e0aaf5d1b304bc70da02777cf936268bf0f7d555bb3c8fe0dc4e262c0ee3f888723c1fcf5aee5124dd58f177d46424cb7ee70a0154cc15a8ea14162b0b5572cf18b6fb2ebae8675f6150a748b8588c8aea62e2cc9c53e9b740ef8d4c8f55acaee73be45102c91ea166c0cef88be79729527c3a11ebc285bdd26112331686c90db403583d2efde910c31362b647b9f15af27a51798181f7bfbc64339d342a9f59df22799c1dbc10b5a896a307a4373b7eea4a4b8c79db603e55d70162b68b3907f7e58d50bef12ff92a92930fcb3a8aca416eb2c52573779ef47e927dcf8055e6d18a7e91a7e3b5847eb779e23dcce64877e8f03d6ed604ebcb347e2db27de64315e15b9ec8f46661ed1960ea5f6ccc9e26bdd5e2e92665e26437631d1cb393dc4b28d5fd0c61da71b91b985b5aa3ada88b611740b6190c55a5cd62a3ac9b369ff87dc34e1844fc37673f34b9494a6de4eeefcdd74f72fed36f8a9f0c16025f3f4f519ea87bc08beb2ac48b69e7e5e4ab4403f40aed8155af3b89c4f97bfacf977c9d068c06a79ce07151b85a21a0603f89e0eb911d23c0443eed7ecb9081fddca81e3b631590163ec2ba1ab751ea21a0a3faf684b01ffe9dffc5ff1e4474949f1d51a492fff234046b12d4a90896dc3ad9ecbef422ed675f743d7b5a145bf39e39cd4c19076a0763bed64e339a3e39621cbfce1219326306f79cab495514ac6b1c7e60c16bfbf260d002b20de2315599bfa618eaa395f077ba474dcafdc005da3344b2e083f22cd1f33070a1efa241ae577d6fa755ef20df8f42a188ce777a4415d039a9fc8d419a6fcaba098cd581a4d6d60b72660d98db1942edcbe32a399e4ce58f09a48a924c26750929cc1d8d6f29f708d46bec1300c781fb5204dca4550bc0711d5e5cd0082a8d96cd2003537de2e6e6b2646adda02dd796403fb4d306e10b432e28b985b6767de2e59692b310bb8d0ad81b3559f2a28e1018abd30ffee7b48a434bf48b8a4c382ec539f0026cf5083c0923f27c77279fbfc957b6588a0fe06f43208a92dde4e40478c98388cbe96a28b1e8644fc002ad7ccfc5dbe01f6e8243a307279f527b15ac2837b1023048b0dd17aaa57078a823225974f68e4ac562f5a77666874de57db79399e920f9d8fe502b445291c5c068158958497c70a1cb9e363c8178b93130798c8b050850c630dad72400f8f56dcbb8c04f017a64bfd7f6345f24711f0bda86912646d161b846b6f0de1cfe7e295cf6fcde4888c8a10ddb7938b4c50cd65a3c710b0e096a4e9a84a822d9904bd24aba42d18c604080f2d14daa519dc9c54f39fe2a60308dbe91c9991cb0b15ac6c1761b87e8e973fa577d7b9711725f61e0e1d340763b83a6246d5f7d9bf6ad2176a02109f9c5e1386b29f0fbf897ef322bd92a324d111257b93bbdab0a08887c4e319d2f66c48165f744ec487aad599b2f8fbdc2d9115824c16458e34126fbc292243071496f37a6e0dfc3e6622c3204f7afbc51b48be68401fcff41dca0c7ff10fd922a3a7e140398302ffe61e108ea0cafd3906e42ac09a77c8505ca5d8a34843f61712f626be22b132beb936aff780a443d6772f1af78163f257c4a563b9953f5f5c397bcb104a48469fbc7bcafb517ebe7723dc29a94ef057f690f19fcdc712a5828ffe4322a8b26525a889e626cc961aad908d8125bdc721ee2ed473642bd71378c5c6392cce3bf924c462b9660b02b885c71c7e5c3969093df3e160ab0c25df877a7791e9648b93f1d6f54d476430f06df7f16034b06491b0f58cf0c132f0ad166a798f280a4a3845e985cd9d28b998ed486c446cf75eed79c535157a78b93f55506e6e9c7c1aee45f645a0a241f98e645a5c01390a9b399fdb38ce4051b99d45c90bf6113ea310bfc39ba3b9a819eb1606470be530c3037f94e07400fcec363603231cc8bf5262c206dbcc73cfe8306c3d02bc70042e17577515b840d51387c33ba7f12bc71aaf4aefff4f49d61e213f5a4bfe4332da782d11bb6a9a7222647b6929fc76dfc810ba1b4c8ac24c89ad4c296ecde9cdc46faa92c5924807f3fbac6b6bc2ae9898f2185c9f58166de4ed4679de6382ce4e5818d4e1ad398ae372414deb4f942116092c14c0b074c87e603bf12d7ab5b5d90efddc2e33dd6e347c927779a13a204a19439b1ec74f9d80c9ee8e55a711b29b902ca3231243c7fad8f345a35eba60ea4fa37f251a1b5c6b01b18b2015e2e15b3ac53c5965bbde82abc7f4080c95ec9e082c1544545499b85387c70d1cbfe3536e9a6d737feb8a982be74f428e06c866fb8b095a893909c810773a8c154361dde26437b4ee97eae000eb6de112c53cb8f633d1ead1f39ce61177c753f5da294ac3fb90ba98b75cbef15bd0cd22705d8228ad1aa5c13e6ce22a79a76aa6e2f2c830100e42904bbba2232db0d344e2e692d004eaa11b6343889ea19bfd8610539ac46a17944905bc86dbbad0bb66cd6ea5c88207efb425bbf9c5ee52c92590edb539e41ea844a774c292457530cc64d7b7d84cdad9e0f5c91272ee71c148d723d078544560639f1e3b4240f1d9c048b710136cf125102070b39a61e528ee2d28d3e2d8f39fcab8111eff7dbbffffc64957e8eecfbc15843faa4ba6008a5594c15ced7ab512bec4592bea786a7864052db2f9069496cbe5b0b809af497f2458e7576bccf866e39273a8b691c53b37f9840723eb38d27b346e2a8547ec382598d4b538285bb3119b39dc40a8f5abbbc1558491e6950994125a53b8ce9c5547f9d606b48fe5cf6f43e28596d46fa0b4e0d33da46e6abbd05726a1c3d467ade85fc21263e4b0cd408717e3f14b5ebc0b0a294ef01ac9f64839a127390c9838999174ecfcb01bff3a18836efbf41622d2becc977fc4f9cb1ccfc912bc16430308f15a6174b0c4411adff00e71a18dc593fc7130307d0ff9b7883957110a4eca5fe6ff9934fa92f00955f552876b2167eedd84a4a666d736ee959b93bcac7832c2da510397fdbcf25716398b6952edf8cc66349a5b3715e4ae3723ebf594913fec74cd3e7facbf0fb83c1791c8dbfa6a3ceddb1934d7feba372db48f76d3e8dc86e1985dc56e1e68e0936f9e5abcebb9c2f70dc37584f70bf221436242e4ce1290e911c3d238d8a0156a91b247a9dc881bbca10d54dff613b84ea87487e1c68370a86859104129008448bdaedc47ac0113ff5b4581f506287038d35aa5387f9fb389b18465cc4eb6ef3bd40d9badcb43da408c5cfe4785c650fd3f15619e229f0ac5cb02a8187c191d7a9f98618f223aee4f9baec1d6f1a6e8ff31a9b242787884fb78bf28789b4e11555d015657c323ccce0088482c6358daa23958b39e752a74bd99cb69b7f4a653498bdaa3e05eb788b646b11638ac981ddd7c67e1f37222648c5fa73e6e47b3518ca2df0b6c00c231a38a53399b2d46061a14a8f450b01517222eb8809fe7801547d1792bfae2cb569d0a72a0816cf0fe2e1af1d77b3326a5813613926a32188c62856c3a6cdebd767bec85eadfc8cca84cfd04ca0686d24941ddd307612ac2289d57cb7a36fcde9d05666d840b32fa23e83d982a21e7b28456c51f6734d7d372e2cfe287305c97dd29149c665c28bfa7a0b19ea380c7e935bf70c7e93c243c64f7b18d203f00ada7d4c26fb6aab1f0a12bd1af4ec9ab376b259b79b8a45fbdf6d916df6322b787a0a56d66c9b4c705c8f3c53d31ab17455fa5f168529989f291d848fdb98e3708ce8f24eb41e4f6252c8f9e1fd05885ea9739aad59c305f95d85be14486549fe72a3bfdb916940f29ec21785affd7f035e797b479626d31cc82ff1f60c4c321044941ddd643e397fc7eafae26791a43f55dbcc1ea02585f1cd25bd1977815caf4c649323b53d34b6ab9af7b968d5d250e30886ae40af3f45615f34d7dec66411885fcb04ad06760ef8efda5b39b7b31e510b4d252277bfca8e8189591a87c14b2c9719729be7a8b1b1f247182662fcb25a502c19552eb09ff0ec885a090355fb1dd4b3ab701e8e9caf0b23debeb3a6beaf44475fcf4b61d53b1cb59a0c8e6896765905a742a966bf1dcb856953c0132d76be97dae7641bd3b352200388da22470777c8e1cca675e7fbfef4c87d674a32c737feabd915984fb29a5dee6556cb1cd1ebcf26ed463e459d19d3e197f8c3f52ed92e6d467a5e0d2514238692976c1a8b18aabc254b5f7529e848e141e778f72c544507b135067c1093777c7fd8a729200288911b172e0c1c991e34dc8f1f91b68d0945020ca3de068e85a8a184cb402f24123b195173c6c6123fb5110ccf81b4182f78070adb9db15c6d38f1d47e1b8a13150c8e12ebabc999df3f4a17784683801968ee3d69fa50c6b03a6d7c94bdb7f02f158ba86aa29ec785b73803e61d7463ef47cdff0e6b34e0113b9664d69772aaeea047e4cd2a10d165e54bbc39fb4b5d06a11bca358f423cff8ff179dc8e2910fa9e9bfb0d43be1cd33f3dde1056c0c1f1f534a2c3c57467e7be868840547fb4477d87f923b39d29bdcd88d8cabb0127be61d810e42558b80d0f6428c17ac99f7321abbe985c75c572ddeb229e412ec0239eacaac7cd8f6dbafb1bba256e94d2ad0fcf91e47e5f932533e9816f7bdc87b9aab7289c394f1ce0b74073f5ff1d98e54f5b4000454de2e7a8495b9d67933a73c6e7103f9be26e905eed7a538ad7a81c4ddfcec95a82d31ef7971a31d45021f48b8aee6cf92533dabc3e1692c2e91c0cbe8db91ebb16d1a281a2a5281e04ac80c8d51a6f330ab9eadad5e44e35ad2e85a1bac4cd6173540893fce0c9cf12dc981461158ae416818c8b562d2a0e33cd377c615dc20edbfcca647d44328fd57e854ca86cae88e33a5d52ec396b3dbbfec8509f890bd2dff554b0c17f9402183862c68c362e329f1355e2c2c423463f13633e3869a0aae9ad1c4aa52ed6cb9c5c6e8ad11879968c54794f8b74a4ffe7a6294d1146041cb3dc5275cd9d7f81959caee19ee120367e51d4cffb68545d92743bb3b4342c41ced9da33219f2f972d7e294600b0a8ea60a70c914cd73de054f82bc8cf44488d1d4387d34c6e057c00a8481f68ace032afca35b9bab6e1adf2e11ef0b541521d57f3d45e3ca5b2d19ffb28bc2851d02cfde5b322077d24686857ef02db02843788489e64a54027830d80e3acf942714a6f084dc4f8ea65ca63070d175c4c175890ba80bf7eb409ae451c19f377b9bc7440a4c1ffd5e815c0c80bf922763ff1f4a2c1eea282106dc6b2214b81a377cd7e3c5d8642ed7258212b9c8c9b0b5b1bf4c70c2839af347718b4a6ca27615418814627797d10968a8259907310210c2cab5908928acb15e9cf22b1485df852fdf2d2b4abf9137cba8bceb284cb62ce002088b42a3cda11bbf9c3305148954b331f6b359df95088641d3fe3e1a29ddc6b2dd8cb0ff0a9543f88d4c93a0ec723d0dd2e647d672384072012090a814371b720a13954b8474c7c3e856f55b30497ef544de6528a6edb613ce1d546a1eb96205f8ee09d19862db18b7cce61c7238f3adf8ef91e5d16e969e2fb572022ce88aba41fb145907e8836cc50879bb0e10cd11dca7258ac835353f5e35cc05a4ba3f3772710f4670bed2c190f39e0a65ca058a96e0c588f2479332bcfa36f5364e4d8804e8df3fa4a9fabccb85bbd579a7131ccfd04902ce9ec16af2ae096dc40fc2c8a8fe9b31b205c6124c2ea6ebb03d9132e8b0da9f8c7029adc128ced02a7edc118bb6d3780b32dd9f894df18dae387985f4488dec9386c56fcdb8ac00a893e7cacf5906484ee4fd5530a7cc51fea406dc2e316342ab8e4b7c08a9b7aaf77b84af235ef757d022572ccf764f264ab2038613b795e4c49ee09e35a9bd81f28b92568c58f0226e03903f49d8cb60020766be8b2bb79e8f09dd0a83b987312f6127836c77b585fbf5adcca36edca10242b8c17c1b5e60e3578be494eb38a595e7d934caca423a29cb778286f1300a3f8bcacb075d8e9b0a75dd9266ad3369084951220be1a9bfdb518b23ee878ea92bfc41e22f14ce5375687a0839a2772d6ac6e4ac99793ae710d23fae3b5aecfbd01109de4ea624805579345fa9d799d14c2b22d99a840eec5ecacc71ce0571ea62c4970ce294f8e1e44a14a3465506dead35243afb3f526bc5c22aa70e4f3fabe0e30506a0d3ae699b97e178998e2684092c79288529feca2adf9b4f33379ab5f5c6203b7069e5e99d8e3570412d9646ed4e1a7fa6e098dd9f1373591b2bfebcd2fb4ff921bf6adc3c6bf34fe61e1a30c142b1090c8f524cc8105e31038bb74e582188d7d65bcf2322c95badf481087d0f0d5f3c57382d14b76520184128640a2b5192d4592b804c3b6e6fee53c14f848aa3eac3c57e037ec308b1d44823b553a0de248b1639e318297b3d6b6ea37f7e9a8fb7987b059dd3cd35c61713ba2208331d467332efc7ec804043062af34f799f47dbce98ea8094ac589a8eae7a6ae15afb370325c79cb22b48560fb1ed5f0debc414dfa539d2dce32cecc0da1a64404755db790ec6469d06738fb4d7e6758dcd38ee0d7381508e288d4d92a7b892bfb5810d9a3ef930c0b5c2aff514a005f8e2ec98d53c74003c7fb391b79faacb2559045a9f30a9025b2d94471ebaf8c29126951e138623a849c02a33689959eb5073a19b1bbf715043ca628b33111b5e75540f519a5bbec266ed8dfc3a339a62e3bd542035904e45a00921a29f3868ff43e16f073315a49200efc201540c820a028c59d668492d8fe08edff006a59226b2f717ba38e1bcf409cb13b21c395aa5e00d44119779760fb960c6b2c1f89b6d8d77befe011ad60516f7ffec102e91906b2ac2baede1d365673c9b7dfc847faf66c8373289e146bc453b095b89d8249a3fe5e75c7d2c06e1cf40138288a7cf397ac7a3e8f46275cf5b7d4ff29150fb35c89de1eafd534da925714706b5940c22d987767d0001269706b3d9285b8b5d9a38984661047bdb0a3b86763e17b6433986e334673c1d6a4fc0b848d07e18d8439598cb15396b691e368e5f05bf4e922223a8e632d65c466621802c249ae23c98e5797045ff00bd2c7ed7a3fed7b87aed83a08fa61dd060128e658449471f9e54c83280cbd1abe1ae837973a3f1b2738a6904806e12f16c7be64e831e03902ed0dba28b56cac655765ecb11eaec36938dbcb99e747fc4564daad5985be7e273e90c56355dbdaaaf4cfc1c21578571fe10e766890a5633682ecdf16fb94bcc5d1ee3c0964cb9fca6e2703c7e39368d08f8138c846446160bffea4d005e3f02d53a8c72dc229131730cd4440a3009636106b00eef4badd8ff56415f8d28049230b7f7f4993137276bf8438b291a0d7c0aede64b409a80763d0015b465f0a06609a7b05b9a58e72e23edb5628ecf038111b2f9d1ae9b7c7cfb31797359be87e10849c5d3b6bb631e73990c5aedb307b7f1afc89c1ecd8779bf2df11849faa815588fccb75815f4027c4d97bd571c9d7950a54ae6ef4be5fe35462a8b6f1a0b6c2ce79bd6fca7310d9e79d2633b508a4913393798d8618db97ee46c293d8ec8fe0e0273d1515894b4564fbb7dfbd81e7e7eaeaedab801778f9459cd6a5c07556ac741697b655746949bcbbdef9e1a30ebc85ba82fb572fcf21cacbabbcb0764517073790886edb33d79488db20f25ac36b1b241dd3e92a4f7c99360c6bce954985466b0a218807c27463fe4afa85ca9d0418871c2cdcfbba4998349e88f2b7e44f4d0603fff979debb6cf684b05876cfc89ecac3258ee5a01bd76febaf4d02ec41ac4a9902c305b6d812a2226fb56923aebca1bb7f98e5a45a49d668c01cbc511c45af31ed916f260e745116e8f32faee26a330522de7b12c1cb9b7d5d6d02f115f5f4d9e85dd6003259ff80d004b0504061d077982c34cce17a061d6df27f5d540f57c9018f661a2d74fc1a610e3dc1ddb8a0cf8d6c93599d59ff839ce89a0d76a76037a6b619147dee5396d7186e267f5e736655624a86ac47a110d80acbd10cac30cb2cd88458b447c3daf4ca8e6f205519eb66fecfbf1b4840f7d59609701ad136944c41f7ecf8835f5c7d36bb7e6fc8f6ced93dc520c817a3cd8a618d66994ab3d4a0f26b5982aa4117bbb4f8e84eefb3aced9ac4e3e8136982b2fb444df55683e52a980f191f94d4ebfbd2dd8855be379e840d7fc98170d65b4de6c0098f990422ce115dbbb2099e300e4f9b604e6a5653a59b48d8f1be80061cd77f0d3c45191ad38b121cb0329d4fca237850b7baddbdf87976bd65c49591280dcf22c416973f672781dea253982e87a512f7432ce9524dbc300363a0b6733bb5b0e0641fabe31833f9a6a6dcf3f2a2aef8a859e3a7341596993f4143a158655e7aba4c5ffc9f5bf35463efc7ec6afddb68cd936efe12e72a6c5cab342285797e1dc42230973608fbe351414850077dd94e164159ae34fdee741821aa08148d064cb07e7feb8adc13395c76c70ada8bbeccf9e48dede5b49785ee76ed162c7e07eb416cd96795f5b69cc9fb34287f947ae38bb388977c109cc69f5213299469668566ed427b1a5a15264353a41f2f89510c57195a623d354588b9b1010cb833a1b07375e4077d71b0bca1933b847a6068d51a3479c1b0ef4ef59063c6be4503a3958d6e84903b87828404e47175589161d367586660612b1849598a3d1808c2a80f11b4d1aa864cb7763c059e684299097ae88a1d5f917c636b2b1e080b89a23513f8187e80e654f428b0c7d425117a13aec60659ba2e97a713106cd2015394174f93de286914973105798f17123039e52106baa38932d7efd55f93ea0d5ed07b39a03e19e704f12174a1d7d220855f74cfdbc83bfda1ba4cbb160627e2525113fbb6e6c8909da32bbc19acac061d7932210308c5459b61d4470b0d1dd4d25647ea47763eb1b9d0f0962f31a3874498099cd183cbcf331433105343a75de1e37d88e40238a9ef0168bf121c7bfa38d2681c52f9157d70bccb408119bcd8e03e6e335c94255b00a284fa36347e5b03bf60f8005cc8c54b6868b8d552946f517285fc48a8f32bf9826f5c32d9898862fccf98da5356eb90c87a98e585217ba6cf5a5805afa562d8574fcc0c7c6d80e2ac9f5b4eb845a74eb5ec6cc09fd4c913683498a78cc0f0f238a108d42299bf5ff20fd14f1ac1ffc6a7804d175eb48829939b5f8c1a85ed712121aeade51b143f7814a6303cfa2b0d3dc29a75d6c3c8726d01882da3fa4dfd7242038f73f28293ebc0cd25945cb2f5b986382df0b8da0f5473247f6ac9a31070388a388da518457cc5bb5acd8f2a9881fd6732e602fb726b5f97d023e5ec85c38e9b118938bf0c60fbc13d454a4d65a050a3c5c92008fb1969ec3b7607be99f7518b06d16773c25101e34090f33bbf42d51c74fb473d258a4de8017ffa510bfbc71a9a6237730c0ce8aba2f3162a153bccc3a9b55a2b742cd880cd51b34702a4b838f14ed13ff7d1eee6f2eaa5d2be81b2f0c9822e6af734e7aebd6e3c19132b4652be3ab48c5fe64f39efa20a6337ae211b86c222bc941780cc4050709512ab55b697e1317660141dfe037f0ac6e27f403bf429079ed011f4f040e483e667a487cd728f3d9929282cf0359878783b57c3b8b83d527dd9a114fddc0a61ef337bc028b4a68ae6b97313757936b85122bd7c0972dba70e060b7849ee16d109f4f740a8821cfb4afd4b369b84590fea0cd97d6176b2af7eccd5cd28e752776f3dc64e3dd2fb5d87b488e11cf933c988fafbc616dec63b77754a43b02a7a9bd7cdf66c08e9e17dd15f0878fa5baa28c46a14889ac7efbe3273faf777da3ec413757c24d6f77e7f181e2baf7db0da2a3d0c3cf129bc571318523882453bc321b54ab05c3cbd1890061457dbb7b49119217d2a93981e009198ad9b83d23d23c8043acf43da1009e41977754ccdadd2ed5a42bf4fd6973c744a717d4d216de8b9d82d78c77ca240056fca72ba9f6a53829e0d7bda1287339479f37f95118095c20f0d309d292fbc711634bfc1a9a3fe95b78506c9a0956069a823033a27101d21be47deda52ec158a0f13d26f1d27739ec607a933fab01464bb1cb016b5836a0255ec4f1fb5eff3cf4227a3df9750856a5543d2e588d7f292c8e7f2e8dd0dd7cb4e1bb34aa2e8947415ae30e6fb23b9946d26941e14577199c52178441dc3b6352a371ec7ba954a3770cff9ce4503cac51a4a24e1f5555ef68d83b4974a25cc35be5d08615700fb93539884286a5f27c702f35dd579c2579afb005e404836d0b9308fceaf25ada0415389daa091f47365fdc43db8070e50ce0b51f2afd014ef311a662e0eb99fc600937008f2431a0523d67b665cfb6b126a4a362da013634beb9f6bcf22124510ba1ee0a0de537066baa1569e3a4ca6b75afdcb9457153b773a8fe1b7f85e17b2ecb712353e9eb9d57051664f9266fc3079ca71edfd0141fd75c353d3a77513f86c727ae219ee05c6386e9b081ad8eabfb0d1acc300e777d8c06061728dcbe6bd72ba85c4d897645ea42542fa3fb22c7d75757ae68784d9faa4adf5ca14eb12f4f79d607b7c9d39f9a712977bfac3c1d58a4b1ee9f92856c77d9597720faa2123677399aded6df7a52c6c4286280d47071af069a390c8cd98b21d8ee69da26be39937998358916fa47283d23a820d79a618b5daaefdc2c73ac1575707a8b0e061273dd12b99d424ee43bcc15719d1491aff67aeeb1e092b6c189a5698e575b8e002075954b8c509acf6682fc469dc8e1fcfa73dfcc294fc65c209aec27626ef356a6878b57793debe3f34360bfa59ed456a857fc97d64364bdfbeee910c20f94a24444386eea1f137d502b6db8b2b556e386897fc8c6d1dbcef0daa74a10d269b0f85fd0436665edbe0767a86dcc00d521e71b385cc351949efbff5c1c199c2bc002a77f2255b06a8427fe280f5d511fec3d54e16bb07f4f63e43a42e64f125d1f07ef1132121684aa39c90e60c7b39502bee20a78ef296bc5f1f495952997e147cdd67b1f11a29a556c636f4f5077c7d96ee9ef35808c9087d35214221586052b08e4719b6199e4f0e19448e21dd658cbe87fbfa3b8c3539c7979dccb936cb6e2ba8bb1ddde9df125cb915b82e14b8d959e7fc7dae0201259e019f3f71d9a1dd89feb29032d3006f04944f1e0b820a93f2d2d685d05581948b3a67c9b1b34b292587afcdfb5dc81621e62c8da3ac323d0dd773ddcf7f41c40deee4f832d88d6f22c33f3124eff27013b7eb78249624319fc43d935c6d55e431c24541c2677be0d00dbc9d790d531547aef174ca7ffc18d5f6827b2d0a2799feb6a97d9be0bc4358e882f5f8615a56c4eeebbc4206e373291b0bac94a9517bae186a6751d05371b79c78a32ac580b492e2f6ad9b398716cb754b01243dd599ba8ea51f46aa6ed812d2ff148febb766eb1fce4bc592fca753dcef99610be7f1b411aaa9b3c4d6f952ecf93a1b839ad83bd21fbba1e8e423cee01a578cd8e9f9f2d1646f44be00eb12519f08c5d34d26f85062c7a5c6f05a0de7d46270b3a319085937bb3bcecdd5deeb28f91f351da53dcef55d09c7ea6e521e9fa64bd89b2e1070703e2ec19a77c28b558a09ebae5b4547ef1088d2b45843990452c25ba709ca942e8c4aa44fa227f4cebc653b5282a620a596b167d86331fe5058e38f0fee8107a19d3161c1bb04bb5393ebce4e096918f948ca4b8dafaf8949f13c556916ccbb734e5fccdc3d5b4e549901af4d13e87766a53b4764af795de21601ca282d0fb6b1de22e8c03406bb625baa72ec7584af0ecf94cf15aba9556f083a0939d4de9f9072275e24c2d80e6eeb806c4b3c27c6590c1bc14e3f3fdebcc80eec6320269d67918c71209d0c70435cc5ebef2594464346ea89b7daf582d29f17ff211922ac8aa88eebc18f0044384a79e2a3248c94178ca568dc406b33de98c21d7f3c2c1ae387bfc48ef546ff314f8c8214debfd3388fb7a00f099675ec01c8651cfa54d7e4e1ce8c85410479798f18a4d12e92ca7d2523c6d8208c009feeae74fc37ceb2efd0e2d2f9789ddbe82daf06f8a4aa6ae0cae3820644997908b66121b3cb25ca4250801017b5b68c0cd3b4bf400158e97c1ade77028aa54d3174d223c60450b567c003d9c1a481a6c02fce6768bcc2915558601b99d9ebe08e87af5c882382396264a94f05085cdcad705cd9e080efbaeaeb32b7016c008fe31228af7ddadc8c00e525263935bee4c9cd099288ab918b79db38e894542735937dc09e61d4e6745fcfcea5929679c2a8fe6509b3d1ad759f58a0414af4fa4075f236233f2dd7c914115385e1284787fdc1928e25380c943334a3f5c783627d92e17764a4f6af287a81038dcffda5692256007fcc597b536264d7a5898dd8170a7ca250e1b328de4056ab4b18bb0948e1043ae8d465e2117b820fbd657301988e3b1d3e6cdc6854cfacf85dd41a59f3fe92c2e686be239d6a4ecbf482e691c3885aba0bfc5f928de703750a226abcda3cce0054b579cfbb902938e8a17085a8f356ec70a31c1c75bdc00d342b0abcf29d0a3c95d7e3a5297a98c1d678eba44772740a703d68fc11f3ab2ce5c6f2aef1bc9aa89deaf9e9b10bc731f8c1075d99f43aa51cc78ba3e3e77060f5ba2a793beb57847506ee8351d603620487c9e4e67ef4ea27d742c6f3671e958bf0519a16a11f43d82da9f38a9f3f7bd02606b4b14b301ce53f3df8660eabdc7ec4178c048152cbbb3d441356a490c6238cec49a38d4210b80ca216969e6dc7d7fac37057d157c2d20a8ea955c0b10d260f7a433f8cc1c91f788882fa2da24cf4c2ad115fd815e95bab8acc3c6e808b098503c251b3081bcf2b7d2b7738e94210a8c52dc7f87a88d0a3ee138e897f6cd213b0215472b65a8518016ac89fc76834400b1801c5f8ed432da59b1ed554df5d7afaff41762e28997709aa242624fdcde914a6d28103f885144ed6068d49b50a9f339298dd184067147de5572bcbe770ee8c6e64b994b25ac1ba2febb5cc92a561fe4b7a83de6017d0b9eee352ed7186efae576991a90359cd717fc66ef0af7adfd3d53064303f696e108bef409e8eea97ce753990c26742322e4ba045eb28af927d53876a818a7b1e445d0ecb57b3b92bd039fa5d66bee57f5fd9d87296de43f89bfab6aa8701bfde10a02bd17340d1b5adef2dd89c26dfb9a613f5e9f0e663b222f9df2cbf9687cf9eb02172b279c440f58c447f72ebc6755a3914629b8de06ed9ab48eee6a4edc31169c876b80e48bf48cf07df7a80a192076279d35ee4fc33db24ddad8133f1e4fd4a849d8f335606dcaafc13488ea26ae9cfd26aa1e3ea171902a96d564b1107ea79a0e721498d81fa0814f9f50e95b07512299ef41a9cd1d6934550f15ac966d485fd28bf158b1881d644da9f7a7a42d1f89fe288f1c7032953769e20aba6dcae05dfe19d8d46126262e9c1e703331b5754737db476499d7ea86d4e77e3e73dda01e2d9532baaa1220cfa9771e44035168bd90d71c2362c1502745bcd9ed88ab22e660a5dd8f02c5b427f5367b9f8a051fab5b8dfe5538b823aeb99bd0f5bc003f7f3259c65874f3bd1d46ff7efb048fe284552be6bc3e32b4fe87826b5519e8d566971d493740c038198b0d041c2dd47ca2c7df6681697cd66dd4fac1edb788484f7b22cac89a589045e00a9416b310d9099790f1d1e48f8270e6c35c19136126e3bb2b1a8dd11b63782dfbbf7e8cc60cd544ab92221e38e5fc80e6cdb41f2d200fb23d2ec541c09aeb769418e56f408e9fbe84630563848f79af2416ac752574551e3e58d37c913912672ff0d2660ff9fc37d2b68c675313c05bb662d0690a3495309a12b18c0f9ce3997b82a44e1f51771f28339f98f297558e7d4bb2d48b0db77cba2588d1df5987378b71f39d4cbc172c38cd00b7363c9c57e44a8bbbceb134c08e9927f3ed531e369479cd8bf180c43a5271716a8e776d2b0b7b5edcab3af3e1159cc0dcd7b0c161a3bc052bca7e57e6f5c45fff41159ac12ef1b96f7df3b8c8f3b834a955b3a9ea06728b847f70f5ac2d401245f10bf18f7f3db8332350f9659bb6a536e002a8854240b43fdba415b065d1270e36892f79b8f24f189f4142ead68a385d287b5599bd064f73c56198ab96a9ccd2870cd948dd1c6a9e24b67a18ab883d0972281749a4c346c3167df787890f50b6cd54142ad8ebadbeddfb98bdb82f0689796a9f5557cc7cfc93b19d4d40b04a0e615aff3bddbb2c3014994bda782455ffabdc26c51cb17792e54eb61d6372fe0fb0529d73694c2a72e965cbfd9d8cdd94e05260438a43698eeea4b9bf616e09d8a4cce2361d810d2e5c531bbfb89ccf57ff090f46afac9d7a567054982d1913edbe5ce98738a9d7dcc4c96d7e4fd14914892135e96c1980121e988ce8f2f72b6c4a1c26673d215e7e094ea4c867f337ebf164fe0957227f818c3900c241f9206cda34f7d1f9ab8f3eb78b7bfe6c025e52939048c038b649773fc9205b334a6203ce2207710124b2056cac8a9e155a3d256cdf107c50d654e9ace03aee424575252aa2c9e842d1daadacf17dc6f34f63765380303ccfc3084de2868926949229a151b4bc204221e2440be8563153b63ddfbf2ce4d08e39f19142d383db620ca8773a91cdc2beb00ea3144665a90a92209a90b65353b4d459a82b39186afcc568ac79a40b660c970239202da700ba3e8dc55a26df9f44d6dee0dc5ba37ad93a6cd2f8bdb163ebe5549200ef935e6b0fdab776f977786f728a0ff2274acbb608e097dec6d23bb11295f1c6ecd6b9c3ee7cbef3a56661ae46a052abb1b5cb0408ae4f37ffaf8e2ce5952a3a01df0741ba0a15861f1b87ab233e136389f7478e5fdcb88dd7cb20f43c131dd15070468fc60edd456e2064082ce4dd3743ef103260564b9f1af572a85272fbab168fc1bf9f896541f83104a791d0213fd1dd7527dd4f0a87a246e66e9d79ce74d1da5ee0fb67ca8d50cbb693ff643d595a9817587a95baf8da7264d8b4559da3506e1c09ddd06ad25e61d16838e192eb3c628c4884bb51c12f960b1cabf5a48abe550a1e7b48149d2417bdf74f88bd603d70a2de41b588d42d2ff52ca364d2f62090b8297198000310d3e1449af0c6fe28ae43ba7b60bcd2f3c478df00f749203c5addc37aac853ab2e57b5bb3350efe383a068fe22ccf13c363f696a0a426eabbc5687b77e8f4c3e14946886b82ff74cc7684ebc931609a1ac54d2a53359fc6bff4804ed70fe0efd91c890ecde63d4990084a7d6b7dd6ce8beb278d5ebe446c7f14b4510f54d7e8b10e61431832af9f893a84de79a879773560e2270fa7f3638080d333d82f09ca40b5aa820992950cc80af09ad159718f40cdec6b3b218baaa510e48f38a928295e04d98841821522b14d1b08acee21df26238df1c288564fed1eb359605f177bfeca13ab0f93d02606bd5e804ca7bdf4b46d2afee72b9df89ac01dbef84c80618fcbd1cf84120fe69c6e2ef458ba51eace8d73cb004d2e1941ebdc5aae05e92a90791f93620e69e79aad3ef9bc481d21be699c5ae0c64e4da492222d66d695b11445a4f4619cec078d43eb8f997510f6ebb485176ecc5ba0dcf503569c751a8008015278a4442277efb9a3575676e2fd18c38e25dbc41485038117aba261b29bdd2a44066985e9b5b63289fc8355c47443c675af8df6303be33b508054c6a94a19a1160c54644d3133f8a20d19697f81bd03ef9a3aad7a7e29dd433ebf671e99acb7ee99229cce85ae339b7123001c97c29ce9ffd1384743d5cb12aefeec5b8a98da652403f6aa61cc93d1731c9083fe40af2b56a7cda97c4074c01f99b5654642bfd3143375439f85fcead79ecf924a36e28a0908189f85f6773b5d72e17cd7550d3dc950e3de8bfb0d24356170c805b2d0833dae2055d6bba58a7b75910331898119732bc3746facf5e55dd7a18fa85f49676f8759b1039d37283af98e1a2a16b67509ced92f962209eee414c17b7ec8cf7585d2bf8888d01baca360af349f43007cc57d3bf966665d3c15fa5c6d12f712aa5c022633b1fcd86fda8d7d9b00bc949d96efd953fa8a5583533f51c0543b4daaf9ccf11c51d809decc52db149745ed3f4ec4786af80ddc85b6100695aef3771b1af2d4dc6a88130db32c56976d083134364d78e1d7881bd23ad71410e4f33e8b17e0559545280e3ebf6fa51f956a23a566a2502e5ced0c98f83faa54052091940d0404fb2a66c19caff7858084db8fe9a181d7336f5f79d727003f46e3c6f7eeb2f4f6115a04317a09b6ecb099567c9988a47582a14037cfea6ec902825e3a49c6a9a319531a9c531ff5896571f54296b6208386f85a068727cde6be97772c375160b370dbd521b63c45b404470d2a0fb44ea6f7557b3c152ec29b98773d036da089d5ca09c417a8864292d0323e62b8c5bfbe86acdb524e61439892cadbcd898dc034fc49ccd899ec17be20364901714d3394b91ddebce22b60f1d618cb96dfa725f2a367d7f57ee9f1c54c6756e04ca5d108279b265e1d3b75b1a55cb12a3a26b007afe74bbc8b1257e782c2ca82ef7aee2a3772bd23281a3150124fb3357c7b03cbcb6a93ba70361e3a7d3732e55d9ffa98e325921998f28b1848aff4e263fec53dc96b85ad2c5c118880ec749dae98fa1cb8a05f7d42296ddaa2e8e52754d0d64546d342e5f2f9abc86aeba78f7591c038c3427ed888566d6d157b43075dfc76de9882071b163a41724ea729e1ef56ed3d04870a6e63eb04300b65175d822116933c7743ed7ca7b8590532781e2e3cdbe0fc2c1b0fb88b3aa20a2982c35fca569636f9e9bb1e2206d5e01a89800fb8505e750b237c88d516e0a1392194de884d3f93cfed2b3e777c94ac1d72c665b6978b2350c8789ac905f7a5d993cb640709f5e02689e4303c7415e6b28c44a294d8513d44f5de20b1ade5cb7dda99db171af8d97a62104a53dd2e83127e2b9098b53be852d0ecbe8bd880b61e2af9eeb5133495df4b4954b68041a19ef1526e77ff01dca0d757365ec7d6049004ee841201d8479827f9448beb73797b358df360b8ca15089b780163d2cc1fbe2fa2af2b0a5f615f6e1dffa167ab5bc38bc869a14c5c483f0d75030e9b282a908bb084919e10e56e7b074912d70ae295969d9410306252bc5172e0150d2492a458701b33763af6d633f64a821ce838482ee6ce244f135733e2a9c58726c48e14103160f27f46d3e6a39b2f8f8dfa11b36de3674cadd6629c536299776957a8ff7839a45ce544425704e23e31aa2adda8abdf9228963cb437bab6c652734453d3866f43a5213e52edfab46d5d3e99cc1a742f10dfbf798dbb0df1d7c7232189027b53bb39e6f26bd92474467d31d7b9a4be357809c9a0b9a6c866c7ae93df612bc20fba0e24f35c45cfa3998ad7edcc049b39f466e918337eaf0048343180433414488a61145822a7f6a31be5bff416c049378a57635ee1417216a3bd97781f3d73cae46629f9a8235c6f895b8adef14f8adfd403dcfcb2717ac8fd42b78cda6f28fa45ed0925f4a2b4e685e05abf7383249a4f34f836469c16cddf6599a584cd9618c089a6b4d1701b0de1165b47bfdd3e1c52dbf4efeb91e0607c7ab55a68d27fcedf2c3d2c49bcb30fe9f603b67e6909bdff7a21326b1ee3561bc0a38c0b64da4f08e9322883d93a7f480640e242535ffe01a8fb55d2422f79eac8496bc700dc2463414cbde09d447d81a3f910bcd1d0924432646c14563742044cccd3a770056087f96c61030013ce338a6859692bc87e82b8641751c8aca3d1db712f7fb12c0f5b82b1e2ccffc0f9843b2638d9816ca6c02d4daefc0269b37ccc0d2d7ed01d6dc142b7e46d09feb18005e42bb34459efd38bb586f37f96eb78084a416e3e1491ae0b2daacba379d3b5c0a50e583f69f97f3e7812ffb7be8095147c866f89a6071931d24b3eb03742434fbda081becd6f3108919304d56c67cde6e383b9168f8b3d9910b1660b9ea710dd8e921227ca51994d66b9e21e8b58ac5034a8e875bdff43774d720490222437b2f64fcaa1007d0db88ea3317c4f5be3735ddcc03d5793e00f24709fcf92aa33deecad124a41e88a65bd91b3c31c87a30d41dc7e70eb9079d798b3366b1212336ea80b5634c159b5a910a3284a9b506f1d9d3c8719361e1103d435015ac17adfa5ba311d1ac48f56b6fabacbc5c9f99d3dc433b7fc3a115bbb597860e64ced9265e72b6007ec98801be208379aba13f109c9f72a051f8330f1c5d1c24c541538b72493e14102a212e4af323f1adc6cac17b068901c56ac3f2f1474e35b1020e02fb27c80cc39c722966cc4bfbad883754ff156906fb0b95e177314ac87b6163fdc3c94a698d627ef767820f70a9242f89530a6a5aa2d270ce366c7dba6c6856df744f02367889a6d524356bf4d5bc916ac36e1f1ebdbf16f37828a95742a68dc51f77baff21fb0a967d7ae8d9164b32d6445e3ed02655ce67cde86e04acc8cbbdb864f7f33adb09cbd19a2d1788632ec9e953ba88065cfe67870647cdcf06f14597fa3ed50836fd4586dd8787f9c68b346f8b7b1f5ffb869e3f461397c45bcf91f8c10451e5fe8ff584e5c28f6bc505f0b2f150e2821ed8b14e7cbf6217915b507c63fab2bf5e44a1ba1ddffe9670345336a5ff8f992bfe8c5541a2114a190591a6afc6e79752055998357981639e77189f0692681e0a1f90b5b68f5c50ba3ef95f37653f049addb094adf44672bb4f7039594845627921e8185bd03b120882b17374b065adf52d0aa573bb87956d0e9771f3ccd407b778cae6ee6dd2edae57501282d895d2b5fc3ccb8f2474530609d259ff8d4a86754e37d168880b328e2e914f65c823c49838ed85e0d3b366c5b67428cb5e0aab2285314afaa47ca5a20bd4a4e5bb1469f908b959e7a4053deb6471b8a9c21b147e4c11092429ccffc05c3a6df587a6b94d853650a572c07b9b6a040763837ab57f42b389da73cf6d2078b468aa575343f56d6876874eed768f063cb63475ac568e5acb89ba46d00eb5927a3bc6df15a957c0ea5e63f873b898507a8d628a5e130605908c4d71ed785a373beece4a701f5340999d83a95a82f3e891f556982ddb0e51475a5ef05633ce88c06db75ffef0fa50db4bc4846a44694fc97c537029f3c202fd7d5df3393c7ad8ec0718a529f9a5b61185bf3da226430ce455b12d1370a5d7cdc5d5798d840f5cb6f2f682ec2c4d1cdced09c9c0830c154cd5b1dbd1c06873c1c4f07a946ff00b39c420a56f0f3a760d287f27da68251799f7ded41a2205af21530bc9d6566bf40cfb1020b33a059c2f5f9e9e581a2592fed9fb7dd5291f449b0b5a238605c583ce776a1a63dde4b367bff6ef048d0cec9c3d853e543dbe0e39cd9d27c13d139f2dfbfffc6d374b4966f9a361b0ed48e94c09c2d81619535f783be815ea33e787e7af6c6bf020d528cfc2daa9e319863240c763836c4e0e174a3c7098530fc8dcc6a13c879180885b2e54850f862351d2bc26b766f6bf8688d17c7601da6f7d3e243fa53f78d562db20ecef51c8d18ad44c77645fbd23b05617e939b19be1296469decdaf98b237dbd04a8529bf1faa71f630c028488721ccb337fc95e76c35a8c737a8cd363adfbb46349b7301bbce6f746dab50e1c9ab64a95d16f8a4080858aae458eb21a9b40859f34e5b4e88a3a3f16a0497061e408345161d04ec2194506ce72fcd5acc641537d37111b81c11bb7d0aa79acd8c268ee26115c54218b1b17f2431568c830c17a3a885e50f41c0bb4c53fdf5e37184ebd302b980f6c245b285e32c0dfd46a7d2f144c9d158776cdfd5f01f2db3fa329899ffcaa755e742dfcb4f68fc659dcd3acf0255ccd57106b811ae860f1bade552b6237ccc22c46d4f43f9d5ad69a7c396983e476fe194a6c56aff486f27e0a095287edf507589fa954d55de453c94d70cd6da00ca1c7b4fe86e7aee4f203f80d793e26ebf60dcf67daa3bb8fe6b2b10aafebb716dab758057f358cfee554acdd09c689fff275584d8686851869f452b413b4a75052305980928f9044714d465bbd8e985d87500e3e7fbfbc6008e50ecba4df43e118d6106b50a5deaf23b8572c9f4868b7310de4b317ef86db16accbab11baad68c59f44fd5c4ac2c8d18596d4c682132d1c1649bbfb9c86b8c7cd190729c62a59d69dddf5dc3af6bf32e20784619e1e861bbfacb20c5b7de32a08839bd13941194696eaa04c2546a0ce8fc0260d8b2f0348f7d40a8e375961b01aedfdd93bd2bd489ad9f4e972dcff32ee5c27b7ec3506ff5bddebfde965cea6c14ecf9bae06a7746d6edc749f174ad5328f862c306dbfcf800fa513d12cd37422134aa800079b955d61312dafa4ac2c5e404449ab340add27d0d9695c5be4b224e783cb2a60f8205d2c3b138c6c568848c53fdfdc5362faee246cab4f03b8c3ba932366d61e49d489e95c61177df1a74688d6e9c7baa9bd0cfe0a37fb60aa33853b5f28042a578744789167453a8bb2313214abdd905831c594d803da0c59b9e029976215c056a388a5917dc4467aeedfe1821162484aebb8830a1771a847caa807bd2be4ad9e7a75a9edfd6584a81cbec41a3fa6a9607cbd82afdb4f3121b8d290eaf25a9432f495ebf294442a70da574a14d7df8575741bc736f22e1bc303a7174dc4699f136fa70b6d239d7cb2f5e833c01a2e212d0ed164450a6fba2eedd5cf2fe244067fc251fa27eae935e7868d72d3cb97ed49257c110c0f53da5947204ebb270475c7e342e284d5c124f7928840cef57e911a37b08952f0107dbccdd172fc24c5b28f65713a8c7bf9756901073a22825805cfae22badee749e9bcd73579b448dad3f22da793d73b2880851eddef211e46826ae4ee100cc19aa1684bc1807c68573bfb3d4be92b0e3ef72f081d690f4b508975944d17d0f99b77c49ddc582cb23650a31d07583d35dfb4e543dbd45458d8a6f3d1918a25986b4b7e4175be651a44075df1ce68e9c587dc1a782cc07ed2f5dffc7b6c696319976b3da65ebaf1fac460386d9891a7ed56cab97ba8e404af4b5287737601a4caf36364f551d3204ecbc321b848f8457e5618a98246363a4990a7546a7603bd5870f49f8f2a8125ec36bd9afa0590690021be9d729fed30b3a95391ba7b0e246278130e6badb1883842011297bfe66bb9dfd5fbbc5e81ce058d6cbe06e332e967af1f42140174ce3172d349b48ca7d32d68b250f72fb75767e11c646ae3d9bc11f267b6479536201bce5e957f5b984495bf12e46e7e79a9aa6cb5b531815010a31ce2c6867977e9a89234301bd003b0e9e7ce06debb0f1e30452be3bfa7a5b608ff7309717813f9734c4e6334285d7b7ce1e4eb75c2e0119473391341a1b65d26f963418dcc31da03b284e9a3b62cb329cee0bb89e8b371b8bc26e2c3f1e0200df03c37bf0a21a6f59b5117bf46654724fa68ec493c90015b3957ca22e9a7ae3847b2e8ca879be3894544514cf93df71a7231c06563a2de49f09100ba21c8576329d0802b53467c6b8f2e455afa13b10cee94b2ef606a8fa1ebc6274b12621fea9ee2a1f00d6e61b680151cdb47a9b2ed99d17ea9fa87d2f0611575d75ab4107a2554f7222f371c21ff4dad3f2bc68bd8e897b76a671323a0daf4cf02d95475e3a9f38457636de12cec66152e63ae6a08d1ba7f8250469d27102051412404c8bf9fc6a775edd30199574feab407ba2979065bd7b18d8d8ad58b9970405fc3b9df08074c76b387deb577ec0a13f3aba8c3b9f25b6f7ba94d62a467766d58ec04dd2a3c5cca92d138e8f6a7a4132e4eaffc6a16bf84648a113e461850555affbc7a1c2192483b7fdbead419e31fb1d756de86ab0e7aa76db5d648537b28c8c119c1c9b6f905a62d14205102aec583bcfdf899caae5860d2f9b5b72cbee8b4483a92ad591122dd1ca8cc869a4ba7cc927153efc364872271d1a54a98b5ed86d590a6e762f394f393ddc30df28850106b8e2e1f778c239b59287b5f323294d901e49846b842b9ee35029498e84c6a364e6bf86188ba18bf90e5957db05c44ea8a96ba90392304bad117b29906237340939a39593bfbedf0c1ae55e2fddd393a7b89c3474e7b4be8acd80a88888021f51189b14be7049a6590ba4b283141868f25a7ed555fa10eefec288aa1c2394894a61cc1c9261ca4a20205341ad69923ae02b6f90220f92e5a21f2e3e7322567936c8c0d92eae0223f4e1f3b84a925c7c3e5d4c37bcb18736a64e58a8bba6ebad0d0b2fde544c88f33ddde10e38f0dd51ce917a83e6bd576f4a97655aa57cbdd9f8f3a635eb1b424eb7a778671a56535cc3873f05c9f7dac1640b9b8ee7a9e9abd72bcb5d7b8725b1f1360481d8dbdaed4693b6d371348482ddc533f0b909e8aebcdc023db4b4d7f5d086f5c21cc3754ef5e8e9474d1214393479a94559f975576e24cd3fe744145509909577d56bb2545eb8da14b341db91f94d108d87149bbfe75620018ba5fbfaaa8eb083b858f078891682bd595dcd9c5311a05b0902d971d6107f45baf0aaeb9198f324ba4370b66bb814c1dd52e66d7bb3c49a2850d8581e1aad9bb15dbfc1f383f6dc9742a201747d13d53f5ba4603f1650ee3261246055182ea95341a7a2d71d2cb44ffb63d702caf22b628398f6802ba728f667c8924611f803a79ee29e99b0326da3e8206debb162168ec44f87b46304334b74df8c6fbd037e5647959667da3e169cae0656f8f28144f1ac16be646c29b9898f69e584aaa4f22e8c0ca5c7b39aa3057fbf0564bf858b19985930af3e1ab90de9f7cdea2dd3f8a17d1a080f4673e3ec8d64756158a05b7b77a32dc67dbbd0ba1202863d27d25846bc6872ec516e2825969a5393783001f02a7d486e256ec4734df667ca92dac657d924badc65c27dc76d0a95938e83633369affd81880243a2e5564616ecb62f3247a8dfb444cb14acaaa7ee6dc7b4326438a94d6d9f9c5cb49f7687556078d7d9d9fd791a9c54e1f55677bf22e9725d87f60c12b3a6637b9b34a6eef3d0afa571d766b10b0b150785b5c83327fdb63606f2a557690d17ee4bdee4490fa6d43dc6984112577813d0811612f8e4cab5ea2b92480bb9b3e23d3df4654bfb70c3edc6b9f3ac20b9f060d6bbe3a5372296c1a70f5fd660790c9aee977daebe5e7e0f02dab9295f874f45b3cda231eef5b5350ebd241e2f1c559d6171c667927a993630ed7eb15fc8d43b0a5ffe035046881259dfd1b68e662692997b920866c0fc64ab1b1dcf40237a5e1482501695db55a862163a02f4ade82320e1f8ad5cffe58df132aab675e72ce1ca6c5d5d69d06aa06b559fdf70a1191d84e812cc4c8c54e124a6b1b24643f3256cad220a6dec7a24986bbb082368fe6ad4f039e640c448b58a1e7ed14fca11d3fe6ea8acb6ce46db701c4236c40f0b21277e58cdf65e81650e9cd44eff4bebecc6cd06114ce8cbb360c85dac1a4b599f1556a04d6191a02dbc25c3a01afa94b2d03b11c3ac46899738a5e89d8680c8503cac374d2800e3d4480a658038df58216af55981472ea3e3ab86642b7628a1aebc5eaf3d2682d2fd88b0bec144a5a090a8857f8c0185eff7d336f3a0f37bd558bb42546ae8b29e0ff5f71250fdab02cde15af6abb30c3542ac5ed1fc1473391be380ad1d325505fa2f29542e786f6b76306bc0f5a96d27eb95fe15b688134ca9df01bad07a85f487767692ed9c3e9d2af4e8c4e22411eab1d276b831110f6cb3e1eabe966cbc5090310c8aa5a44180325007bafdd8cb23920c201f5203b291b91f2ab9c064a188176bb1b21da1d0fcfdacced2867058ed87cf73b813c921e8a85113c443f7ace05f51f8727ff6195ab73201a3ba06a224240ac5603761ebdfe2350c07c66a9ba0f9b5fe589cda185b9c510feb06abdeeeaa93889691dbe194c50c49fc5539af8c1f8ebe7a8dd9db93983e1c3e5ce5797325a12dbfb3dd4ca12e1a9922b892c3540f76ee5f33531bb1d16e12b4f72bcf19275086fe4a2267b8ba29436ce7d72ee3982b7217e8fc1fac731ec819746bb4d30520c062bef62ca2b507f2fcdc2075b5d20e5b5a875af92410cb3351ae85d43eec76ec664cbc7a72c703b108f4b7caa8bca778deca4b045d51bab0d30cced9a67b1a190e26687b156d74f8e5469a66e49d1f793adb7cc18b96715388068e6832abb8f818cc06678b3332caa432fca4cb15c499fddff2fb874c3c4e605982e9c4305f1b184131916d7e1bdebcc6c4f9212b88e7dc4b853fff7aa9656b3d5a245f0705b75c799cc7965b8fdea4460cb3ca1f830f189c609fb50b0c7556053caa089600000ad08d000987f5b4640ba38bce726dbf774483cc87c6fc0286093f108c918e11c92e5d596a4b34e674038c0d91452596878b417c29dad4e53e6ff1f98ff24e66e103f964b35c3aff7cc77dc45f4c2ca4ed2cc21eda0e33f88e59d2f3d640c5e0b71864cae8237911c3ba6140c58d2a7c01b282e5896d7cdde755d71db003db63f6d83333fa33653f5ef6d6e7497768d30493d019293b57d69d62e8a5e25e5bfbdda00f8a20c9f7587eceb09a8c3955b29f5d50c31ba52ea5bcc162129ec276948537378955452d885a997424ddf00235824f7a9e0b61b182e4e6955c391c404607b0fb98be95880d99ffde7ec2daa5db4d0e196392cf5222213346dbca24322ade8a42fe244e8c0c42865be416bc1a6b2868cc5a76e6cac6ecdd2ffde241a7c4f6160559d51043688f328fb2a3a65d5d9a52884773449266d15e7bd3f3eb5ffd33c0d172808abbaf2b20d11c678011e29aaf149ac9556be059e74a6c2fb5fea76a2f495df7e2831ab2a2aaaeed09fc6eebba8bf80d5ed73f450133fde6da8e9c15dea2e0dc04329581135a9e69a488e02464de14f3909d112e31bdddb4c2f95f4406263aa687e0dd34bd53ef71c3442a184450d3072149c81792e451fbe3905c86a5b22a8558abb0516ebc6493c96297644ef8c0a350a93094ba9b24826587aad49f375515783404011def431d0a369b65da42da7b1e51f672e044123d267668bced3cede6431f56e2adfbe6d3cdbd31da206ea1991c18382d1c390385ffd9a34b275ab540cc97074c60221f738313d0e7d51085ae5d992c7fe147164ece91d182abbf4db3a7c7c98a9a5c672ab8fce45a198e96fd29b3d6e13e2e7b243862d64c1aacd2578f621af2d0d3e67c0f5ce94691a590555a0acad639221dfa2f739f3fd5f7cad3cf1bd0cf0a65432f35bf006825e52dd5cc83ff37aa4d1d0ff6d14aa7c18a667c442eb7941826442dafd229e87b30cf7364ccb9fd4a4b1d7fadebdf9d94e20c3323d63dc4e6db439188e7fe690a3f728869d1792c53ac593da917983188903bd558d4307d797f73370a76067b481df8c8ee4803ddcd759489410cb7db76bcb45997ef87ad04c2ec86189693cf52aabb7b13277190149fcf275a66f3c9009dfdf47f6e42434585945a598836ea181978d598285a424397d7e4c0266fbc8c022ddf84a053ecb75fcd33f82dbd1001727fbd454c05eb39bc9d31332f407aaccc099537549f378a1bfa20ba8ac3b0c81141720155ae63c3e562df28ca881eab7179aeb96106292b38d001164bd495492520c8624adadec5d8b146792320c0d17b3ea4ef20839230677a13313e1993b6329afcaef7bb66f5d429638fc9ca353d07071fe6ec55c4de118beef2d49b82d48fbd57e86aa8135d0819ae283203f9f547cba993a9416600cfbe60dfa315bcaee9ef159a779bec3719e7c18935526959007ce1c13bd88be4f08ecc07e6506548be04548435cca3fea04f10ab8e37f2c90d017c720ad75549f693f830fb183ed6e3a3f7277f51a143aa6c351f5da2d509da87c0858493b2b119b8783de4bac91effffcd7775d60df33691b15ba7f99c9ae9214bc19b770f25c2391b08c715f8b80ddd5aa1f85ecfd0bbf3139cd4b472f951b33597438e0da0117e031d6e3bb003dee611f5311caa9a9f28e0ce2f84e6963a310037f61f8b8d58b35c2bc8a0deda9557f4720a51f708c16cb8da6909def66230d282305f94779e43ce9c66dee84029eecc65b346a404f0bf96111720b47b9bc3ef7cf2240dc01e171c7bff00a96de63484c41f516bcd71cf1ed72fb6c8fd4aaa7ba243bd2599c5a69cafec6952f6ec1747c684e378777db052568f340377eacee071aac3eca5b7de6eb3ac3fe20bafcb7933dfb4ee1bc06a34a2767709b887157d0cb60f90a3cad099fa626efd1f7503b1826ef4f653dc521ec409572d53447a677c76b11e3c093780bf52385e9adb772bbe348e591ae434dc09cfbf52a7d57f6ba801feddab32c65157675633ab51bd8583787fc5fbe14ecf5c365b503b05ee9369f0ab8332395064e3b0a812b1996aa22c463b4092501ba78a0cc88c6832e0de86dfffe661c3b69e06df76611b4c8f0056ec1e6da4a2eb782f0663daa9cc23f594ef08fb3bacad5f41c940542c557afa837ef69ee5bb5240603c9fd3c1d9e6fed847ed23ba814d8ab9e81ee87f73d0a69d5491e53f745f124cabee395274b6ef568e4639c80776a27fdcb277c7bbf25551876689c51aa78dc9b23a196c4acde324738ec7f5b20023d5e5a7cdbb14b5e53b7749a7e3ec1f512d95ecb1161bb268f6269b62202ec377936bc3d6e1ba6465d9472ede33a0cb83d5fe856bc93f26cb93dc2b1b22da61459677855560bc1ec4da5ffe4057de31b78d77c9515b1e64c49a4e85691f48f2e8f6375939cf7e768379ea7cfe98c4aaae4df072def8b7ad20773a1e1f68bf6d59fa7e557b9b386ca8c7563b71b36079d5dc3f8011471a2b39fbe15b31ffb9f3d4cd418da25474b364c7d69d89f639b66d939b091af9ee185b61885e6fc565dc03921bf051fb9449324be3efc33ac709c2c921aa11c4ec4676c8f7dd74f3269b7bbfcaba8132495c5b3cb7881c80f636e48450788cca61e6ac6ffd156c071271cba2af7746071d8028ebd0ffb3d4a875c2978c8eff4fb0a89e0de9c2708b73ef7c89528f47ddeb362f7973711523309d9dbe9e2340c2ce2d89a1122ea6214a4ab23cdc6847f061c67a4327d350d4d71e2a3232339dedc4c29705514a5213c77a4c919c1a5fc5855e896f36585bc65ad0e5ce57b6557e3170fc5a82e08f38d231c32883aacfd82d9057632fe667f36a866c34036c6dc8c658ac2107cc696e869a18f9a7de8acf8513f4fd6799294b2b7fd52f8d31222dc867d96e444771f9b0ec92d5c49dd5758dde55db735932abb59c635b31eb835b914f833e2448212354cf25fdb6dfa5de998b6ecbaf7ddf97966f121d90fcf0170ed8b94d93eca018298bb5a6002e9e09cf0fb3e9ba8186534f11ca142d51792c427d6a4401f7b691180d9e9e48b4c88facc1946ff8c9d335d2ce439af1f469a2c25dd336e55a000fc0b8274cd8e073f980afa4710f788db10ba3e921a73c836b3879f5af38d9bb8ef50ad4e01f95d289ed455b346a4f2c4b54813a3f817e9b2fe13ea087f6acbc6f3d0cac062ee6417f6c46beaab9a9fcdefe6f7d82915ab0090e6cf0bfa14756da5293fa1d3c6d5601c80d5ffe6f7bb9f9cf72cd9e5193a89da73a5c87bd1d536fa2571cfe23281338a02a6bb54368d3b27f45c81c1c764816a3dfa6455583482dd2d34f38e0e9eb51e943c928afe7d56b3ae6695db2d31a499159554b7598d97437f8ae0e165b75906a4f7c8174799c1421f0ded7b8885074128f7e54ad45bbd45badbcac8c8ae4cd3f18c76114ea4918c8c5574c3596d8c85cbd109195488bce7e2630b2aa0dc04f5715cab357a733e3ba8b261d17ca3239b936e64d4350cc151204e8cb00b16425f68f95d4c7243306f0f2b11eacf7bed3067143abd62999e23e462feac0b83fc3331aea7733500f585ed306f629c08a4534649806effce0d15d0fbe9e3fb1e0057b55212e3016f0196796128d27ebdf00cb389fde33983e757c36205078cf444a0c09f96a93f60d106371d03a204f15e962463202cacdbc24dfa96b3b88e1fdd27b883a49f8a80fb42070f3fcf6496453286273cbe3df19dd7ca170b033cf16442f6a780c3ed1c7225e4fd7876dc9c4a25e7d1e022efd5bf57ea1cd8f5eaad799e38d5c8f09c4442b5227c200ace311c3db9321c160cdc83ec34c91d5934fcba0287340623f5b64f124e6c0edc1fff84d87ead364b30d92b25f0754e68096a89fe0df095b8cda5dea719aa8451a8e7532e09d6618633dc9281769f36778120196c5d685bcdee3f395c98d06920c51db4d78806812325a6cb4e77f59aaf062d72ce0ddbbe8e97b403b322f09c44f35b183744977f1e4ac9d287de2f90703147f381295f3c6a31e4d1ca3cdabb47fbade870f1378f3c4a558fbbb92ee30ef0e68758e9c66a3c9e3e8078a22af479debedbc1529f1f21ec9353239b0de06969ab7fb5dcbd85359b36eed47ac28d08de7633e68275f2fc3ab0f46c0fa188bfeebd0bfd9b590d9168a6d1aaed8b2a496f9716d7f689fe688ff9af2d0248c63d1462f5832846a8e08ce641689ff87c8e541811ce2c3c9aec6af43b71bf686a6dead332bec18ff27f9b206a5142d122e09ee5ac4af9c1351dc0de166083e6af09623c8195f871981f8aeb2537fbcb95e7801ef031840969fa80d13843196b641907afab536daf426dafe191b70c2fc4209aefb09e393126112925a50e5f41fc705b6f016f029fc1cb43a400a7e47195d793c176ee9140cf782c93e64a60f917854fad8656203072762fa49a4b752d407e684de6c9725d5d3659a115fac8d486f7f0db751341e4c2f048acb961bc4273257f7497170ac662fab1f25bd610a7aeead6731b25f63840a7bae89b21effe09f31346b5388021dab4c35842be3e71c5da6d3bfc3b543f8f1f079de9c2a657aeec33415bb0a32b4bdbe1480c086f1d2da5a9fdd91595e71884e3034841ba0b97cbc0331a3b4841dd28101a5584fce10ad5a5c215642774a24c33b483ec1bcdcdaaf05c110566208fd9305e8f77943f27c5d305ccdbdbd17664331289b6115609896d1d4ae577f380efcbd1f77f4466441bdb522c6e862a1d805e51a64350ca71b4c99ef26305b038c44371af6e1e18ebcf76b617638802a74048fe771e79511af1222a3bbee0e8fd8f8d9e18217e3a8a62fb8de77f44d3e647fff2c821a9448160b4d61e1b46bac5929670327a2619ffbaa75cca5b7a97d5c79e3baf7e1ffcacdbf2fc64f376966fdec04e7133e0f3b645116cb77f2ec261e3a191f7e46e4058e9cbcdc4b40e4a0dedb5a8ef7e05aad5e345300e16713528dcc14d99cc52881ca70fb1fba2ead601e92be499b2e63af9d056d232caf311bda7e54e5a2ce2bebacf0d13add71e6963f337036ce02b99c1099fca0c7b63b35cf65e592f7462d0d7b355cc88410b38974e3e69745ebf1332a6b533877391b9f4cd6f7d4c90689f26d3ad596d5243534564a2d5e8dab0ae746196cb4ea4f167552ee83824b5bc1ccbf5f0f2fc35bdc376f174e6b5bba99e586e56aa24bdd8518c50afe352284595e1de97ca2c8760439dd7ad06998c1d14a0b35ae27ee20dc27414f1bbf7b3880f6b67fc8e16ccbe3c00ca844463920541949136f59f328fb00eb23a658401d8a47e4affd1da5c3c9fe3c5be09d946ad3ee0fadf1b75557dbc722b2621431523ca2ad6741f6355dad020a4f2bb437e48599f5d645af8ae5812b6b6a032f5de6e65f947c1a74caa90b4bb5c6bbc15e0968f91283973f9e5f47639e7f2d3e3d5e4f85a3264a18964e16c0450503ae1c084fd761500f2f70246e1ec2688d6aef336be82882347afd063276e1d52986ee44cb9b96f9426784e708606860fb278451ea62760fd93bef1cc5d1f403fd42efd3da0f04e13560aa30318190e6af1d079a91dbb1315b544df555e6d3e0e9af20006c91f28ee9ba99db8dd3683d8830f46165300ab21af30484f92a84c10a8546caaf65d72a7e6d8497299e26ec5ae114f0a34b9e3c5c7d39d311de45804306a1623c130404e3228967996e67405c71560290ed02e0816835815cb245f96db96cb1ffedb80d17b0e22573f833c5927e05490bf59a92d3f2a93f519f988e96056dd0013f015cf9c0abfa13fdc9d15b0a00e90031e5aad7fc2452d0ee44a448cdef8df7b162aaa91e10e8b6f5c7f9ff1bdbfb9f047a643b858a1421e2a424eb55a8eda4f234d77831cf40db79329a5771ad0b6668b2614b89cd734088e0fb2c0e78a40fa4696369a0052cfb9b10517fef2c9bf911d924fc4b1a717f9f959a8105bc9f811ba8f63f304e2692b609839cc5109ed4cc5596824522a2719c0576ad26fe7b5b89e3d823446c7f736384d6b17336fdbe4d7a837c3d40b12b304b896287f90bb779fd79cfa1dcd9709743b68327dc16190b771ba951b9dbd1e729cfec5eb9d5d9d699eb505bdbad501c64d18c215f38f6e3857231eb757276bf03caee753a9577d704635a00a8c614e3e99b0214512807a087ff80e42b5b48c261ff2d215fbac482eebb227913c490e8c1cbe9ad639e54922821ae611991111246776f1936e458b4caf3d52cad9b50a63adc3ac49dc9c9e81f95721d1ea005f02e3f64c28c6ce1c9d7ac95553711cf497a8d16cb89c78e724401b42c372ae0a04be8a5b285dca36429ab09a46443fc2b67620a91912e33e7006d9c7a1f665346e507c38e7472d0e8975c809443201443cc20656e8786a8f828c5427f1d9cc60767b65b8990a2a4a4204d9f1726edc48840559b5a71cd0cba4f58e3f2d4aa6424c2114e1a27915dc915aea37046bfa58e7dcdbb9906f14425875ca87f49827ac823baf2bed89bfa9f78d8326be404fdba5e1ef4f3f45967c9df6702b41da925c0135669d511cdcfa3fbc3ba4a18fbd6e7b71e25a9ee5df6eb16f790b77fba52611c0ef5e4616be86c758e621523376c5e8f678792b6c9eaa6a0ec9a8423e10b470d0f69e58b6c399400fa8a1c9620d8680cda5f24dc5e8ecda20917a8206117b8c30aa5572af9a3fb9fefdee445711ff49819c4419d5126a2847644ca870ee6d224b521418af2c72302f7f3cf9ebb86fc490b6f1470cb6da66e41d3810ad4d1d89e478d5cf068a679e9be7a299ce138be9f5ba55a0b67e01075d89c331e2eac3218007811d42414c6d298ff79549a3de413fa29dd5fb6d4b02e90ec0099af9c08ac9f1005bc1ffc28aefb3a41c8f3bf2fb99942ffd9be664b29225806e67d4f87f5eaf0555008724ae200f27962caa4516530cc9a6e7732bb0328286d94d0732cf42afad4e6c194ab5ce66689645471f07a7a5f9a6a4dde5cf5a5501133788ab4c4ac41cd45e24041258f597c499781146f768503b9a17592317af9bd2055048483410d2d9a3593c2c60298b306f18e7492e8fb6257489367e5f2964237dcb0a5aebedf21a6383b55e0e71e62f28f865c7cd509cdfc0a5ca0843792e2fd997cb8467d9579fd9e9d81600eca7e02f7af28b20be4e38b2449bdbada81e15da77f66bfdb905abdcec6983ce8940b097f238fcb972196c4c7c2bd9b4ff22daa3a5c3f89b8f6248d632df6f20c6bd48cd39031614c4087ce968aab9f6ebe6c2915ed0fa0fafa8edeaa44c7c9b48807e073553188433954a12cf6388bf4f7e637144411120a3d4e5327df42873d6b492032e3bf7a5525260494f9fba600601c85a4f888e55534e0e610617a53ab5126deb876e552a3f144dacf01108e966ffce646c6b41bccac43fbd0c22e8f224d0344011ef8690f249cb6529f286c1ff007bb107b1bd16453b5af866353ae6ee1f0b6dc0e7d04c0af633c85005fbae723ddd32b369da49752c1f883ab28907c3501166e4d6e6fa2d95ea7453c44699e7ef07f56f6dc9fe2fe3bc9d07e1862dfd303efaca21e99e32c63f2626c02eed8ed734c6178ddd44593f33ba57fbed54cfbc839e4e2357d787f1977229896d78db1aec5b365ebc5c6fd2c16f320c6551e7f4e6d35847f7096062b8ebc6e6d6899c4479cc5d22e76eebb3d2bdc8888f0d337780c11858576397b4cabded1ec2f3dd54445a50d7eb24ac7d421354394e28c03361ac2a23150a04ed9ac2830b4c69ccdbf6074bc4bbbeb91c4db0bc488069156393fe3dfeedfceeef09aafaf9d9f3fec94f1e84bcaf9e61260491bbaeff2cb5b9ccde35a05d50b1b48de7b28ca56926c8bb6cbad9ed3d5e2bcc15831fe3949a51c3986742a4d6248200b09ef410764bfbac37a67bc0155686c1168856712bb6ccb0c63467f40211617b9c96ef4f66b532ed166f0440886147e512824acabfa6355e1666977195bd2bf84a1942058beed09929760848e8eafdc4024208e937e154459500c62cf690e3944680970f65992b0cd246f655f16b5584a1f9395dde6d4dab2325a8f37654330ac6c3c4f659cfcafe2bcc0f6cbcbc0f19991d0ce30743f7111f6304c06ad86051fed3d4caccca772d12086db02de0a31a424682c7b8e4c6d3ad6272e26664e01249f69867d9167faeaa66ac1b085f4a64a37f836cd3fee3e24e3e569a1f745ce102ce178b9779025c6b8d235e778a4dd585bd0f4cbb341ff5f7fc82d568932a9c3b0d6a9626db0402c536a74d874b3526dd863333abe4f9518e3d93a148ee7f8e83ba7082b48d7e64389030970bce84982298c9c55565d821c9a17368f374b402b52c50474416f725fffc45e3193068b327b9b412715ccb72904096cc6fc6a9cd86f73a7f173a5807a6fa8960ec3a65c86333f8672b88a187dd4a5002018a040fd7c4e3a205e6249a027493f1b19274e8d653295a0a2158879938cc57a0bc4a2ae5ca8446367d2cd2a8902c817e6a5a2495dc0c8e15a07a9141b8930e71675494763b2e9f3557e1f09895ec9d3800e6f7a1a17b1c10839ad991c6b2c8dc26a6e9fd877f1bbd6f3410132d47c4ebf35bda1ce69a295fc72d93c9a2cbe815adae6e27aa806daf8fd683021d6fd4d8006463c60e3f48028ad9eec6c68b8f46ceb0381f832f3d2b06d95e208312edf25be9480e9686045a1df6ac7bf20f72b4b3c5dd778afe5625672d2382dba57d11439a5233a2c1eb92163939f92d5dd369076b7a4119a0979278295fbe84de5c9344f4f91550eba8788e1fa0038eb112295f37805505dfc26c2d20265291ecdfd734011bff0d1bdd556d6543ed960abb8f9705eb7b72ce934ac6118a5ef5086f742310b40f61da07a339f2826f0d6558d1fd561a639af559acc8c0160d2eea31117b73d890550a1c7681cf621cee51b1bae030551c079e952ef3a156d7a216f77ea745d65f01e803d63e11dac16c4d64a12c27266a859bae5459440ab25c21a8eb8c7cd6f3e0d302637f56e3b3f3d7c8ffaab271d416fe77ce24ae0d525386aa20383940ea9abfbc219237cfd15f8c418d837152550188d9a3e734ae360a7e59751bd58cba1abbca2e4da1afb390de55064cb70c48eaa5b4fb6ec8a9cce6ef51fa9acbe1bc7c113bf96a1ad8c9ced50f719e0a0a228fe5a56085a14ccfff67eb01993b2a17258ca32d1111c560899c8afb737fa003dc8a4a0dba9679792d0dc0a32bb74ac2d12c8b65b6c9a5c972423cabf203fa564c51fab352ff455bf224ca58a84784b371c46969eb8a22a3a91945cfaca128623b4856f034ab27dce6929b23980689401512a762975c16033f26787f3abd25d791f3ee1949f3a0c9e8596d15c9de1d5b42ab41e2dd817849be367de78f387a455f9d7ad1ee35b291f1864b39665b750a885e57100a0b555dd506f811d10e3151c052c68382e719ed83358108a49d3b9ee3251dc9e732c80398bdc62b84373d42712725c5ee9fa23625a2c6b62fd0037c85a39d923d3fef242b0e097e6478c6f4206d30a71443c75eacadcfa4be539c12baf27965298ccd56060e55385028c8d2fb841ad343fdf8fb32023f9f79141d7446e695d11075f974fc5825d384a72c0219aa43cd041e18392413651194da533f0341932eb6df63e9ee8bf60e6bafc1641349a2051454abe709c2954ecb570c942a1a0f38c567bd0315a977f3b9ab8502d6f6214268c70c7ccb785a9ad6d29a301c5db7149467546c026fae626ccbc3774aeb6d2a5eaca033610f42b1cd4e35f27684816950ee95e561ecbfb59d7fd861bbb9ac9849f9324d54618c3ffd382f2631beb298dad803ad26a8ce8cdf96df01b7e706e61bcbd9be29ff094a0bf74f7981ad44a6dbe9e976976b9f13b4d5b4ae08ad8c93c1eff942bd1571a4a3d7f6d5cff6d57197a3f45222207fca3fbbda7cb79297af8f5f0ab912082bbcf436cde7a4300f19547c943594c041dc9836823b11288badde088d58ad28f9a0527aa0c70930fba8ddc2eec3ca7d4e3b30d4c725b47eaf319916cb9a0e88e1b44b4300bf2f085a0ceed046051dfff442dcf42e504fe8efc3a06e205ef2f868aea5e3d3d65a5ccd6cb6f6d77ee118fb94580125fdeabb471b5555e1fab9f63f9d77cb81b6404429a1967fa5bc093f22ecfdf98e78dbf98866303f5ea9807e810f19255c63ffeb488f348ea0145076adae14754f1aadb093bffe92959900eea18a1b1c0567782d011b6eb5fbb51c3e8df5852ba1a41e1162ba9b3e7c401818aed55e16aa444b2a084660778338d89b001528d0e3153caa41066e29c306cef33f0fc9ec7a70fe39b5428465e39f38c1c3bb23fab7e5554f59e9d68c1e9e79c7f448d680df4f870d974c6e4c3e554c8563ea03deefab07a68979bd146219cbd59d28059cbce516160132ee359cfd2a4f14906cd3bc63fc102d610fab4954805ff9753d7e54420d5b791f3c4303566f09fb0149ac978d34e829b9058f581bd85adcf064ecc3ca14b2353cbe11f405b2485babcbb232fb2d70c8a14d60ea260dd224e9ffcf9edc2b434dc0f4696bed1fbd73c36c89deb1458c0a076365445abb54148a8d11356a6539eca3e2377083b5ad1617ef921d8a59eb001f810c713ac91785fac57353c23716e5f7ad0e30ae18121a0d161103006a71b75be58b16285ecc36dbdb13e1343de1eb1095b15d65f9a65ce3430201e4cc4de6b4f75b7b2eaa7650bf1a73594c746221ed5a6a76924875794ed0475a67f9b76ba18ebe437a6caab31e0c30b3a9f53d943ed1ef8e5f40db23808565ac91dee1365e993751a168f60af287d5be0739d199102f679bb674d03f0d3e3566794348f9c75675d75acc1bd5ce975eea61d6fb086ebf621eb6783e75d623d8a13cb08457bd7d3d0f56d6ffd1d4ac71ee42d588bdb21e54bedb62366ef0c5586ed9a60252808a64f6574e4e1ccf25d6560648f7005436dbb5e663e6b12eb2417d04d987fc56a82f4556dface128f66a74d3925711a94a59a255f3697136063aa9e254d55f840ed0a9f1ab74e1653ca146d57067f9a781e985adcc7de988e47493fd7073ee433e32d56d23a1ba071bba99e1dced073fa8286e4ee5eb544da5d56db0bb7252fcf017177fdfe0705a551a5a51d69fd7f49c91f5ff8bdbb0a716c8155def96fff0340567d996464b3a93687459d87f9ca37f3cd9731f42c24c267a37f603a4aeea53692bfb892b1bbc16266fb19e06030b95e9ba525a4835f0077747f17983697ecf8e08d00a5c50d30e9b27fbf41b53e69a6ac63349a11bca1084efd7ba219c5f47c6da2292ab66cc1d4b4ecf1767ddb0712e6df12b383dfe1f66a2c77e3543c3d948db583f2234d3f145adec7f76dd6f4969e2145128cf840bc5778ef39b8c82c74d4b26d2ec2eafc7625208a6c0575b5688286aba82c0f00b884a73404548589cebd4ddfdc32e34eb950863a004703761907d6f10a5c98a8822c8f911a764b007a737ae189928d0da1735f09837ca1844a13161c98fe5f84520009c2995bd5a0aa6f9d115d4d17172a4284de5c2adad2ecbb1d04f06d8122704cb7787d61685fbafd1a22781728cdc2336bd34f9f06751139ddd4b90834d6ba8648d60abec99381db4d57a52270939825ceb80aca2b284a74548cba84a6373d7957f70a0dd87a3f7dd20c56eb0c53f449608d8b3ec4c999d23b7f3bfd00a0d17ab966d53863b312b6822ef13622fba6f6ca0c5295dfdaf41b75d5c0f7eab0d66708a32a8ac3c301c773bd711d5efa8524a81c628e1a0152a2740a10d99fcaf34c7aa5dee83f929316c751553860c7f7b1795f16a236884a0af56d8684b196edf5a45353978aebf7122493da50255a90baaca5c2e982dd4a5772a2bf1f167e8ad29f0ac3f27affbf0040bf8f27aab1bd959077268adb13531d1b218c34033287e3121a5b02b2e326b8ee28312ff14d844ec98daf09c06047fa20b437ab65249ba1254926df060dea92d5b4d75478b24ba62d588c3ce49dd0b893fd1f577b0d8d10ed4ec73e6fdbb10c005c772ff5eabaf430798eb0aac9ccbe3cee9a0f30665770ec5703ab970c0ad843aead30df03a576f148ddc22a864fa9424f2d8f65f05c3e78841c308644ea69984be0b0e88c05dec34e78c9bf9b3238b73b11933a2dad784f69fc8b718dce3d4469abf0081c0d359dcb89faa17036eb93a30cf5bae458558b902e286ed15f540851a06922cc3d660c3189fbb495b2f213c997b91fa1d6df19f5aa68b161acae40e25308a2b897fb8ca62d428b3bd655ccc7534ceb32772e54887019f371f0c7460dbd29ad81d5e6dc8630b198b978705d06d164bb2bea21e5aeef04e499ee4f4157e37595cdb864a7dfdf1ab2087a2a1bc7a54d9f63d2e021c144fdc755d8a7f25addf152c6664d47f480934248b38a939ed4871e756a81298b42e20086c6e305431fe3b597d4639bbb82bd123977876df18204e14039939dcf4c6ebff0c152d056195edbf34376e75f807919c3a236b47dff6e1767d67a89c002d41dba5bcf7b66269a308ecf6185be13a4906e3dbf44d2184157d2bed11a88b9769ff1d55a145dca64faa1074bb9eb77cb73eaa9aebfd2c39b9b2b393c21c3c6477004dcc401ba404ee76a9e9d54adfb06a28966931036121e81624a67e8019b92c549cb610c0e5b69acaa24a5f3ff435a17c05dff58097934a02aba20e347ac4171778b112a7eafb80d25d6d0898e5e4e15aac653ce2a679a1e7927bbca8e30e60f2af718f74417a3fc2576f4a03ea4aa9d8595f8e3cf2b082e0c2d304a43a80c3963d48fec2e7d09a8f83b1828a66b7755327ce1edfd2c4733e195b208c638a8c0055d651c1dfcd7726dcfa9ee8f8bb4c1b97a8241116e26bebaa1a2ebebd69e6759573d79884d2cc75b5520f178ad6dd2c3ff95bd3a660950a15916b47c41c0508bbea776f6876ba51b8c656c157016dc1e99568bfc260b2c5ff467b0b4cbb12e2afb741c872de19f09f89545cc5958723d340edeaed2ef10034b7c16c5f79866acab287e6c8ac1ac0620eab3369bbc12b6ee1c70fd625eca615db3bf53e18e52e8fccce56b63a9d9f9d9da42a4a1db30c276d97e67bf1cfc90e6b46bf87726669a92a8e75cf4f47449a7d2329879fc00c0fbec10c12348676b018b63be898f36e2ff9802ae3e88b26521fef647014e51b2ae0c65b2d6c32e11ddbc52a89d204fc5cf1367e3a7e22a3c760d0f1eeb23ddbed232813530584d66815b70299e514aab1d10fd0fe897d4c5f4f8c33a90e1c8ae5db9fc1bf03b8590a3329eaa5371dedfbd5ac75d48884179a91a5fee927c607028f84987e4018e7a8a8ff809af9f6c12f616518a3172fad40a2f4b4151c1c598af48f670e0242b5403506d15e4d627d9c56de8680ab7ccd7b50e2f6d27c3f6d96391912202ac2211662b5513540d192abfad075d666dbf893a337a61822278908aba30bd49a1c0e7a967b63c48ba1ce36e5d74e1831a2e71f8580b40c212fa39aeaec8b47f54b1d3896ccc3c7c47754355024a031bad381e0bd091e0ec4982f79ec8f1506ba48112b9e6abb57839b56f6e9e5ce57b9351a0464e0a4c3044001003d1fd2b59c801a9276d5fbabd1893ef4a261d3cfd06340c445440433585b46f77edacb5f1531cdf56daa454bd65b02eb87170d1bae6f5fb7d778db641c17e21402338b35f6f70bdbdcb8701a6217cfdf868b702a4bb390b7d23284e2b18fad8884fdabac67d23953df97130fdb520108e709d41dabfbe5d14a433e319cc496b35b77ab809aceb78a656eec60945dc7f8e5520945f03ae15ae8a6b061a1e9c5442909e6ce1214abe4b31489b0ead3947903cab1415080ddea7c0b4787f3ab430b52651e942cf4882a8bb63fe54c85fc720550c6e096faf63bfd816b1175f3f431d7f44171610d52414b9a92d07b41343cd5383651791dad78e71a05a2ca554f2a39806323ca0fdc5863ba4deda271a65bbf4244dd41c8f6e19bb81d355194246adb0c949e699efce6da5f6b98b285bb7d1085692ce1316e2677138ae1d0bb570082ae8c03b788b699061afdc259f8a2970cf2e2f1cc1ffef34de003271be5e3fb0f9fecf5058d00604038e56209f1f91b89e2d3739833cd6a9b3dfd9e3e871ab1bbb33006df228d830bfbcac47651326671cbe50368358de2ad280d2c1a4b84e1ecefdb5a105db56e673a856785938053b89c03337b32cce334bd77ff2c38de65820949fb02f77ba7fab1643299df8adeee06a2e41ae88754433e3f8aaacb7d53984f348426b2c8518515551c6f0f282c0d093a259537223dd144287f029fea18db74b8b6cae4ff9d00664d6daf93b4d8bc6b584f435992d8b16372aebd957749f7b831689633d9e2250431a0c3401ba6e9abee0b23d856c0526a8150824b79ce9606eef5bb6f7bd44022adae316ead807950e52710fd9be58ba252dece0d10c562841ec89ef89e8a0ac3dc76a3adb9d39d4df8ba619ba8424ff728d361ebada0edccb395f58467057eb8e059384ae608550add2115e1123e77a2c0513a8c1bfde0103d6e56069edd3d586ac72a90a5df3041780b952e1835a821b969c42ed9a21896b174c028abb71fba64b4e88c3358bfb2e8339745e93e00a7df70fbd2fed44560f229b97847730a07960732f146b5c4011dc6163d1c3ffad0213ce48c05ed88dba74bda63a85782bd4e1e29092e816fd262125a88a88efef9c892ebe22382e01645dd553978deffa19b6cb31f477333f7bfbd67359db341b2e2b22b9e3e88b5ecd7fe378c5fa5c0b023cd55e9a4aac2a16a9bafc07ba4e431d7fa5b79d80ea069a53bd9b697a9e297ec339af6373459b697ce14c59a67240614103a4973747ab55a3533cff81ac9dc611b1e06f6c65767859599ee52459b603dfe67430e68313261149352ec5ae8139de2a565a907d2f883aab9efb3c1a784cfc3ecd568cd7b02d8fd8280ac32037b15659c3ee9f0241daada9335efd19dbbdedefc57eccceddf6e50b34b33906ac319a0bcf823ef59b21538f9b4ff704cbca3f564be36adced992f4262bcaa2f62503f58b6ee74fa4f26626d02dcbfdcb0b72f99b863d2796eb25c069b11dd8e6a87cd973a962f04ee4f24eae6be6e147f6730cd9af991d73ba2b03504d45e9bc6b7412f6412bd89679d764fe88f23bd74ee69e6c595d91d4b13427612c9fcc6e17b8b2ae4d353506ce74ae9d71c7cc269a4347b252f023d4c237ffdc1854902158f11a71e858ac3882825b427f82137b618a156f3f0034e8526a5a6b1741e51667b2a0e280adcdd86eca15b104c3ab106baf0da3dc1fd82897949591a33146ff6f2a41f3fbc2ba1d3f6ec8808f4f3b6af3c1b34e8cbb1a7ad6bd2bfdc850b84ceb63fcc8de53b1feba5488167b7ff3a52ca4a5a8e5904325ce6fb4d1f55a7e8ffb030e03a9133120d9889144d411229e3cd5ce4f6fc82e2aa17559736b2cefa47f49f7d167b2105bc9b9adb281b660268bf65aafc0cd9f4d310c2f5d1e82b02aaa729676b49730cc904f8c67a80378afaaa2a381a7459e074fbfb7fa601ae24b1805e1abf427e0eee1435fda65b4782e54ea70f011a7ba8b4420c5012ea0a6b4c5344ba8a60fa9b38457709f71c5b77ec9489bbe68e57bdb3cfd31972a936d5766ad2c936f78358c7e3f6592266bb9d10dbb5b1712b2c6ce742841123aaed7e1dd176b6e32a1af39ba7810ef8b5b6bd64f8bf05708bdcdf42e627226b212ed4f56e9032c90fa3ae8392fa1604591c30c22a74ebc1246db14fee1974c10ebcd535e1f55f36a4657a58b7d5a149e5c2ea7022500922bf89089882874331209e6d60436cca7cc98d9a1c419dda6c70029d1431089bb5bcfe4b04f0f606d106a0d397b034325000abe0b26570f8e58038dc703902648e7640620e5abee5876bd10128227c2665f4327435c57e9c17744660d65dd871886be281ec398a7e9bc104393b74ea6f58d2fc2a2d158d8062b410504b93d9ae2da3219215fdd9fdbfa94f95eac5154b29fac5712bdd787590a3019ec02f88448971591bc11e8084a4758eb21c1e0b73d0dfabc48878abb1308f8574e6411bce14bc8dc7442a26de35a1e160e43accfbbdd2b5b74987a96fbf5d8db27101fd5a29fbe372712ec78711c4296951b5ccfc47f4488f6c7687dcb2e40e78e4ee4c419e7626cc514c3206e3928f61e52f750b54dcb36821ce6e56c70082c22b4a5cc07fa74385ec358b8ca505fe911e1aa385c54aa5f2ae821cfb249b6519a45f3273073ccc13eca3c9f2039da4e8caaf26ac7acbb1b5059b638638bfb94a57cb099340f678014e0339fc500e75fe143f438de261d1ec431ea98059c68337853ad1090281a1cc7bdef9edd152a82b7d6c34bf7528e0edab5f67bd97674bb0c963ab542862c4586fcf7e75553438ac3020a3ea9b4f1f22cf6eab294a2ba06171e66e36396404efc41a0fdf50f664d470043325c80eb18eefa795ea385b92d0cf81253adb2ca8cedc6d479cfda72f55fc00f71bd6e91eedd91eddcb7c093667a89d8f05cb38994d6e9c971a3101e090acebf90df223d453bae44cfe6d312691fc6c9e7b3821e5882ea4c1e9e86883ffb8bab5969558a16eadf73e263c47fee7d00cf8aeb5e13fa56caf5e32c5e967a352e19b4863fa3a47100127ba09c4e06fbbe079a421d17c9fc549597ceb659f8d89239017ebe6b89dd0e50ab1a45df1e34c17afe5382317ad517c72c50b518172ae1a6d93a15516d13f92038136a2da12a7b05de3b513ae2ced10"]}} \ No newline at end of file diff --git a/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsELECTRA.json b/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsELECTRA.json deleted file mode 100644 index 90f34d88973..00000000000 --- a/validator/remote/src/integration-test/resources/responses/produce_block_responses/newBlockContentsELECTRA.json +++ /dev/null @@ -1 +0,0 @@ -{"version":"electra","execution_payload_blinded":false,"execution_payload_value":"18191970007912226669631394668547651071148255645822697200640823429642410377933","consensus_block_value":"61453013339935582189619461221462653003808078281923085412032520595023747176323","data":{"block":{"slot":"1","proposer_index":"4666673844721362956","parent_root":"0x367cbd40ac7318427aadb97345a91fa2e965daf3158d7f1846f1306305f41bef","state_root":"0xfd18cf40cc907a739be483f1ca0ee23ad65cdd3df23205eabc6d660a75d1f54e","body":{"randao_reveal":"0x9005ed0936f527d416609285b355fe6b9610d730c18b9d2f4942ba7d0eb95ba304ff46b6a2fb86f0c756bf09274db8e11399b7642f9fc5ae50b5bd9c1d87654277a19bfc3df78d36da16f44a48630d9550774a4ca9f3a5b55bbf33345ad2ec71","eth1_data":{"deposit_root":"0x6fdfab408c56b6105a76eff5c0435d09fc6ed7a938e7f946cf74fbbb9416428f","deposit_count":"4658411424342975020","block_hash":"0x499db7404cbff78670f0209f1932346fef68d985cb55a8d27472742bdf54d379"},"graffiti":"0x0000000000000000000000000000000000000000000000000000000000000000","proposer_slashings":[{"signed_header_1":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x32a7d23faa44fc04cc23dc3b560a55ade3deb2c393e9de2e6d20bdad2416c39b","state_root":"0xf943e43fcb615e36ec5aa6b9db6f1746d0d5b50d708f6400e39cf25495f39cfb","body_root":"0x0c65de3f6bad3d7be19d0de5aff82b13d6d8b49f26588dba111e361d6f545486"},"signature":"0xb520c40e02457e0d3d61ebba3b04912f7db82a9a74132fedf190d94b32738dc62744644455959b4b4dc7aaf1e54064fa0f4aefe30696b7ed758c921d266402360e9abc003374800cd2aa6ffaa0c11a5cbfb3798b1816bac7be1e0c67c3305483"},"signed_header_2":{"message":{"slot":"4661716390776343276","proposer_index":"4600574485989226763","parent_root":"0x7e2bbb3f2a737918a12f79e9a52da7e1fceaae0b6c0c82172425cbce8d99a0c6","state_root":"0x45c8cc3f4a90db49c16643672a93697ae9e1b15549b207e99aa10076fe767a26","body_root":"0x58e9c63feadbba8eb6a9aa92fd1b7e47efe4b0e7ff7a30a3c822443ed8d731b1"},"signature":"0xa01cb4e18fb43a400024b67cd091680b8412ea66ed4a0d41f7ee611a87476d153e18879e22a5dbc98df9ea4ecd016c1801f1ee9411e103383c73c06cb5331b8377ef8f2f4ab67a4975135a59d9121279f9d979625d78f339f71aaaec565911b1"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4580744678799082634","index":"4579092195582398506","beacon_block_root":"0xded09d3f4aedd5706b7e7dc2c7d90de31bfaa9e5fcf74dba08ab1cb8d17d357c","source":{"epoch":"533461240","root":"0xed7436400b3f287283b1005a48f4f70e79abc311779529d2628c4161a3a79565"},"target":{"epoch":"538462976","root":"0xc7324240cba769e8982b3203a1e2ce746ca5c5ed0a04d85d078abad0efe52650"}},"signature":"0xab7a632a4707b1f8280944e479d239726caec1c6a73e8cc29eb98aa9cbeaa97d4c4921bdb8cd977f07a172062b8143be0d2db585dd2e8356897ae04f59234c800f2a6a2607a9491de5c57a92fbd8ad6e3f5e525618a1481b1f1446623e8765fc"},"attestation_2":{"attesting_indices":["4585702132744102314","4590659586689121994","4589007099177470570"],"data":{"slot":"4620404293179370891","index":"4618751809962686763","beacon_block_root":"0x14b72a404bd6e6fb6d37cfb0f00521a985b1c135e4267b46be8ec8f15869047b","source":{"epoch":"538078227","root":"0x867d07400b9c22992dc93ab5e53a9c77abc3bba12adb6fa3d1955da376ae50bb"},"target":{"epoch":"536923980","root":"0x603b1340cb04640f42436c5e3e2973dd9ebdbd7dbd491e2f7593d612c2ece1a5"}},"signature":"0xa32991816eb9f297553b4732309a4cdba7b33287264611715b0ab3319bca19e581da6e2659912a4e0e94aafc01c488e30ffc96ed14e2a726b9d3c618405ee0bf54bf6ae7f2097465cb27ab8132ec24eb93d3c9159475304082f7f0e452b93b65"}}],"attestations":[{"aggregation_bits":"0xfa79cdb89d0d51c5cdd001b7425c6d726750a9d5f89ade6ed9890ce3a706140c399a5e10c90a819094b65322dac7501f7c75471e69d4567358d8ca75f7c1b3133ebecf006b369a1f36efc5f2b706d5922ff98c58a1825a53a864376658f816600cf021cea843d4396502bb9c74d1510afe26036f89f783b4f5c7bacb6649c46f217a6af835f312d6fa253d2bbc83c07993f4f10de2ed2d952689379dbe4f794c1a1055a6b364d68e038deec9667f576b3b9eca5fcadd6298f181e1edb876efc3d0975ae14ae9a0ad2f1836d4c3f1080a96d8ab7c43b34bb2eda895ff66be698b363cfa4be33da3ec94a1a7a90672fd12c4a59916bb937e78476e4f08e4f4031001","data":{"slot":"4605531939934246443","index":"4610489389584298827","beacon_block_root":"0xbfe0f53feb7ec0670c92703760d5d9debdccb8574d35ead15a1928fc05d1765b","source":{"epoch":"529421377","root":"0x95c9163fa9b8e5a07382c4a8ca24e64fab3f93035e00f87325462db67031aff2"},"target":{"epoch":"529806126","root":"0x6f87223f6921271789fcf5512313bdb59e3995dff16ea6ffca43a625bb6f40dd"}},"signature":"0x8f8d16b39e14569aab1b712e5c19ed51afe3600a6b017e8ab432f43a02ee720a733c33ef087d2f3653a9701e8d8a836301655b9195c0373b775c88ba1368e5d55354a70a3096bd26dee29dddbe7a4820e2b1653e84122beacbc01af7d93e6bdc"},{"aggregation_bits":"0x4ac567b296efbf7cf3209e87096a7a1a50fd523400113f917f6584a5a306f06b2d8da9ae858d47ff2594010089838efe41f19a78d9aae27c2ffde26f278b8681db9d091eb72e7cab3e449dfccd46a270693e1f88f197324e57bfd45573315cf9fb60d770937b32f7c0c6ce1581ec51e6b60f71acfde304dc917f2e0aa7872038b7d9140d15f7927c23a0490a74c2b0aca2773fed9217067e4444f9ca93874e4ff8407111c3efdb138b97c6d4957b6a70ec1debb283e3d0eb1cfef068adcffbf057d20fdc339eae03f0fa2613abdde8158a7fc40c3cfd1117eb6f8c4ae21d6b2ab4b57ae9a8653a34451aee6418c0c3609dc937293f5f5b346a7ab1a0d144185101","data":{"slot":"4544390030852162633","index":"4542737547635478505","beacon_block_root":"0x1bb1ed3e09ca0083285797d894e275ebd7548c015a7d158b66ce053068d7b2bd","source":{"epoch":"527690007","root":"0xf56ef93ec93242f93dd1c881ecd04c51ca4e8eddeeebc3160acc7e9fb41544a8"},"target":{"epoch":"528074756","root":"0x6735d63e89f87d96fd623486e205c81ff060884934a0b8731dd31351d25a90e8"}},"signature":"0x90309dd491ae6ed51917dc305a3d4ae68d0a0d4792c7eb59c193bd03605bd94e61cab37502de0ad3e6162bc02427bba80a798b3670d5de82a854094016cc298b265371345c0e3ac49fd44bbb9ba0d4fcea0c1a80cecb60e93921d907e8c48120"},{"aggregation_bits":"0xe8c9759f0840f980ae956b15fc383d992e7d4420d12ba5bf32f669f446ac6fa388e20e5ce96e9266dd98840179d2cde3cabd9a8bafab5dec9c2e89f7f78c989e690548603984803b80c82d7b76443194576a1ce49da5cfe56f56e83b745fb01b5f18ccc86d88f5a22d927e64ff0b8e880893abcddec45b268531c4a0697537dae643a24b1a36432f37d42962553bd39af71f37e0429b81470c11316aa39db074aa3f1df4124e7cb203debed60b885ffb9b27e46a1434e81bbd56566632d0729c0822ac415cbb67f25973667d88e58df9c2f058a0ae7f118c98cc448453b6fbe590363bd17ed62c2f808df61f2a9e593235eeb56db74b9dd15980189a5271468301","data":{"slot":"4529517677607038185","index":"4574134745932346122","beacon_block_root":"0x64b8743faafef0521f5350f290979d7e470fa3e3f8746bd14933f531ca233947","source":{"epoch":"532884117","root":"0x3d76803f6a6732c935cd819be98574e43a09a5bf8ce3195ded306ea11562ca31"},"target":{"epoch":"531729870","root":"0xb03c5d3f2a2d6e66f45eed9fdfbaefb2601b9f2bd2970eba0038035333a71672"}},"signature":"0x8c40f51a99fce6ebb9a4db5e80d715fff319e7ae523e46afb5d03c000d427e23c7a39e77e2af53851706283c8ca91d680997cb459386fbdff52c4d0ecf498e173717a838a792b210bdffaf476538628584a133899bf30dd5ce7056463b8cd683"}],"deposits":[{"proof":["0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c","0x8afa683fea95afdc0ad91e4937a9c6185315a1076506bd45a4357cc27fe5a75c"],"data":{"pubkey":"0xb1f8f6e731dbf6b4e3265fb857c7190adbfc7e6cc95ce2e8bda72be8b6ea3459f862310a2484c3b0ee33b30920f18c1d","withdrawal_credentials":"0xfcc0453faa5beb79c96a8a4d2dde41e779279b73abbab1a2b73c11749d2af49c","amount":"32000000000","signature":"0xb594382214f5bdd375de66c45e1c61a526c7b73fb166c72433bbd9c2a7ad6881244e61cc6427e0206a50b762a129d8830e8708c55761d61ce9e3b19c1bee13bc55daa13bdb07118efdbf57a588b8a64e6392d14f935e53b68933e3355b35acdb"}}],"voluntary_exits":[{"message":{"epoch":"4562567354825622634","validator_index":"4564219838042306762"},"signature":"0xb86aecf4e9673e9ac774883f03c46c2cfe59320e441abfc2e2bbaeda2193f58c57a3aec0ae63ba17d3b1cb81bd548689004773c1867cf047e1a2d5c3c51973fca33040cae49bee51bf4d2e23786f51dc5672bff5e9df8f7bc5fadae6be5c146e"}],"sync_aggregate":{"sync_committee_bits":"0x01000000","sync_committee_signature":"0x919ee45cc18456f6e85da6bc21c2e40f44f9a887932c73ea9ad354f88e56d4ec0a8c396ed143082c8e31d697b877a2a215d2966d91c7beb156bf7ab5777e210012f70dcd5f7657808a82cba51e194be994f917150ebdb9e5c57476f1edb47206"},"execution_payload":{"parent_hash":"0xe5ca603e08e1eff7259e45ea6bb662256d9d74b1724ee8feb0ea59f6e2ebe3be","fee_recipient":"0xf8eb5a3ea82ccf3c1be1ac153e3f77f273a07343","state_root":"0xbf886c3ec849316e3b187793c3a4398b6097768d06bd968a54e8d2652d2a75a9","receipts_root":"0xd2a9663e689510b3305bdebe972d4e58669a751fbc85bf448269162e078b2c34","logs_bloom":"0x324f493e880f6d0bfaa9e297b9d9b45986a970f94c718be767ef67174b6fc1e9f770a36a743c8a3abab61dc439ddc0604dd5015b1ed3835787d9565dee0f3e64b25de4c097defe3001f483a4b6feac22b992cada114bfc709d483b4d94f07bb0a1c4fb9e93ca3c31f4b9683753ba33ffd971777e301367f1edfe6809da491535c711a7877b4c97fd1a756136c412b4f3c4471ba439607333623558a63030f2cb6bc2ba885822672de14ea697d44fbcde134b6909208466be0b4c981658ba30f999c991aca746c3331766af1ee10cbe69624066708ae086999a0a3853eb777b3f9f0455cfd98a98c7719710515b97c596d2b662d353a90206e470c523d4374853","prev_randao":"0x4570433e285b4c50f0ec49c38d62c9268cac6f8b023ab4a19570abdf25d07874","block_number":"4491510546443434056","gas_limit":"4489858063226749928","gas_used":"4481595642848361992","timestamp":"4479943159631677864","extra_data":"0x58913d3ec8a62b95e52fb1ee60ebddf392af6e1db902dd5bc3f1eea7003130ff","base_fee_per_gas":"48712354854557871613352262057776104244427151172201944877932608112921551169417","block_hash":"0xcb571a3e876c6732a4c11cf3562059c2b8c16889ffb6d1b8d5f883591e767c3f","transactions":["0xb736203ee72088","0xc7dab83ea972da","0xa198c43e69db1b","0x135fa13e28a157","0xed1cad3ee80999","0x60e3893ea8cfd4","0x39a1953e683816","0xac67723e28fe51"],"withdrawals":[{"index":"4864971916622804241","validator_index":"2164897","address":"0x09988f43d11dcf2aa7811c9997eb4119e8f153ce","amount":"4866624404134455665"}],"blob_gas_used":"4858361979461100433","excess_blob_gas":"4856709496244416305","deposit_receipts":[{"pubkey":"0x98d6103215e3916a0cff3af6b6f29f22374a32d87d440a302e18a9e2daa80b32a2824798030f6a2e06ab612b07c41f74","withdrawal_credentials":"0x010000000000000000000000db034f43b15d672031628c76afcc23e92d134914","amount":"4855057017322699473","signature":"0xa8b4b8e92e67565ec430f2fdda94ed0f6f06d8cb302770191d614b795d194e4728c11e72162f25e04d0f7dda1dcd54da0d8a7c39e71e945873168ffa294da70dd1acbc1902a2fb1598267df5d277a0f95592967ea222ab0706571001c315eb2b","index":"4845142109432660113"},{"pubkey":"0xac60d2ec631e27b37f4f5541319b94c8cf82299d71ae4139039cbc1d0c30c71a075cb62166801ccd0a56f0bc29edcdae","withdrawal_credentials":"0x01000000000000000000000001464343f1f425aa1be85acd56de4c833a194738","amount":"4891411660974652178","signature":"0xb66c9d2c80f5a12930f0899b9ff3d1a6a37e0f9edb279ced767eca8ef0380227681b15bd3850a00a383491ed1d8e869310f10edea2b912278e1e2ec1cfaaba8c0981af2e40fd233a9fd2f67ec56540c66e062212ee2781593a4714914e15cb52","index":"4894716627408020434"}],"exits":[{"source_address":"0x5d6ec4433175f5be08277b12261c89e3b0d65cac","validator_pubkey":"0xa83cbdf40e5c4bdb4e9802d94d765c70150d9926521b0ec4d273e788b83a9f304694e75d2e381ce631b24121ffecc9d4"},{"source_address":"0x96d1b2431158938de8efb094a1b6c64ac3df5962","validator_pubkey":"0xb679b4b686530827b2a201eb2b18454e9a5758d7257737b29bb215b9f354c2ff57e912b19d4a051556187aa24c97371b"},{"source_address":"0x83b0b843710cb448f2ac4969cd2db27dbddc5ad0","validator_pubkey":"0xa7f8aff7b912b6363efb810f2b661643624ab914b034e78d72397ef84fa04862dee94c9b2f46b872fe852f197f558fce"},{"source_address":"0xbc13a74351ef5117d2757feb48c8efe4cfe55786","validator_pubkey":"0x83b8c61b63de768821cbd82ee3c67c81bb848163d6af0186ffe1ca3936d283bb4cab886f3fbc7f6336fec3da8d542c76"},{"source_address":"0xa51dc242b07456952ea93a8886a01023c35b31c4","validator_pubkey":"0xa6c59055cc0bed5baf1a815f59d9d1cbd7aba1a4fb8d83de7310ad85640524318110a6a16f5bc141a81c34e103d9b7be"}]},"bls_to_execution_changes":[{"message":{"validator_index":"1816540","from_bls_pubkey":"0x990cf4f3bf6ede0aaef3010026465f98f381860535ce007b87879afbf2c955c13d07d7c2d91e22fddd8ef5531f8bd22c","to_execution_address":"0x2b0599420f867177e37d0db84f5ea0beef702ac2"},"signature":"0xac8ebc3beb6cfc97c27f286e0d2e582707cbcb972d0898a41831b2d1393a684ce54ce54dc9128dc3988930ae4d92b4ed0a51b2bf639d8fd8e62e40ceac222362d9bb67f9d1b8419f3123dac1bb2e4e0cccb5c7c0985c83bd0501ed610935aa96"},{"message":{"validator_index":"2892795","from_bls_pubkey":"0xa0695f8f6f65e3d8401e144eb382eaed73f9ec56be6de71dadb917af79a08ff7b74967dd4f4766ed77f7bc2fc01cfa38","to_execution_address":"0x51478d424f1d3001cd03dc0ef66fc958fb7628e6"},"signature":"0xa18c2c70d886e11a592393a7bef6fb3a515100e1436763854eb96fca9c031a959e4c105be367a10ea87c3d1a8bce821303470a1d6053cd89139bbd86fd7bbdd3e377b331884bedb0f9b10eafcb3272561fc5d71b96b219d7fe3aacd6e1558c97"},{"message":{"validator_index":"2664029","from_bls_pubkey":"0x97e268878248299c9e4d2c86957935d6cddb83900dbb6d4e52a935bcda58978f6fd33e0dc891cea14da0feafd5173762","to_execution_address":"0xad6f0e43909dff15ba42fc53c6ad05b972343e5a"},"signature":"0xa2010187045aa6d63130c7ff23464438af57c3e42eaa90823205936a94c47713b68bd93d3b6837947e277ece630a6d200d428979548f340f6f71ca33e8731e059a8c20f75d71d36caebbbf6fde28f37a919353dedb7b7c7e4dbcda553e5bbee5"},{"message":{"validator_index":"740284","from_bls_pubkey":"0x8aec1b1f595063af33939f3c3322ad38d2e1de1b11fbc8a9d04235dc7fc9792e1c88e51452d337855d254a71f42816e8","to_execution_address":"0xd3b10243d034be9fa5c8caaa6ebf2e537e3a3c7e"},"signature":"0xa0ba14bb9ce5877d9f9d607da9b2fd2d629a1de42d6d3beb5a8f4c1661aa1d6863e01de14c548be8a9df222efc6373be1290581da81c76d71bfada1d07481d7b7de94290efd640aadca41d6b4d4f81091f4c459b454bd6e333eaa35c60faacf5"},{"message":{"validator_index":"355536","from_bls_pubkey":"0xa912f4ad989d87e777e45af7c265b430daf0b39345987506d4158cdee406847f294fc7745154eb52abf0934a5e1866ee","to_execution_address":"0xf51e0c420e9d60ece0c4bbc926328df885b91272"},"signature":"0xa7f77c7fc98b1c3a364dcac68b5cff112f7745e6dd41918ba56a6fa6945507e0ce245334e22d4581f49bda913baa2a6b1176b44d52168151b3aff9a625dcdebad1899747c42c4a43cf31f49124fc0d4543e4485592c243c5300b79214398b770"},{"message":{"validator_index":"1275809","from_bls_pubkey":"0xa77e90361be2a534a386cb689d6d763a98bea5f23f325b553a762648668e4adb4991fb5f41ad7ece1578f082a5c01b5e","to_execution_address":"0x1c6100424e341f76cb4a8a20cd43b69291bf1096"},"signature":"0xad188010cb0db88e067c2699030353a1c215ae9adf083916ee2069a805e0f2cd00c76db9250a859106dbbff4430b4dd114d6293c4b3c2e9cfd31f07949f04e53f63423a08b56d7247772d07959d5d92b17bd8c7c0b294b71d3db903d56509177"},{"message":{"validator_index":"891060","from_bls_pubkey":"0xb4582d56f8ad9dcc77eb5413558e63a6b562e42534c579a85384e7d7d6ff8974ff933d05a444c1d2784945f4cd1c952e","to_execution_address":"0xdbabc5418e28d8265fe892d2129c8395d0dd064a"},"signature":"0xa7f07c5a20159b029b2dac119315a0d439c541e63b0d1f6d377fd2867e5559d6b6302eb609d5796fab97cbca121ddf400c840b9ffa60dbcd89c6d441f84aff2cca1f68fd9e258a969b0d511ad1d90c0c783dde3c093ee8cd56cf6f70a61fd77a"},{"message":{"validator_index":"2123298","from_bls_pubkey":"0xa5849044acc283563bd9b40fe9b01a8c079093829fc3837cddf20a8f9c13e59629251481406f415c8e2df65285ddb41f","to_execution_address":"0x9ecb7542cf4bad14a20f79bc45931b8d1483242e"},"signature":"0x81df97c3071aac41af79494001a1c4404b5121776a71d6cbe3b8eef000e803f59edd2fff33331d2ea037faa919ddd6a115e09bead88d7c8f23368628f306e3a244f2ce0a54e4472d29e4b79eced6da3e5ab40177e96fa0d94d97f5e07d2e6e95"},{"message":{"validator_index":"1738549","from_bls_pubkey":"0x9815cccaf23783a4b1bfa265d2d620e70c76b50b32e1975b909ddc3749fcca44d97e3e7e717a1f2979c3d1e4a70c1ccc","to_execution_address":"0x5d163b420f4066c536ad816e89ebe88f53a11ae2"},"signature":"0xa4fb80ffdea501d608a5e79ed05fd3ff67d39963afeff1b2e0be94811c3497f8b615af4a16e438e23e8cc6c34376a514169ba117403d86a2ebeb85ca0bd638e63ca982ee45c8350d726f228ac03eb8fb584fcc56e8d3877a3756cbb06a7aad43"},{"message":{"validator_index":"2814804","from_bls_pubkey":"0xa6e3b4975aa42a8e0f86af69da109dfc42eae539bc7bce0be20f733b1fb15107bc42eda74c8788c1feb0aae542a6fd17","to_execution_address":"0x83582f424fd7244f213350c530fd112a5fa71806"},"signature":"0x8d5a3a8aded5a58f952ac7bae812991f1b285e1704e87ef9fd8a743aeca8dd30ed7710a1b6c31a1860768704e6ac709316d5e7002605470c7fcf4b2c691f8a897c900cc60e9618daf83af929b7e8474e7f71bd996427c256691c9b90581b1264"},{"message":{"validator_index":"2274073","from_bls_pubkey":"0x8d2a58c4d8939845fbbdb04c560d5eb57cca82d7dfed86580867df9faffd4bf8139bffdd1dc92555e6325e18d57afaa3","to_execution_address":"0xa5c538418d3fc79b5d2f41e4e96f70cf6626eff9"},"signature":"0xaead124a78a24d0bf0a4a7d20c8c4f34e92899d925eb47750d683c474093f4d5a5af0ab36598838b149c0c348bab313e0079198921f7df6009c7e02db76b077b2541c12b71c70cc93b80ee4e150b2ad10ec6ecf6086bb8f70e9b49e4f708946c"},{"message":{"validator_index":"350328","from_bls_pubkey":"0xa5062aabefffe7dc8d1d8d47651f86ade1d5ae1e656cc53bdea561703a90e7d961763550042f9134880b9b7e8270e3cc","to_execution_address":"0xcb072d41cdd6852547b50f3b90819969722ced1d"},"signature":"0xb5f57267f4332f951dd6ddc29fb6ff407e92bc8c68d439d69b7c16be009e4ad7bc565d8dc5c48cbce29809340c2888140981fe9e575a3eb126255ad5dd4ec229eacdc544257ccb5633b06b1ac3cf845bf91030b5eaf67fb915afeccc7f79bcd6"},{"message":{"validator_index":"2965580","from_bls_pubkey":"0x97b55e1024410802342165e019ac207ab51084599c14747f2234a1cbda282926c4897fd5923adf264dd2d8309b960da9","to_execution_address":"0x2730ae410e57553a34f42f8060bfd5c9e9e90292"},"signature":"0xad1c182d5c0751fe063f7288a2217dde6a86066bb31d539fad2564c13fb8b4935299b02fda5567b1188554c98d6818511876e25d09ca0278b541e759542a5c0b7cf393f9a3d986a4af2687077a115d312c2bc78a7bf7c95308feddddd0e4a998"},{"message":{"validator_index":"1197817","from_bls_pubkey":"0x8238eb67219c0c314c0b387a1300ebe7ee0b3bfde764c14e90d42e82197100fedb6950f6db432cee0e766cfd35ff22c7","to_execution_address":"0x4d72a2414eee13c41e7afed607d1fe63f5ef00b6"},"signature":"0xb3e53a1e347fb5b508927c1a938354500ef34bd89843132609e5801d8831886b94d9c5100ccf9be9dd1ab272298319f306a7e2d95a304df4b42d08006e2273e3d30e054eb0cfb4cb143c70a9b24ac213ca467f0cb0b089fc3e500339d288bc3d"},{"message":{"validator_index":"813069","from_bls_pubkey":"0xaafd198509805b36458bfa1c0202ea15976ab05f75100f8ed811fb700b4d657531e364c12a87d345f4799c43e2bb5ae6","to_execution_address":"0x0cbd67418de2cc74b31707894c29cc66340ef769"},"signature":"0xa6c82d3cefe88ef29dd3396af000eaeaedd8317fd147a1ba13051e010fba5f612665b21f362d4c4f0d91536cd84400d517af6af033a6896d89038c61a787e2d790c173697c8628bc71c91a0f5e7c883d898836a4f6069bd0022fa89642b8deff"},{"message":{"validator_index":"2981201","from_bls_pubkey":"0x99b20a6a3e75af8e62e7f3f5143a149ab8e4ff041b0bf44e70174c19184e0ad2d612a3cd648ac30b428469bde0d1cea2","to_execution_address":"0x7c0e7f46d64d29bb09077be5c681fd8ec96ed2ce"},"signature":"0xa0fd1938a1fd006793fb69482996de28fa8269f16aa5fb33fe4ac6047653d5123500b8606f67abb026b57f8a46e40982054d914727f04e16bbb647a214842433dfda136d4e2987131e03549764c8e23b93359e9e6ea736b3e8eb8c4836435f19"}],"blob_kzg_commitments":["0x02f65546365f449dbedb4d15903f8d2af483cbcc493fa0a9a6d1d982e0a353b81d20fe781245e513126d1f941ef3f17d","0xc8926746567ca6cede12189315a54fc3e17ace1626e5257b1d4e0f2a51812d1856c1ab5c2322444a3c24568fc99bc6ca"]}},"kzg_proofs":["0x4e7a3e46b68dc1b093e7eac2de62df5e0d90c714236243925dd6e7a34a2731e37b9e16f4fcc866cb2fce2b453b128117","0x15175046d6aa23e2b31eb54063c8a1f7fa86ca5eff07c963d4521d4bbb040b43b43fc4d70da6c50159856240e5ba5564"],"blobs":["0xdbb36146f6c78513d4557fbee82d6490e87dcda8dcad4e354acf52f22ce2e4a2ede071bb1d832438843c993b90632ab1a10bb523f89d9469bd50be3de50f45bd2773bfbe661215c62910151d0c483c50ec40fbb55aed6247e023e620dd668556b349bc91496072dc17f97aa14ac2b854ede1878eeba037d7901f5f80c31a33014d2315794381b753539bb48edc1c54019c779325dd033b359e1d0f5a2dd9f030c97c4ef1567071321959155779569b724396a3af21ac002c4de5663912a2ceed8bbfae8d87dbbbda5765381d7b0221d704745950ef8eb42fd8e5fe8f4f30944c0dc0cf5f5f00db782fa69e4d60b9357f51cf0c3742252635eac020ee22f2bfc15d8c71776b988433796736b84a190e3472256d5459a0631c24a5073eb2913c9b9c8a68a3bcd5a95e3edd366f7f5678a00339a345382ed7ea9a78e1f3840d0fab80e7c01d686fd4793e76f933e95b83f772e8ac66254e18dd56d3da9b07617f73d254713607be68136bfebae5967bb96f8554edba2b35e36cd4c9bf4e0abd7df3f117376136e85ec26c916e3939afd09bd3562b809a42e8c38487fb5c405046884e687544181f45481b5d0cfda768f223464878cd858465b84cb900efc39f8537ee1d3288d0ea9c950734064c5cf2fce49f17b994424cbc5c03c864718e6f7f1bebaf892d0887e5daf3efcb65f45f6005f2afcbbaeed2745ff7e23762023bfa98f38234286d4fe50855a4894ab20e792125af82bfe7ed61f568da637c623df17cc787f7cc303c0423d8a089ddfcb5894b766cf19b50d3d6cb0bca1b4d570a4f1ebe291a69866ec98eda43e40fdc07b73df24ec21290ee208d8793a6f66cb632263f8d3b0029cdcd1bef9c678181e1a6c6ff7391abd3c4b7d5fb280e43928f81af4a1e21bfd6b0b5d75cdfe018bc0ba52bd3a585f787e9d41476a77d309e65c704f06e582bcda003e29ca43150848b6ff5faa1a18ae10266b77e4158d0c662a761d765c678541fd65bdffeda5372820c5fd4af874a56de8a288af952a928745fc3b7bc7db73486e23f865be8a0459f4b681487a950229511a7882c1bcc44422002dfce76bd3af33e67a935647f601ed55f42861ea3c4c0a2ab58ec53717cb93b23b0b702c9b844cd3f9198c092475ae8923a399a7480bf8f9f4e2af4469b226c1221c113da012659c13e72ddbc26ec2bb3ab2f3d71dc0875f0b4b14f8a4bcbb5b63a22b5a9f02bbbfee1b596bb4a5c316d99233adb2392222146f254ab9d3ea5a5990d4cf862019348634e043ca5628e9cdc6f89dfe5427c54b49ac97c860dec8df00f7ca4b7a36a37dde0d5994eb7ad849fd62498757944f3230390e45c279ed084bec89d55af541b1e5f7424ff72b977e19785a66a9f027aac673f965dc291a6b0b54e4f25bc573f296ad38e95ff405ff7d75e2e1ece7730dcf298a128d4a39d60e72d9014ca3827b3841c2294987cac0856ce3d328353883398a385411ae32f97397f8093bd766ea9159e66a15b505090727d5d08651a25a5be780892b2e3b4e974effa18ec8837a83aa83906e9707dde856c184817711b1cbb0b4be543bdf30183596c9bb8b22a836a41b231944b21958662c85b1f3dcaf31a766f6bb6752a1cfdf9f31a40fedbc1e7dcc9342494322f003bd9f3d84dcc7cbe92af387cda758d0a16a8151630101d037d7947249a2e75598c02817a7fe57cc3f1f0c4690a23378f2fbc1211c6c0042eebde42c4c760086b60acbe298a7aaa3f6fba6b1e329a16aa200f1a2753ef1addef082449ecd9dc65f9d02320ef1633cbf6eeeefc3c40b683b8583a5aca50b53ab43c8f0d344fb709dd3270dfb5c039db3dd8f0b37ce3ba6cb3c704b58a9961a9b7e14812f072b72ba0ab2976eb5d4fec7edb7a568905574c28620b573ebb5a1603c5bc1e8fbbc98c2cad14d4153cff7c7fa97804dc9ad65ec5e36a91deb5151c840f76716af732007bccbc2e0dde44d86ada3a23e6a6154476263e136fb5b7f4b91eaa00487e0ae2fb51f175ed9010b04badf425a771075e4d5b272642169c3c1e6aae4ab44bbcdd275946d811edc7872e123ee602f22f7fca7acbfad791d284f3c27bb695cf8804f3fd92574c54c2ee363d948c5a6e29e9d85c5405ab469bb5638730849bcc02341c746f6dcbb2ce8a31c0d6082a43229b1edaed697b85ef4018d58f4d6a2e90cc92a9bebf5f44aaf26c8fca1aac355bf076314879eb4ef724b6627f16a85befb76c4221e513e48c58e36595eac6b6a98eb2ad1b3d7f3493bee76a88ed492aaebf2652803948789d04ffb34631c300f8a4f6309e1451e0d56bdefe5a1cc546b133293385c2c461789f83a34cf18f08c47955dc0e610ddf84fe752946e80591afe0ca4220247370af4c66c2fabe94d19e497a832e4207689afe6387c3e24dacb5564c1503a5b2c446959e950a9954d1ad7582fdc16278c7c7bc1c5cebc95bc1a27f85ea2643783036223aac175163b62a025ead11d53dbfeb3458b6fbf87e3ca9853fc944d8ac1feb463dca21846f14e36451cd6bffa3aa9fc060ead6687823d20975ac5d4e7bcbc75b1a6a0d23632db9a37222a1981372ad99958383404b7978aaf29a33e480809de8c922238bdc4d22c33bbe86b4b163942ccbd1abf55e08ccd91157cd81110c369fcd778e2e82f9a7848c5d76e044d6053b73621cfd94d551048891f61c02f9d025a837dfccf2716775b5c5cb4ff11368ceb608480b34715e07842fbe2dab2099b193d48b3b54f9924c663969189e6a58faeb92c6e4ca549ff79341f83215393871811268bec28ff09999c0337f1568236f11efd0be6c7f94370dbebb46817545c7abfc34f022d158c4d787935432384bdc394bc42fe98b11def0eee2c2bf02cc5445540e3b180739586b0cf660acd3072311b2a4afe18ebc6c86c88cf50625cfd30e91fbe0217914a660244ee1552ec258e5be95a8de2493d6260b9ce53b01c1c1dd1b62942083ffb06a328e13dcdb9b99dd476317c63a63d6f1273927dfa70bda5da291a0c600246d07ff31d35cb2e4aa4848fcbb55455295675d4fc00fcc8a60974236a2b42a404e7dc1b44563caa46d2de8f1d982ce55a120660d1d56ad0f3c04d4e6cf441824268cdc18971ddc15218739f888e179ba331e616614291fdee03dc399d246b73853ebb361c6dd612675d7266c9575e3a0c76c5090fc5e45300b8ecc2c2e2e31f33eefc819823718f96cf819cbf08e067a0013a887cffc90b149ec60de397563642cf0fdcd144d6c6611581537b2932c91e086ec8b5f15c5025832a8d3a83647ac564c798f611d2977f30755f6a6d9bd7f4f234d9d9baa4f384991e294a23fe2815f1f2273f0e43cea7e4a10f372f1b0b4f0577c06e4c20e5b8291628d6b1ca2ad0d091a120be4bfb21c889f3a4105bff4498dc4d16e24aee88427788e9de1cf8d5b9de809e91b7e7725d61d7f042df6d639f77d4cbc68265089a76c4bf0b32193d557467becd42511a479989847dabc47becf968b1b5a8a77e9a11edc91504dc5482c968beeffe410e80f02fe8dbddb050bb4d4e53ecd3b355bf2e33d0f18e53bc10e26e39bc4af37e8466a5144c7ad25f01dd375e068d5d8fa8b17cfcc5db7a4d80c4031e1cea2fd744f6ed14026288e9033889a760d93a025cc1fd50efe5dc254d2eea4672df40b3b7371e5d365067c68674677f8cec642245ec9f8917caae915b3197321d3adc09578f098680d09fda3a2c674b2b2f466a5f989c4b32d218455944556e60dfdb031be2d17b563df77c5f3c3bb2aa3832908869d292b2cc491344fd90b20843a207f3887347b3707209cfb16bc154c8e9c766cf7fa501d05209a663dd2868bee8cf4ba02e6d8cfa7858a920ca95ad04243d022b171f805d3d7612a90f2edeafd22213700c728662f041d60e8fb278c6c966439f6a9d7b15f71daa1f0322059db458fdb8c9572cb4451f05e21eb175537837d756d23806f663a94d79360cc4a120a773c85b16b1b71f5e786ebf964eaf7eab9c2c542e72274957f60f1ce81e1bfbe40f08fa3079fbfcab4170f692e87c92038055dcf7ef0ee765b3855d0bc0d975ed749fd9498407480ab3de3c1ccc81c6d9016ee75dd0262e9734d0e3f0336dd1dcb1ac4d27f33e31ceb0a7f4d365be88e9f77d246f72ce089b05542f910f564e243a7264f6eb16674d3fed89b731d08332181ab34e66a2609f3c6285e14c1ecc7c335062335ef69b4cb959aff23b07b821a1d3699428385afc50c10d5fa8d6f9bb9e48e0626ab82db589be21012a0a1921453a3b00b4b80d459551945cdbce561e3afb0d703f79c3f84380e29d89e40f126310f19204e7a65d68f089c1ceaaccf80cfcd17831f4f0592f8e5e9947b2c1fd2db965946dec865f675df4c7e6d4dd055db216e5ad385aa073e39b4afcd9811aace1d57c5caf669f7ba3bc9189c9c027d5ba825e3ebda7d02e2072fad83b7aed09709e18c42111ad8b9990c40204b390ab1b4ab49791322def7d6c713c8155540981a25eea262b7e63cae1322f8c6fa7c8000114ef473102befb22b60ee4556e6920f38e43416909f38035e83edf8010308b18d94b7d314f58daece095ac9748d14f192ec1ade8cdb93e4748b98cdbb8a0bb7fb85229bcc597d63669791d96998feb8906ff1daa44e57735ca13faacda540f857c007222d6f621670c3efa3628cbe89f1b8e3b26556de66245826e68b3b64e444174936fe79bda5a9b0a4c77f6bfccf5a8037fc7218e39c5e6b1d21810b64738824c3ec5b740958962ef96955dfc978d20191dd00f17f1a9ecb65d8eeff450d8e68ea4032e4d5f2fd0d06016b339cdd46a6710c59318089fdba38eb4450b787e2fabb71c901d5416a855109c01f9b26063cb320592e9a8e99f986d8aa736d38b9cae37c362e185aaba5043743630eab46bf018af49e6c8e1302c15f7ae3c216666f3a93e5b9f47254deaa575a2ef4469eb29552ae4a59980f56bad6c3ad390ae688fce317fb4a233c92ad055c5d0ab48b07b14021089737b7daf9f6c8d7cdce55b05da65db4292ffebbfd821783d4b3ec96a0d67b0b15dc3945091d7e90414b77f858db21cb07d77aa29d8c4ac3683f85294427fd840966449795f41233cdd030fee0db1d4e0ec7519dfcbc2c7d0d017f105f737d8f45208fab72546119dd7e0c89d169e1cb2c5a6ce9561fe4c5f112e64d330ae56ea81e53d2d39d7f3ec7f0b8bb44a451f9f1e1758d8bea0eb38a5d2a4d29f6b21e4c87cc7d4b4a31b1a26e86d42df225a862095c4e1af63c755ff79f2b8694753045c6bbfe637472d6aa15ee7d5bb0680d4936d3b33b6073424e97fd90725555fa0f0de750e30ef27bd8cf07af4332fa57b601d31c4508aa109481e017d4878c1505c897f70b2f92ba9a7d121c3b6f0e97b07d15e4caf44f2021574946bb42e62c6fe519ea6c46e9a8d967f036fa87acf046039a943279c15604d61b166834451b1ce33f901269c2ddaceba7bf912a302886b1242ea7f2d668f7e89f0d275d1f5a8398ddce2ac59cc26f54e1e430bdc974079bba79a14057d5555b00e88882febfdb9c4c246e4129b61d046cf45e22fba713dd286e76b180af0826d0cdb675ff8ebaf9ead4dfcfcffe1f5dbb9b8adb1ea494807ca49df781393191811de51d54d315949827b7622a8df75b63b81049dc224f128bdca1e0ff4e9c55edc32696c211c4563ab28399888e6c729fb81cc9b7cb1e556a91edf2939f4880cf2423942446d3b23bb6de2c6bcf2d01cd8b0481809a2e4ef59de5cb4ab98919489370bbe631517b068d3bd70bee09c0ef2202afe19567304bf1031423e391785b5a95778afa5e7b8e24a9f9d15c2c802b816c7cd5d58f594f53e42294c76cbb7055f1742618aa66c78b9683dd3408f74b5cd26a2adad3a8293975f906efb412d51812d7e493a4ed2a8fecb482d8daab3e4d98a00631b4f3c3a9be0c540a8e6f38aa33d9829616d035650e2ac465f0b49590fc8b5f6d91434d32f177500daea92ec9ef9bd046e3c1e96a9341e1f813ad0757a28d816d50d3baeaae903e197f62b0c75d8134f81ad74af0cb53c5bed52ceda5069e8cf1d079418ac0845737a4c199f2249f29a9b4f5e314636494d7901ec6c7802bf481304bdc8ba89c48e36aa6298a437cbbd1887c3fb4afee24095b77b54cb0c0ffa513c0cc62c6fab065f310854a8c860d8267e87162862d8041483afe9a7a5d4b6a6f9f13bffb32a5fe195534841c6ad3d063c18d789ec766e9ab2e8c0fface45378d3cf8e105e330e06a20a3c44cbbd899ae832d31dc19ca06425d6eae00bdb2f742588702fd87ccf8aa7ba20ede317a8c9a88edd41171512f6a3b566310962a8f99190ec2ab29eb8dbfb7e5a2650a298aa32deef60965d74ade6619d2eafc98a05b1fa2b0b01871a6fea485583901eb54939857c1ec2ee9258ff731b71addbca446a198e19be83745328b7c855ec0db8d1d81cf5152454cd490b83ed818d6a950d8f40c907a038973d8172c773d88a5038d59052694c074e3400c8084ee3e474cf28e5dd382b0a91b6adfd97a1b510c515c0928982f0a0e5d10db5fa5779ed238cec8af63d12b4ab3b21d2a769da165cbd32d706b4b2276999bafe45ea6165907fc43676e6b7915b68218ac0fd5dc19c1067e9368b25641e2f90d3d406aeb409f64c6b37af6f127f046e74f679ad82708ca80500ad59eb5e46e273d041d00bed48acc005ed82391984e69e75e34239fdc7d3ce839a4f04cf4e746d9e196e060af84dd8240b187d73bcae3ac72917d280fc5e99f1b57ca351defb5ac6c4cad840067e1afb161b37276cd17ba2193b57b8383fb40eb6dddd306fe647405180cdf491d58c4cc0e2b4e7fdd9f92e62d3ae5febdff96e8833f3ca65a5592da9011f8f3e7533f6dd6ab4c82d60b97bf5f79c94635f6f453c2c5bbc0e79d8e7811f1aad4fa87637325d0c1ca318192473f561c8fd6baae7a06384e3e93e791eacf23a605f690b718e239f5bdf69c2f5ea5cbe7385b0b9ed30cd9284c4c69ea6aa9a1ee0a33da8754dc02a6a5e0f37e60331ec9e0b180a02b3359304fabbe8aa383b85b49202862503610824a83d473f1b7d673bd0a8e16e2a3a981a6b56f877e650919cd24dd9aecb609b05e78de6552341c294ea1386435fc4de83d67a39d86a9342660c683c754382248b8df98934b04eb47599dc4759fbfba69f0eb41a8d7ebc8d6f00b0644a4a492d65bbc719aa4ea14ba3aa50434422a07bbbab2be2b9f951a5248563fb09a32a5db56f876002d04ec8382fb52146434b9c7bdc4ddcc2c28c4141129436f60d4724c6a19a9ad54e3f838e3417c89fcd8ba2d110a0bd592a9b16c2d9ec8275b0a011185f0fe6d54562283703b0d8d50f197be656645b5d6d4c3fe5d7da743128c42870ece106fe6993ba12d28861c9f37fc0378d7327db1ef494238323973237999ff0005e9c98efa03e3eb93efb8dd4209b552ffad64e067cb036d1abd2a4704de3dd4c6a6f17326413079b22de5848d844361c40044059adce15ee6941f65ca98f937a27745c7b1c3821ea338ae6cd90d4f52f10a1a176404cf08064bcdb84714619e2eeb84bd8fd3646e35111eb9828631cafec68962650a95345cdaf2051d259596aecb531782b87b6309e3c8d6939272e570fa581a0e604f01b29b14dd160d2af7b275ba39c1d0face24464087aca4528b7262ef77e0b8ecb9a492f7ef28a9a8d80820a4d65eee2c1758fba0fd0d5644909858de6b8550affb07912876cf2e0c607ca462252582ee450cd0040b5ee46da8f6be57de96cfb22cf6cc76a1881947593a2129abf4703167548f0e5bf38e282af0fdaf7f413a5e4209137322588dbca1da398f93e24e7d375c0763c0bc3584a06e01e12820d6f5d775b3f7bc865fc6625dd61299992169228e332b91150cc42d686e0accf27e089502a69dec0f2b301ca5fd917dbfb924910a8d9513196adebddfbceef4127401e4152645996f52bb7e37d0d1126129ef884a5c51fa46c7fd08c97d72d9c7429bf8533590dbc1949b164fb7f9d2a6dba19907a814f18ab100667ad720fadb136426870867d166e8fac5879316095458221cc3061739f923f86c962ea652164d4287cc5f75829ca1740fb0159889d2cbf0c086299955d2f8ad699704571a7929ebf13e30a8d4c0879a308973d3facc029d6eb86f58c89f521a9e6650a81468a56b409ad2b4902d606f0c80597fad99a64e701b351cde7bc0ed54a9f8b47118dea2e6901f9076e0a1eee5a8513905f82d3ef458d42d8bd69108c4ada2b93eeccdc4d8cd7ff661617982ea87d70071e22339daa989d757494603bf21bc99669b21d7c1dcfae77e98008b8c70f7b8f3761c21d27b7aed5c4c46a739fd805a424cbc388a72bd258e8692e0ad85a7aa3c38ee6dca2b284450de19749a4ac2a6b9fd62162ed064b839116f489aa4ff0250298ca5ae2f7d33a171221cddb65d4cf3e4f1f6e3399a6a79557700d2c41450bf852be775497519a5b075068513a9bd95a0702d93bc2e4b0732a738a496830872183665819d1ad7d3ead6cc5f349090976354699abbf42de4fb1496996aa502fe72e8e54ac63252143df5bd17d3c6fe9f794129e6000a28ec8eacb1aeab01fa8f87613f633f80f318c7045c96623b66f7c6a740574922187b2c8f975e76cd11646757e3c0c1ed26cc7f81df36fceaca0726a8567987a71a21dafd477d16324ace279b505e6b52decc7a6ab0c8d051516dc4ca67895632136732f838e275a20ed32f149e28ccbe0ffbb08cf061c7f6c396e13042cc57f229a3d945d1e2c5bea94f70e1f53d326c33149e84ef0134fc10f690160a35741b47dd0875ee9ced24cab248dd147ccf78c65f45f6b503d032c9c061abb95b3c529ec3e0746211871307451ad7cb5e9294ca6b6f1b7d605ee7a5400e8398c3e05e1eaf76d66f0e75f45701cc73812ecca344d9323034c2a586feb39421c9717c2301b8cd6b4fea5bbb96fdc0ccaf7efe25bd381a76fe65aa3e52be5b70bdd3abef41da2df0af0d87830463039362fc69e0c1f6787eb2ce51f7c268443cbef5a9d01d2a8ba12f4e0500c6f6a5b2bd8c38e56358c12c5947da5796b2cb3667096e6a9818ef36e318062064329e3075bb5c91b887ed69b235d011b6b19d56263c833bfac7156cd56cc1c049ec98435379808457b04dd2b13c6b5532433cdec1dd94ea4d70b16e80a5746547908b76b62a9de7e02b4dbffc2e7d26bc4b9bc1ced6e861e1d305d656153eb510e803fa0e06179072be00ef6dd15c442988d49d396f420ac8c3cd713c70e1edbe73782762c275ae32e38fb3936d767af4ed54f8c264e76b5ab49c290906bf914b521ba0cb9514c1cb28cb0430346c7abb9b25057c51c1d9b3612e5a0acd87561abaf15ef7836374e99d8106bc6ff0d3c05932c65062d58f3084569a813c78c16c01328ca7fc93f3bd4d6796e186c33969aaafef785d8cdbf694829521f723edec362ade30cacaed7ccc9e3cfdce7f06c06a02a004f984796ba2ffb594451cac244a31dafe4f313de0d3376aa32e1ddff302cba35881ea51bb3476adf560fb9a05c464560d1980aea5ef34491a905664d526f6b0accf892154424e86568d9ebb630f35336eb6dce00d3aea81dc235b1f5c26f5bd8b901e6b6a34edcc3c0499e277510262c8ed24975d28c7411873753921ce7f5c54da924174fbff8b1eaf29975bd24bf4d437011d5e67dd7f31429219e3d781c9bc132b19ad33434be9b13d0591f5331de08e5893dcc7b6f33e43d1b504eb15364a2ad8d294597bfc7fc549487dc25ac54d8dff102a3b4c73889889420a2db0a7da182aa26c0e8fc6336204a5e6fc508f12dcbc859ddf2f750e276ceb1f62daaf1bbe9c43bd6cf5ea4eba4a3f79cd25f136f1fc360d1d3c4404b9ad6e929d232611033936d16cea958e84ba7846fad055591ecf7e57d1590a4ff062c5548225019351c45038591b46a296ca6b41af7b0f2cef052fdd8c2f380b09787f7c13c165ce5af8174453bd9caf4f6ce6c2d6e941f4d7949b04dfcbf7056cee1cc314f505c02d7db08ee73fd3ced27fde060286d20b3485d8b5b29c4842114a7909fbeff2be9d9f246fb20dc88b11a599abe1d1d95f941c29dcba451b76b25851ce6c3f18eef8fdfa88140d9067a633a9384e3d08f25d0ef7ecee27448ecdbc06ba523fb0ce0ea5c83af7d2be587f636a93e0a5ee58b8d2e95a17ac632777e60715782a24ab86577e2fa1b9234458f0f1891a01213ccc7cdf1e6aa2f314fd63ae4fbb6ef42b86f9adcf9a4d8e8905c229491ff3b7db51aa9935374981db58b4f87ace2678c4a75aeacca2c248788e636ee9fb53cc876f66ea21a5ccbb39720540cb87db7c4241d8b097b2625bd5175329e07fe52525e8190e677d51679a3188ed1cc2ee1145fa9101e8122958599e74bc8aa5bb7caf99e2a81009dd74a162443e6dcd5115c0b22ae83a80fb6130813192a88a195bb44bec8e28fdab77a66696575f6f69697cb94fbc4c5f335f7cdc0e4dddf8c4e3d3cda4184377ca7033b7c049b7734116925280cea10169cbd0b3c4e0327e127f4366e544f309ff7187383b21e1cc71faf58dcf8a01fab268b5ec1fd692152cb74e906b5453d95ae9135caad26b44571ae66d9ad8f680cb242819391a4f5f545ad20a3bdb7fc7007fff12a8a085bb810e7e5d6ca350e9cde21a12ca809b692850bf36c37d19b1f54ea690c89588fb798c27f78b06a1247e7eef61ac901009226d9bc5e1b0c6b90bf115dd84d06c164187231bea89c2574c4a007fca4829c1dcf501b81cddafa83a319037f5a950572bef04566d3c4c7f867d19d60d4bc63cdf711dacad360155f7920e498533f5c2f8aa3297d33c20246a84fba41342ac56ebe3327c38756d225cbab8260c2343284dfe6937a46fe01f08e8dba4b26503b2167c142d4757a88d349e6e24f207c4c0e933384fecb573712c90e1849bb9d6e22d5eae2ce586ecc83df642774e8f24614a0c825dbff58521bc75aa349e00962624257ee0f30576682eb1faa726b0b7398823013eb6aa04373b78c7fe5d6698ac9162148412c2061f575e92d539d4f326a43bbe6fe681a4bb6ced3c737e90926e827797573351628aee5eb8c70175f1919a1a9aab0bba5cd03eae0222931f229917ca3f1e3821d30f5a634fd1d32ff0e0e275c9cadd9de7d174d885255ca74784bd89dcec2f96a6c80531ec824b07d3773c3752fdcc73448dc138267d19cde63bf8e94e4810acac7e9ded5c2d69639e48eb4090509eab37844355ba37ff89469808fa0a9403b2bab8b8341c340e65fcf3c5b9b485a2e7c641955c096e148794e6eb059cd0a69e2ad74b0ddf2fc14cbc79b7d3403881bcbcbee3d1500d0727602178bb1faad04d5840706c0ce1e3074f7996f18d54d2a729d6bcaabf71be191eb99624c7d6bbb1279d55533ef2c4724c02740b46deffcadfc987b7fcd0b644a7ecdfe557c5aea27a256ebec28a3781edf4c332eb062a6df7d6d99b9f746f62b5b11817ab9b7a81bafc278c1cb19a4a8ff200224f9407244bda1850b4c475fa6a2d557930856b52569ec539827d7ae936deadd51b97fcc8f9041e223c22d73acabcc1c46751844f3f5a27d55f1c353a06edd6464971e10ce6a0ca6eab9103cb3c8d544666621f3268e197cad2a04dc7cb467481a92c349238e5c0da6b356be70dc5e90b56eb4e9d4ce2ed452ca7569d7236507b5f21aef2dbdab2a4589860f4ec3b8c6bf67e962367b594b676c79e538eea524263ec77fafe50f9c743c8093e6fc3ecc816eed75bbc194ea6e9d70852d4c55553ffb575b093e1640a6f406aca8dff491a1d768d5a4e01bf7a73ffb93f9f3fe02c55c86578d2057bbc17a4cff923cf4ad4843fabada76d19ea399887916d54707bd775cbb8b63a91e7e6482b316f448d92592cc2aa545c75e30fe24e625f86308fcd4fe0f37e3578e17d9122f1982dec08bdf63d7866c3aa6b0285a01abd6d3d25d2ac05302e28a9c1f017e08b242dbd87669fd8bbf29dbe77cfe369f070d9dcf0577f70e10c16270049a8ef227f6289a31e2c9d419208351e693e86f2877f48bb1f7c09442338869a040bafb57ba2025f42b61340e36dda9077a46127b23dee62c190bb39c371cb77929f1dd0ca3f378c43c9701705c3d530d3b71741cae9477d2aaaaa4effd41568d6c614d9866346f82911e28a1ed415b70c3fb88c47b2ace018cb0c7a2d374409ab1d3316aaf5165b0b59527cd7a06bdbe5879b0dc80ebee5a0729f8f6ad5080e5b87043b9715d3f298d93e24ddc8e31cd499f0288f4052c2a852f31a14091de1290831c090222a2991d777986108314e8338efe00263fef96e56088b4e0c8762c96fbc4e3e549b9e4c92e63599c4395153b657622c0ca282fff8f3297fce2389e3d3a0422ce34a7e3382b22a3da81afdd4962a92397d96e5c1eb8b59d3d8e60693da4da74bc1230b43e75fef0a5030824e01c25cfd626358ea0c4ff3b84282aaecd4d61770e247ed8ea7c1f64207fabf3c03b7c26e261b8206ae067763b6681d70a9d99bd21411e5b97dd83d2a94e7e8cca23c0326cdc064752adae849855bc9998e68f18c69242f7e77f9ad8b14648060c1dacee26959aceacdcedca6ccf624549580b3212d5a132bc4d57433eaaf370fceb220051d8b77ed0aee32f9a9ed628103d773f26f7cce22d33d8f71b75b6e2448b83716a5eb875a018c74867fe883747e072eca1e1e4557c2b9c49b6b76239e11872c3d7f60aae0546898d248371777056d7153ab0cb4d51c513b8b5fc0f4af91c1cbca9732128353a3302bffc98a46bc2f3e9e441ee139e91b9c1e8275613ea720b0af8ab7531b6cc1aa1e0e21c7918551d9a09f5e253508777ab49480e6656b9f120174b2a5d3f43657ffd727f87d6dc3caaf4fdc070fe3f550b37729b2f3c36c6a7a50ce12aa413663644a2bd024e4cdcf09af98061f6a6bf00487909deac58cc1faedd34ef50abaccaa825e4610c855cb4d575c995463a4ae940689d202e85396026c7df5db23069e6f829fcec440c8a4ff297270b008adfbd59ea4042505263d374c0913d880186b16f46d4c3369b79509e98a6fc7f6bb453ae9816cf12e23f9e512eff83a8ec42046ff458f47ca719d355f4beb456b1c3dbe0cb0c43950ac6d7977578ba7d672b26b2dd1fa5ba51eb1b418473e65d3b19a5e6ab95f849fe99d06253a6695f9be18345e9f19c10e29c69aab69e07aaaee849a59270f8de1bc6ac10a4373d1f16d6013cc593d3ed803045f9010ca6aa85890f8f490fbc17bc962853ce1cbf9ab763ec020a2fb894b90415f8a57122d40f42181f606bec4567cc35063210d02ed16bbb7990f4bcfb4b1715ba05966536e31ab43a46e03ec903dbdc4bd3595b2882e0bb53cfc3f32bdd7c9f6a4682d7bd733d0cdcc9dc2c4003668a7b0cd7c26e85a9c3e1c14ed4595893c394f7decdd36900ab6d349968b87525a63e79e804ee08cad1aacd8b792ef3b5910e361c1c11cd9c316ce1b1cb2336f7a8704c2796cd8128bd4258a8514cbfbc1cb69599c06fd94340aaf7b526233d47f44e2c475994051aa3afd2881bd0db82e9e7a45359c8d778d3584e4cef4966e88f06294f94058a36ee06481dff1ee336521938c970a75f8113ac2d34e76e36e5387d19f7ca1441e569de272f20932eca3d0ff4286e948a1d443a1a5952d7537f925d2d7b34f7730aa0bf4e510d23079bd8c5458e866dcacb67d89f0c10db2c46c8de26ad6b36457950bc321550bb637891875cbe2c4ded125998f3145430cf9ada56a61a0878a65ac3fea73f7eba7425d9c2d2745d8c952d44d01dc70acd47657f85c7b795bc6128f8f495c17988207fd265bc9728b20ad50d1011e1c5bddf11d3267f1d013fcd040a5eaae19b0a2ffc625a4b6015cfd81f36336ec3d17ec22eaa8b2fb70660c1f4070f55d5ec45f58f3ecc0fcdddbf09113c3822677dafcc671cd344047611bd42fa5b4ddf4d8f693643554e5de2f30e6d0b7d5e77053545497be8f8ae1e3261d1b39aa5422e5446de29b537b6191138111880553a365624f63b8826fa81fed26fae8b30901585188961e0025aacfe42f7b84bc6e91a674691d9ceabdb31c23ae22b23fc23c90a8b949a36e5485cafd574f7a75e2c6e138d3e67152dfee38fbefbc589002f46386ee2f72a090ddf836d90e4188a6ec940f6d3ae2138a25a1fbb42ae311621299d0ff07b62b59c3e3a55fb53146f919732ebfe2bc07677bdb24708b1ebe650c70e1f9f30174a6b1a7abbe2ca7262b1fe392e34872c68cc33d68ac2d179192c11722a3b5c57208b078532f52860314e880c4cb7a9978f226736e42cba20daa6d33754beadb0fa6511a018c88b165433c97678cea6c373c8bfdad10e3d6d142fe5c712e78000a9f76676fe35b92e13d13ac9f98b349fd8235afd12f608d3b2fd6a16bf29e536f9223cdfefc2b9683596bf64117263126fa362f6fdd03e81c640400520e04d9a54bb9cd53a82caf4c0b12ded7bb734c80944b7b0d97b4ca805138ef5520a3f903d8547d45f24bd1beb7148471cd9c63ec0af5607b1fea587fdf49d6667fc101f98626071c99b7757958a13a484527648ee6c6037668eb7f2281ff927c91e39fc5961c281f5acc18e688f606312fbde28d700a5c9f9a786e5a0fd9f4b1b04968c48f54b274f232bdf4cba224dfb58a9366a91bb6c4b5c817025e0a402fa9a9544290640a4fedcf67e81cbb82f68417d0c8a92231758377b3f3bc7cafe419b140e70ab4aa6b99f4a0331de143ee6a62180a0f1740e8d51d3b2dd7a2ed14425c6baeb9c9a0e1bff0f66471e93dc364398d25554d2c344dd3ef04a2269baf0554f96ec7aa0e40f6bd78231fb37b7a7d23754c40288ed41cc6d36d4efdce7b325573f47125a8fca5fe3d3bfe48bb732bb5ba06b8ead9808b6a2d4c6e398e12501a45a7a627393223f4ddb98eb6f08b15d36be3b510ce52278109dabf0758a984603fc55b101eaf6c60f61ac611c10f6a3a39b92908053de857b51b519f9dac5867152aa840c9536f81a884a51b097e31a1fb76f59c6bd1ba374564ed91412a63badfa7db0f3fc1e6741914ab81551a47e42a8462edbe572de510ea06d61c0c292b99ef19ac9b57355df339703a694fe10365b1b24613343978316b8e177c22b9a0a4093a5f8984cd90e12607b0bbe1d322efb8aedb519d629407e26a59a2fecec9435eec25cb6106f71548e96af6f1f5572c4f54e1b4a3a837b54983b5409d86cd1f4e6985b8a37576c5d388b879cf55e427c305404a87ffeba3728c1501e1eacdc45a2a12b8d72802a965d758d69a244c0f2fddb1795e4f24fbe60c1301dde86e22df6f28c9f257647fab539653b883e3b63805d9770c31f32cdf48c951ef719b0c5895edfd2f6b5e8821704c6c129e9b11623993d187e3166b46084875abd671bc3c76cffa3e6e1708df64484f2cb73fbbe9cf91e570338b2dcf6882e594a4974d67655d78c24a1bc972be205e041add713bef0767f84e6faf172e098fa99034400d7984c71e645e99cf3b18b1c611c497f0d012e015c0126ede419c53bc387bf8472def46986ed2c748cde0914f566bb160345a2a0e076aaf4656a3869fc5f53e2d9f86edc19b1aaa9ec61bfe7d9682ebc16249f5498f00f82cdce9701fb8c3bd77e2d9c43b76d61a1b96442a560aac92d9662b467cf8ba958bbf8ececc6a9587bf337e69c106ccf5bd924bfcf336a8993c5a67f318ee6114ef9bf4509712798e4209518f881cf39c79b3868f4d4e3a1520ea13281b07fbc9fa5aa31c38487c2c3d44e379efd204737f41c6b0c5d4aec1b9751be41b686a4bfd6e78496c64bc9cd2043b837baebb649cd74b638e76b97b3068347db821b5f5941b3765ff38bf7e85e2879a27eb615ca5401a5edb3652c51cb0031849858166647e86f0a5f0d457408b5bbe02d6a4cdf306958ce46255459976d932e7e91f01d749afdfa71fd30eee0a77871e4ce52bf2c6ae8bb8f2ec45e3779a1ac49b7b6ef563f71a5b4fb071a23d439dcdb8666fb896f52fdf4135338b9f9274cfefd0174adb4dfd6d8e66ae522e993f322dcf97b814a5fdcb49222235ca1fb7adb5eaacd5e315db01d00ada1d6495bd1c6b64247bef85b020c86548e9ce5db0fe81f6a013ce43769523366d2560cffe39fd529a6bf8cab8bffaf97ef4313ffdf38ab4a90e2aab89bf0fb5c4f5cd74fb91b61503b5134cb6aedbffc35823c8933711e5438588513eb999f09e7e2876425633c56d2e977702c906d3b9cdaa7339228f5a2e1e8cd5b8b8c3aba09bd528740035b06757ea122542b390275123250a422805ecac3c7e8b1b8c7fd12cfa80c7c0f31e8864796b5ff86ad27547686af73013a14486ce523f61b7f61ebf884b843cbf8ce85b4de922976908332c563d43bbf76ec2e44fafd0f5ee49d7c44c9f8a9c88f4a541db0c9589cdb292d280e505567b82345dee2631ccfe83305ef411c9eb2f066054dcc112bd04b4e9354115a9e575c02e63c628c4c83b45c1b15a1cc8c5ae3bbd594199a8331f1a31da7cdf6fd4889b87dd6ec8e3fb5c7407d5947f21557acee7fa92ec9966f1078abcc8adeb7812fbdacab23f5bac51eae144836affa1b67e09315125dd7830dab4d3c9f083ccb77d06f62a3bec31de8d13eee995a63015f7d6c123639e39f23f9720f5def571fe75afbaa9c7ca9f0963c6c5508bebef785902cced83baf63ac1bb4e1138ce0bb194bad9aac21f3fa6c906f22ed1b877b929c6631621441f8319d0e41baef3e20732d55d390486d97e85438222c58a971f5d603f986d03b65edf280574461cddcf8bed8ffd1492d3f231d1b73683f33475ce9073aa00f293b02d38be6cc054c3091cb5fb642745170b7268de7a57187ac2b71d401481be7a13661beba8d07bd8bb602684163799ce5185a4b46bdd3354121c4eec7dd54bf762af0fa4e2cac4bfef0af98724a6fbedf6e88664f11e1242d83c72b350f630148e86f53e002637b316902f16af6bca8e0b54f10bf4ba9672d14f58c1f7bd36dc1d93d5df9820300a4e868a32e8ebdd19deac2ed5c85936337babd73e941cdcac19f458994aaede0f3e09112aacddfa41c18c689ff97b79ae7889486e76e4d65833654c1d84c0c5298da290f71f3660cec786313729fe7ffa814f07d8edfc8a1a65ef24fda3ef3c4e21d615b807ef3e353fadfb1e16837977b608f586422992566b53767a8f83f0caa97f742fc7a03906f3c0a0e7fd522d7e6c5af954e9b93c2ec4ff7be722dd5f5b2ba4c45c3e55ea24830cdb18e325735ed8c87a47456e0de21e2f92971d4c5e8f8837e01ef4ac5ed3562dcda977bcbaad2491e7fb8785390465bfbd5f6b6f9481409be8e8406c948cf6f27909ae2183e5d5b72f24837d2d7bc76c22a7ed57fddddd48bd866bca02afd60c47b732ede551932b49137d460654795f8b0726c86738d02f88f1e96811f7dc75856b50e50850af05ae595ac317650266e77d6415f8b2a9cb333438ad9cc92a6ce1cf00edaadc0803da71ae4567c053c9c72a5b61a6f4fadcee5ff753754ef64af0c72b419d722049ce7c855530f60763e1a2391dbd6f47fcac904678e15a7ccbab4957a647397a6c910ef97c6227798191cfb1095df8cbf3a30188f6172c5509edc5494e2083a776aa0ae2ff1de89630535f8831faaa11ead0310406c4f1b525f62061eb379c915ba89039603bf2a5b14ac692aeda2287ec71181d218928698ceb3aa09daee8dfe69af8fa4fc4a75347bcce44639a009a56896903d87cc487ac5308b523395393ab97df98397765c7fe94b92f35aac00ae75fea1ff6a32b3d6e9b3a7900a40b31e9384457d848a59bc9dfee14bfcce17412ffd5a2767c0020ce9378af134a9516ab19b31c72dcf0e80f4f3e6ee3995b4547b751eb2078bce9cdef26ff7a9e25d19f5c838c240fa4ccdcbbaf8d22fc6499649bf1b8497b1a9759c8c0706ea652924725237c5f6e7f15eb9de3b619b58abf9a034d59225e513bd718c0ec317a111c221f741517bd0a00239203d41ad715554908a343336d1e10d741167f23ca04be75f537387073c72c8dde42a85e4ac239340a8fd41bea9f8cc087346164561b3e22da8ceca939333a5ae6eea1ed4ac5d3952ccd1b7588ff8c6891a40c99737ec6b3027af8ce6f9aac61b30faefde6235e2ad60eeec1f0ecd433ec30d8c28dd03293268cea4aa7ba0d667698af2d82593b4d33dfacaa3fe876c8889de1439fbb56c204822bd628356e7ffe277cb9554e86a6b197765f865cea5739f045b4bccf170cdd08e94c6cd1b2484359807bec9d5bb48364b0df498ada3a3749ea4d8ab174f1f688ebeca28f57cfdfa36d53a52d95b022591c120142f8ee9e4b575ac298d3e51702a9262d8b92d090cc93533513f2f0c894ed0c9a6e5886ef15693cb21e329e0ff826a622ad0082b8e5c7e823cbe4e4f3768ca9f56f293f8ecad565bb5c10502d6f7d78ce2e46ffdddffa0a4abf8141e8e3fc836745c94941a8ccc6d25a31bec9f80c3b2fec4c7929aae1665b1d48c12e9918d2398886bab7b22f0c03ce8adab9cfb91176860a6feae2505b58fdbe1a0f920f1326369643ffea294fe926fe95dc04beb98b56f88a13182e971ad820761a7ce073e04b089b0d1824af38f0b42f93bbe46e882fa73bba97181fcd4d8fb7a43bbbe98a2fc55f381f5aad347d3af7427640813df118c198d02b846f3c896842807f6f3febdde7bfec920737e8acfdd472ae69abeac9338c427d1f54269de85069c9878a37b9a13fecbc3d2f619fa33517b9e3890c94fd18ff3738a4e891d26d3a3e21ec55e7983e1171e41dcc874ad56dab13c3ebd557e2b97714dcc17f30fd1f0b3f5abba63ea46f9e6497afd1b12bb2e707f59387a53569451749a8932ea603c853bc8a63cd3d64e0ce4673f0b030edd2f4f42927eb7e76573890f07679d37030116fdfbb7d384d41f769ee0a11e0a1e762455ea739cf7e77542944955a3489648bc4e7d24aabd26b2451461a42bd89c126a4afc99c5fa3af0fdfe7baf13c09a4f27fcffc320cbf8c3be31c96884c6134637f7d8e00076aa1445252c5a0a25cb5d2587a0db9b9676778190f080a95c4bf0579eaa48cc62da846751260dbe0c26abb7d0ed4e971a55e6f818320d0a71bf225e78e04283f1fa98e10060812b783f7710f4f8d50d95c744dbcc8267312978c7056fb1a3236d5b2c43ee0ce5ca933074fa0c65614f99681080084de9d6b3c7a4f030b3702176dce7af0ff98df7e08c939bbc1ecefbc292978976f3c06cb02eccf61061c696f3ba4576996cf9ec03d61c45c193dd473f0461e5e6984c750ff2b609795e643700cd6e1c11dc1a03e89724a74cd98cd5394ddbb47d1aa439ba1f95eff82ad2a72f43097621f2ac58d9fb196676f8e32f2e4c4add73713d35100811ea94d88686cb0187d8e4aa0b182c8b4808d5cb73acec09a72a85c06939a353f6dd68be5b57f96cee1d6dad98eb9837128b4c801dbc68db8a9e87c7994a5cb65ef7d1d5ba16c167698a44e0b18123d1af19c98412c9f412f51d9f1008622398f93c92e5a2e18caf893b36dd5aa2e114e217a0c12b080fa0b074f1f08b5c66dd635980a71f40f162c67919f7d9bf6a1cc1a763a527f7627679835b2a8ebcb6ea314febc04f5ff59dada24926a0e15fb1f702c59c6ccf1400037082fbb416d03bf12c085ac7038b1917c22301ffd7986eeba2fdaa72d451168445ad0fa706e717fe2da1a994033435ac397e72ddad610e7f981542ec92a8dfbbf50b5569d9249201efabab19b0824b6296340de7c22de78691d3f5be73d3c310b2560cacb24449701021051baf6c48c66b9c27e9a1acec93e6e7b92567d31fbe5a5845773702d78f289e1cb7edff4e3c0a22d9d18bc909c68d5afc9c6ce933f3bb326fbac47e2b9425b972b24c970885478fd9ef0cbea187e2672588879b6a993c308b8887e5c6ea3f58bc6f75e0408006b4274324f0aa84f2b42b313aac155cb606a10f76ecfbf8c0a2fa04d6c3a9f9effc65ef112eb562220549b5ef1ea18f5a816898776d4c47800f8ba32679dfc228c86078b23cb707a352b8d4ac23d682ccbc20ff675a7fd2e7328c6dbe287fb21bd735cc657af437e10047867f5f31f758fbc5e2250738c86b25ed1711a85b85e128ff0bcc3f74066480f08dd465eadaecfe8b79572ae7af53df8efa96b41a1f35e4dd85e4304ec23eb7b0d8cd65c5d0ef711912c47879dfb4d44f687d80a62c6494e3ca6f7e84865f951ca60a867d18a8a860bd3c060ebf74c86bac787e1d20ecf621c3dc032bd5ee61c315e2527dd209c075184d25e543f58c883ab431846dbbfe3fc6cc8bb94511c665e21999c4ee0590730e6f602705cc57d9059f984daa63c5878adf78f9c9976ffd67bb3da623d7c3791ebabd8913798f5de853b41b348406cef0b7ee18528c71d554a094ee939d867a0e1f03117870c9da1e0c0fa36bd1437d1f3500d03ba51432edd92955797d3b4c22ccaf3ff4f111209a5e2d3b88cdad13594a4afd0b74efa9b1e2aeb4f69e605b4c3c472246fc8054eaf1ed42cdd033ec53c75c81cb96b5f7122111c5c0e1edc65286a5f9a1b08ef24b498e8c98ed99b249102f0fdc4487c07f26c11dfca9e707937cdc21a13dc8c058f9474bb1df6b0635f4e9bec03f9716a36c3e87e001b236fa8020b7ba90339f01ffe714465cbbe243ba1707058887311da819c89425e38e581a3278b446f0a9bdce321f6ca1ba978bc05ac736b7090abda783c1656c6e87f164cb6eae5c7c7300cf64cdcca7d6ca966c993ee14234fcb4a5ab6e482c3be54581f3fa744e4a90ba00c14c157dde46cc802fddcad86177bf2412e3e785c45f3714181b5a057ee15bfa050ea04fbe3395c05e0afc4706d041527e7576faa06659d5acf97eae239ba645085f278ea97b90ceffcace12efb73a16fbbfa225bdabe22197cc311fbca8de75d5e9f3b93418f061d90ee84026ca47a5a2571aa30841d41ae56974c4c7c1ed72fc2d78a303bf0adb416f54b9f16da9293d7fd437fa227864e37f5f435978e63cf711b85d14ec6231a23fd38af77394ea4e55ca1b1ec6ec1b39bd0244976e86c6c391ea2da5c56c37b418b6a4d7ca2fe7e7c1157c5c14a5299534496ccf7536bd7b068248352a56645ecdf2d8c9cba52f52a82a5cc060608423d016b3cc0979c068c20e95770150dac90453beec52f4142ba25f16b557a2df3112867c54c9046947b5abdb5e0f5b427a36a17ed157e507f9642e55c7a72a27542e0dbcbd586693f9d972a529efed5f1c339097564c21a2ec7026bed436e1695e33df6640192f0697c507eb5aaf2ef89f3e1cf689e6394b377de547fa0a191a4aa4867db656a4999a2afaaba94e20d32d7004919f09cebd50f9fdc080d497af7f935622fdc5c50f7b7dd80104fc9f3740d2183d1bf32ce41dc7cd89253ad2d68fd27be91a244a8d33381afbb567de99992fe4c4e78dc81074719daba997015d3d603a1ea4eb7e91e9d53e2922930bb3035432e3f232d831ba0574031bffeda9ada30795291cc8fef5aee59c20cf6948e1aaddac9d3044ed57556a33e0c3bfc22dc52349ac62b2cff143155492310d44853d19c05e1d489727d0be83b30c58d548a5191c35a49372da4e3af7cd4f2c5bb7fece97ff04013900b44b714906f681ddc3e2485fbfdc4fc92752c818242928484734cb9d0e643b61c0c0acbe4c058ee8cee22c99ae180102efdf8d690403a084f5617a6235768caedbdfbf6299b283ca950592f411499b524d821c8ba62adeda926e02fd9888b99243c183fdc6184012be1b489b05ea958b98fcefdb90e709f219592b179519e528818ebe38739ce39a6beae5ed6d44b0b82e257eb3cb436ccf8f50b7d40ac880b4aa9aa34a73aeb66053a7c680e64af05b915805e2dbf24e82cc0915706dae13bdd4e7817d1b1f70d56f1551635f777201df058f6461c14aa8a3822f8fba3e33e606cd58737c79837a5c7550b8a1a375a6200ada6fd49678dd2ba928c344bacd594d49a10762753f1eb4b649d6249ff5432111436b42f6d50ed52ab3500ef09a368adf654b087e7cc0b616e57ac4fefd6bcec34c242354b773098f3861674d9b10d0cb38a001f375ddb38a4773f691649d8d1183acd54b7c7b2fcb00891367aeaa2cb4dfa356f9bbe42f4d2710109bd3bb23cdfe0e92089ceb002abb8b510f7a06c603882e3f6ed0b687a026ad771f1df0fe592cc17f94d59010aa68791804c059da9ec14bea6b5e7eb5861bd688b328a1e145b6b85e699fb99a729d962f6a2b3ac0362364b3411f435bf307ab19c3637d4b3c6fc26dbc38d23b91307d4956b25431dd480dfa3fe5ed1e6d2e264d61604e0b978121d4d19a89ea6addd05f40c3baf50bb21eda39151d97a56ec1fd79bb32ad00f156b685a2d1884751c6a9c34baef834c5ba293837e75d0d9c46b93022ae75cbdc1fd507dbe6d0591f96d66f1ca4bd1d403d84e499b6602e946904b54f3324ca2a4012f4c422276ec9cf4e571767b7acb02501984e0a2418a87367957bd463db2a489ff6e6542275ca2a180a12f60e882d64343348a02ed034ddbde32d936f58f788563d7155905b39cf3f1e54a4db9f71143618d5bfc418892112317bd4339902a08e751067448a58e2e48ee7de01f5bd96131ad1872b8da0595a1a685ba72d29bde7a352f38707a7116a4db666a149c67b787bf0a8d5bb3882c5c5e6d65be28ff751ac74b06fe05b179450b04e71a61882cdc4a7eaab11cb35c35f6588c495b5d12d8ec592f9f1aaf8bae703c869f9a1f9a537a4558054950eade894c851e2d1055c9381d39b9af400fe8bd55c42b14a1375e3b34612164dd2c265ff4d4c26a48138e299c328a3d2c51308f7c12f630975945eea5e9d1b60d3188491ddf48358a7c6bbba4184f68b0f29e6e410f8e4e2080423af59bbc0947ab37fe3927b7ae2c91350a424a22effc8387cb184fa71e6cb5f94e04e5619525bafe26df20e726dae880d1490d84f7b6eea4f0dab444a7423c717eb84059f5f778a6a548903dea14c9d60dd222bf3a76b0579b415ac67e7b8ee0a37a961a853e62ce44d47adeddad8be5deb66b9b4e48e580156f089c4784abf03cb7a5e64431825d0683a73e274e55e70f169d103f2d22c49a68409e65e0b647746082a0e53b79f75a7daf3437f11bb958d404ebf29a1f3a6193d34f65a4e6799d6a21aae27a4e1fd18b9eebb266fd5b7f175e9461f733ded43d0c8a93fac1b9cb4ff515a14212f2359b2359dfd1e5dafecbb57274209e4074156655d29eafe13d8c9abf639850a3b43f5119662a86cebd1e5f8f8ae89f4744b1d6f0cd64d4b05321941d84b853253d2feed184bb9af9ab05f877f99268090008a64953b04155a11418dec79036a75dc2557ffaa681b7d3b57d8b9256cf0801f7d665affc739d7feeeba0db42c7d2c579e84c505d2137892a712528e2dd5e5bbc1f83ad56216b283c5160f3a639f4dd750cdb6d281fbce6a30b594ad62a9bd07e72ecffdf5741080f025b51c5a97537fcbd14c3a26560b223832ef74704650bc315c89d4dec076f7d3d91fc5f6e581ff8aff6b33cfb0f1ff25e94c63d6a270df74c60e669385e331de42640663b964677fce95cb928b9971bdc762e34d951d6ec90a8289229172d8fa43ca5811494472c659438b0efeae8ec61afd79fe4f71855580184399fb8a4f23712f98a0f11cbec2340aa0ed15baa7806bea060a1d93449707cf5631918f76cae839910e73a32334b3df291e312bf18237fd752e01ed3c6e423cb2ab76eb5a6cb33b2d65cd2f69db6de9b4e3a2d20e6f84e67c0384f372c7905a53c857e7b4d73f54c526c89656d8bf4062b9b79eb40654d35eae250c2beb004a25a1ca38e922546182f4d05f20949e648668564dc4ba6b3a8876278332567b554da8fe719ab8beb8ecb452e277b69ce430c88eeea6e2f6e693e01f3eaecf8c0a226ac7b84501406bcba4db721f291397c82e8a6c9d38d6cb32b1916d005fc97bf3b8522f13ea02a2f418007963f536be80099e460e95c5939612f5d85428872092b55746eb83e55d4dc5eef9d597a4aae127fb732e44aac7562de2043dd0fe1ad9baa0dc101fac64bb8e3d3edd9f05687150a4cc83b2d4875a9905009f0c76fa146e705fae3b3200e3964399c7e2d4b4cc019bbe62a25179b200bb9d7fea01ea4780e473d2b124d217643d859ab344bfc9834804e2ed45698aac7a14369cfafc293e3a0342a7b0b63b874a5d3e428c36d7a0e10368ba086d99a1fcb10f9e30e038e58b1ad36222e76ffcd78fb556b57b9155f4985c9092af43a46176b5b7452b0714cfcc92724dd2f7d2a3b1f4b4c88596d08ba14330dd135461368fd9f531a09da99ccf9dab370fa185dc609b84bebeb5a8df2d8579a18516e9d7b69ba5a4ce97b75b8b3b256b403ecf2e0e3b3e91b7b07d861ef88179549d7a4d19302c17166991ce37b1231a534c7c5ff2ef606c63a735073f75ff6d82b9bcde3cdb504373c8fd35557e462fde0ac5eb7fe5ed56ba9f342e9ff365809d344aa3a287263d05449340a3915741ed5512807896f58c91dae96ad95acc2b2f95596291dbde244afc9cc7f4f16d12ce9a071e840ce75f7df20d4922e24e4cf17c1603b5480e8e163a60df75a5d576e883940cbf1c595b010977bbf3f4568341c72c699758563598d8888ab5de2f13daceff2eabdc2d358cbb9abbdcc7bdccde3c3bcf9e3fd834c6bad7ea708a24526a247b4a474d8beab21fc17692175a46c6d078cd6bafc03452967bf0a41206ef594fcbc39713caa31ca2d4f35b6a5fe062d00ba059efaff5e539cd6defadfc9db387e66a715c98dde9cea5186f3c318a88d68c4edbe535f2528458bd26fe9d6e72a6e0d7f007d767c43287915a64a347da56cabe9fac8cc62a5efad76694b1f718c23c4e74ffa86c3d5adac9c7bfadab25b2b4ca31dfc3ee9633d37d6ef9635442328d5339f0688417396e941cc542314c43b8b6e21f810921463b6832e60c1a1cfbb15b4ef0b09734fd3187d4a21059e40254fefbea8aaf8b6ad0f76cbc19b62795008b3589710b00ea63572f0ecbd780de44a94bc5cb9b170f68a4e3cd7fbd2ad0dcfc12ddd5ccec428c4f9a6834509386a98b03548f800423132e2df42b316084beec04533349b60c497d1af7ad20ca61c233e8004894fb3e47f20c3a87a28261bdf4e3092c58ed1bae9afd1a76ef0bd51e2926f0cdce4fca85db101324ec65cbe71867219195bbf9dbf32a2a79bd675d6e392714129a418ab72e4a30ddadc942c0037578a90383dd6a1e50e5807fd0a6d26296b6678d0a32dc2cbb4ebf643bd8f70007accf7117d1fa2d7735d50746749441148a4391975069a9b0c7f36de03c507fdf76725f22808b27b239eda5b4893ed3be9066e71511fd1a9f404806d99e10be31b0b2b55b37549cd40c5b3138be932587cfe5f7338f455a65bb7bcae1ba936c86e09163fa11d5df331c18716a4d99f4b4edeeb5257190de7c958948ee11af8f051d83f3a8b0da5dbbe47a56d266b25a3d96058a0cfb2ffe0a6ae39894de08855a9922e9f54243388244c12f16e94d446291fbf5d25f9d928aa004787fadfccc6a97405c8e2945909038cb0c5fac468faa61b052956bbb994839caac90fad05dd906185117067e15d110672cbac59a2741439637c51db4c59df53a3b64815551ae8609dcb70f890b3ef7e9ded49297117bdc17e47c8ce3f3cecfbcc9945950febf71a8b7be27602c245fa2287c0fdee92e5de1163101efc3573ec3ca98ae0de6d3a5d8b11c864ca399846004d63102afd1d2224a126e8b61c6184aee12a5eb42df19b1af4871cee81e570b5701c94bf2862ffd27bb05dcf325ca3bf8b7ca78b377c6bc502db47aa3d274cc9ecc12d4644e34eaaa43c447db2a02f3d7c5d0cfd35b40789f2fd6689544bf05d2d2074880cedcc8cf6e777cfb0e8927f617d0ea07c54cdc3a5e04dca761439d58604758f1b98982c8cfa61faf19437d7afdce43d2d75bfb56121a216589745852c8d317d1ee2b713fd0687f688a51009ff2efad3e030039103bc62891ab7f77c99481d37fc53903c0394fc6e5c5213dbea176d667ed4631a97775d05a41877589d3aa1e03695713a0b5feaffea3cb8b0c8498fa22963cdd9f046b5dbd781af257fb19b25122b08f270d410c5e8457d37c465695173cbd4da2d0363c2ee7ecf58e8f1d9544829eae16a703f38900a6223351d349a35e62dfd3a21b21a6775c9d08924a24a58c90ffbe559dc5fc0ee6dfb3f7cdf14c63a33d5c545c2874701d5ebac9c2e2bb6b45409d4ae624d426228f613768dac678f641c39e506d5ad96efab228329ed684df8abaea7ddebcfc01ee657c314522680e2b1a66fc72ce4e654eb422d34b4de671fde30ee6f1eb6c5613698e37e7e86b1f32ac1e29e9e767509a1edeb64fb01b8520f8941ce2e2c0d07f7c79a8b85b2b782f346fefef5021f5435c0eac18fd9aafdcb85c8c48bba86a8f926942eb80f769bfe80c1a8e0bc1041e6f0cbc85a3d85f0f57e40aed74a0de2fd1eec0c57658ef206b74a3465e0d41e3c8209eafc6967df8cfd8dfe46bb92d5ede9420055ff715d5fbbab8fa98ca5ff8dc0c80b387d44e2066412ba4e0f6ea82a13a29ae976ca5653f0c87709c12c3b963bb9042a25f4f5381990faa2976402e925ef9bf30a3b7a1769e2017d7635ed16aecc21570c4479cff7546d85abf7919d1ade7a2bb2ee0c4a08f9144f0f97738e8511a384cc55b15604d9d563cd5987795556e22551027203bedbeb2397d1f3fbfbbd3e6372a546d3abfd86c6542f18f747053226be9db776474b974f8d66d1b3f5859d09dccbc4ea0ca0eb8ef7e7fd75076afbc07c790822124d7b7c33f73a4415aada3e7c6f1b9c0d3f0186cc0ff4e76afd864e5ba2378f2478cea19a969d69b7ca06954cdd1e883f0cda3aa16f3d8d813a4c2d5c873ba42ec11d4ae88a2d13c96da753f87207cad4dc1c31bd351ec174337bd494c1ae83230dc6298eee6077a95234328a5af15f2716805f6ca961d6260f78b6ec904efdfe9b7d056e3f73388a239be226175d2395df53feea1cd103214457f3e5bb7c8e982fcbe823efe398ffcfbc6b9e09ae51b299f8a58ceac060a2a565dbd95bc0f2a5ac8aacf074f5bfb2e0ec53de62ada414d963bd384b84f1e15f9f41532243444fd66acdfe362f0e6860739b8ef105fb38e99f3896bdf5c3d5d0389e70cf53acf6ae7092fbdddd81cb8a0e8763d7c890452a4aa78086f11b11c3170178e4e3a315fb72833e4b8d2bb54d6d3aee79e8c7aa721493a08cd9af8bf056d4061908c4515d9c2ff97693b2d7cdb622f6f1bf80e4fb442e417211f563dbc46e87bc7d262b85ed4329f186e90a8c0031a5d38802de473252bd59837ceefa6476d6f82056e6f3baf1469dc172a47cd9e41069e373badacecd1d321d5fa4f3ec1242de72cf93ed2ba5e985e374905ec126ea5f55e3039b1804c3c24ebac21047f04c3c1b115746b90bdd524f5f1ce4adf25d7ac5df8228a5da33ee8d3cad7e1411df8d643c4fdeb76a6001adbea5cb85d2700a01a1408021f86bcdd2c165d52471ceaabc45c3c8c6458afb67219fdaa6285fde37c075d2c75243561b00ab5aff77e98334b5734d5e550a912d0c9dc9604acccb7c806785eb0ec938e9439003603bede902f7332d4ccdc32071fe5c94786aca3648a775fe71adab80c03ca0a5b5911143023c2dd52ef3f9f7ec86f84ca85305c9f8e4daf0538a0851abd9dcf7a0f4141e30ea6c76f90409091768b6851bf3990c4bcde19f1eab3cc2b5352efc7833f2c42f9da049746fe520b3cc2ba1f1b264c5e65f90b875dd90827094122ac9f5a94cc5b621a1ba54db2f428c9fdcc1a4d0b2d0200c258d25db5b726ea13bd00dca225d7fb2cdbc62488f8b7fe1b4127c71dbf6c03882b07c6f29d48d33ceff9a9cc483d68e44bdf949eb1ae5075ae058f2a2a85653aebadff91ab23a42207ffc281f6c73b2e7d4a2999cbdd5c4421e6223b21a268c3d9fad1b5aad774f16a99370126bb576cb6dd611cc08c945942e303028e4f9e07fe355e6496c4ee9588c5691b57d04ea56c81295aed229cfff6a0749a3830bba2920578da0162c1ace6b32b844bec1822679425185b6fa85eb9fe2486ed67b0b9f848de98cd94dd8e83c3e0d15f2c70fabe67299d3938a19388d4d2288a15bfd5650d1a7f2673d464929bd0386fd7b0819ccdacf680c393fb9680f1d490d445ccc5ae53c89745dd6f013f1b7aa6b6d9edebb301a72094b32835aa52e21267f54f58cb5f8523986a3dd0abd4451e98632819551dac434cfa983ff8cf7495cc0394b62e5d272f4b19038d6173b21cace0f75133725f87d92f678e75e2f4e036d5d0170aecae066b90162724ea5c583b78d3c403eb2b097243b3e17b989f7d3743528da0ef4dd53203f118b16407a2e00fcddfcbdfcb778ecef8f88f95d8b68ba5520db478c5205f2d66a0571d3de0b25fa897aed9114dd4c25e0a7c89b3dc30bfeee3eacaf71caad8c6a765cd5a6fb5f1c1ec21ac55ec4d8358ed373d668c7aa3ebce8be8a177243aa3da957c0960439000a303144faf097dad9e3177169bd65f5004ca12058d21fc4321db4c743d0a632fff38f46b2685bd3359d9b35f1d03f25b1f5d1009e434942b484b8d3352dbf89be8f305073f8219532407ae04792a96afd003b8689238c405b4493ef5e8ef7d3475b1dc382ae8cb015fb5d6b3475498e1c9f90797a52b79788f91c935688f5feaa0438b9a15e63e256738a203a535478ec97ff304fb7e8211af16a726a56ef17bc0850e0fd1f2900a84117c16be7750620c22b876db9e98bc2ae31bd61569cc7f53c8952b0ccc032e172fa47565249861d8e7e439bc79b454268f7f7c28ab52be84712a5cf8421312705e3136c9761d6a77dc5cd84d07463408916d987180630ac242ba7c72b1b6de571e381e81ee26feaa3204712cb0b83ca2f90d36dca0bc9f5a47b8bbee72a64c21cec425b35a7da862b4162d431b50493adae397d8f27b289eab9754ca35f8688e515f6e98afe4dd0d2f794e37e2f2cdd0a7821f4f85d910deda0a6af8f4e651a9bbcd1ddb2ef7f786a137d85b45ffaa680f0747dbca7a263f936c0f49e171ded83696542a614e58e598f92cdd71dd208fe2cd87f34d24c052e64dc3f067a2930d68dbe314102e3d91712813771113af3a7354f5bdfef411a00d96f80c4f818be400d7ae9c340b73fb4bbc2ea109e4d6b51b8cd9fe0580d5cb544335f150fd02d63988ceb51d4142b4f4456f0c4dfae26078b1aa27b3b3130eadb8c22fe744ee052d7cb3c7255ba95a06aaa24a2b70bf088feb0579cbc35c50f2f349bc67328b31664824a939982a96fb2181943e8878dc5008a0d4fa84b69e6500ae8774a26142c95981f84ebb1b61a73896945429efa660ff76049b264c3084d600560a2bb8e05181c59fad3819414361b44c9e04aa746faf8dc5e46e31160ee25a995134990855b30b90b722d5f6065e701f3759016f375bcc108e94314add5bae26db9590c85cab14eb46300ed1a4a3eb96ea7aaa62f7e26c7e72762cf05e05b5203d74ede52d615e7563e2a42ee5af4a2e57a7f8572889a1e3b1a3bee4ddd6691b78b2b422bd151e7359758c19fda9a058bd221d4658664936c714f1bc196ee91aa977282c698d2b7f89b59d582c6bd1a94f976c468b8706c831907517325595145304805cb10cfa62c3b49560015810bba40510c0d503b9dae69b8c9c3980237b2e44937586d7f83c1dc0afb174437b3b9aa91619ae221e7c0e434eaecec1ddd6192ce6e7b4d033c8baa11beda37cb4fd2a822d38cab80c7ae89fd3b77514ecf856980c6bba50077c268b93aed6817a94ce7ddba0f9f457213b4804ec6bfb2ff9501831a907a457982deb1ae09638481ee29afb4864ce3a3ad8c03218be15d50cb7d97c7e66af6a64cce3e797e9388538738867f0991cdc8de0919894d477494a8c1f0879f0a1497847e4ea8ac67e37867dbe9ece219ead32b80c8e3eaee6ad96eb5a9cd0e0d61fff1d1a86189bbadd9e3e73d0c0fb79e20c1c2c541101a209ea59a0f477c3ff3b53ff0b8bc1e98992ad45c0e8fc47d67e8ebd35c29c274c8da977032cda653f9a4807eb3ef0a42fb92188ff1fbe6ba1e279d25f3fad98bea61d3686dd93d7a7fd4a7671b89fd97754c1066d3a590b56044ec6940ff8093e22fad6bd9c265cd3c9d0c35e7cc3ec1636f21a8bb859241b57b94f3a028b87da8b3bdacdb70368bb3aeebf6c832262c5aa833576105ed2169c871a6239bd260d2f85c4f42e43e202ed2e4bc5a71a3dc5804351d1d81583b410d06783521f72d0de02a1ba1f4fe01633b7c9e3178b60a895a97c80cc8be96714bf97f0ccda3b785408b40e0e36859c9c69b531939f510a01bced87929be3382ff949834292608fce1242d2ff6687de8bc0d5dfa260aab4178d31ad5ae2aab0b26468fdf8422087bbc6370cd10583d2c9101bd2cdf3184fbe0a455d9f988a175fd20e5da1a1075eecff5af5b7e89fb079a8d3f3988be15b4dbc206a7de94092155591f4d0cbb2711334eaca65bc19772ef27534b0f95d5a60bb22eeb39bc36a81dcba79c6ac2aa6dae24551edcb82905742d71d593b0b447127c2d63b3a2bf1ef9733d22d61a216b55f4ed6a899a8b468c5f162aff9deda7a02faf4f159e307bbdf4f488898b7ca0147c42d2dd88b4ed2e5f4b0bfcfd2236aa5ba8a8a13a8e943615c9fd032e04e8193feb5e1d16e324c838bd724007cef32a95622456b4a75df83ebb90e6f52aed4f05a2fb1dce9717e1d465146202fd795cf79863607f3948bdff83690323b62ee458a6f4c2ac5f559c85c45d3cce6167869da4d41bc2dff99f8ad3b10fd033aff30732914ca16ed3de7da6f3445cb011d028b91fd3d7d8bc7a342257e366c1cd07a6fdb640110a976c557ff009314d949a3f312e27cd9756daf81006b4272201020dd5c99d54c352578a51657be84b96f30aeb1e6ee7262e3df4590143a4256f72d53b393d388280dcfb26df8fadbd61a2b5b4b9941b5aeccae950b687475e9a1ade216ec77d5fc2376d19f349c24f262af3c8330d94d1ff4bbff330fbca27a08acca70a5f584f94fd07eb1223fd3981dd785b571e31ccaeebff8ad14abefdc05c85f3ad35f19b9849d62e2c66487da4c87e35607603caa03a57dae5e206222d90b414ae90e52f096b4a01d4122172b7c841621f3dd3fe09fb1ae1077459934fe94af97960c4b017c1b750017817a74aa083eb37019994e27abd2a6450c353110bf771957194694628d5b597d3530ff93909a686c406bed3a35baf494024ffce31b546616a4c0504b141dc44ea0c8190931b0e071cfb98c4733244a537afb1fa153e5b213d5a191457fa7a72b912845bfd0834c7cf10e13044249c4a286d3ea48191e3f2711e808e18e4dc4e1b44706672a0b8bca70e72b32137d82c07b8b5619171a8a4b06c837f1a48f92d731adcd9cfb8d98c1a759a6e7815a8e8fcc03b526ee55ba33e0ed77280521cc53e1c5bc49aaa8e9963f73d3c858dfa41a4092f358cbafd6bb94063733b921f03b7688cedd4d2b4e51dfbc7cf4e103e772beb9ecf1bfc4f363232df495c7c32878130798c1a124ca2e11bee336535318c2711121a6f815bc9f38a3b0464e832f8d13a43fbe34fc3768aa6424e493543eeeafb9cf0815e04aca7dfdc0fa59295be405e23c24ff154d6bc1f9752296f84143b7c483fb76fae23443fa670069bdee20a0cb8934a6773f4f8bc2e5092e626fd523cc8e41fd4f1b228aad3de6106aa1265c3a31444a1452a0ae8c042d2be8eaa3a3681c54d6e5f0a1394a4d5443c440e0a9b7b88e09433dfe9c14b5109c2f2019e3be0c8ca66559d2267d6dc1389d20033bc05be40ba82463d9ab62454e18cad776e00bbc8a7e34cf5ffee966e9c0b85b6e5c7d48a8f02846d7a44800cac5ae416c7a14c845fc0da032c575e3a0b805821e6e5b55715c909fa9cd685d8da6d3941906fab22824c16c933457be8cd72ff3926505a8a51ff400dedfe4f24c5ccd473eb966c8647a8f5ea4d773c64be41db861d4238dfcbbdcf8b92e2cbacebe2b79149adc3b5a9d70c9127be7c941e5e751cdb10723312f90dcf5fecfcf56d81ef9ef4e63ff1e7f6145061575f8312d500308049a01c367e7bfb604f14e2936fd582869d8987047f75960f184d0e226854a8c11f51eb4cff486c54590622458bc32d7026dac7479a01523b677c77b935df2d6a2c979bee2f46fd2d9587e39479dae874f92732bc885b9b60088e51a9f15908d67b4188b8f1c61491bd9bf32fe7bd082c274129b214b08ff1b50805a4ad366f79ec96eecc90512f5c7f2088ee5868b6811f4ef83c17bc25d08bbd9ec43111d057ddae714f1028571c0cfe054b8c780334c876f2b00c4eb7d7415fad03e5a1f48a2cc1d5ea86fe61937ce764e4679a0958e3a3d02ab9b52c5336d6b3c445f5dfbf85071d498b70073903d544fb7bed66d5c596360addd33383481b1d48f2e9b0b9000a099fb007e3b8eea164f6128b03e75dd17572f9bbb01e83a3ad3a4137d6cd0f71c42113e53c6e7c78ecdfd18dae9e89083ac9241aa2ca96a625246a7e6665beb62286f45f04ee7af5e9e4e83ccb5cbd1ac83aefce07a624ca26f73d287d7e3f51748e682bc07fbc2d6fd8d33cf95afac4dd365773f6c1d8fb76876f111329cc30677811d5579b59ff00b1aae75c2215900deecdb6e6a05484690d6c47e80dd2aacc89ecae8272c3586408022fef164cb0a7da4bc97bbce99c8ea3c357bae3712c678c2e765865a65f659c5ba906e82dd1dd34754370012412c9cb21f430f94d4e134d3049f88b9ca1e6cf26ad1d20d007d57dce3e155124bd90e9d5eca0b0a823b91d6bbe7a2fb0b86127519bb39a5565c6f80090d3ffcadd77c4ebc303fed2de7351852e6c4fae0d06ce66bb0905c5e3232a2f556b55b29f0009d45a48edea2447c36e83dc1f49a2cd13434b66652fa7f460594a420ef9536fc89c7c6f1aeaaa8a4f8d1eaa840a0d33e750a47a9126cd4029d295eddcdc298df5b946bd3704f6ed57f9a70d9a1611af615b9883b5834472d8c2fe47e324703ee85db2a68955599740cad8942fbe7bc73bee67bdd27d670503c0bfd13c5ca966de61c0086ae18a6af37292aa45e131445246f4ce391052c1076e67cb73dcfcd376be09a5c8e069f45d2ed5829421971229d20ee214b12ef5813579790aa8ced23494b6d5a460d0d2ef7b67c105e5b87a67420839dd57f3e4d607c042f414889901c41de995308ab01eac407a3828e78c542ae133e5d348a5ac3385e71a47dbf3a9178f9b4321cb16e47c083bff20a5196030bee1cf7296236f491de4da7a0b745bfa09b4ec92be663dc63da5e1b01b199dce816d12f76bfccd0f7d2d821f04f82dc1e2dee1530a47ac36d6c098a7772240a3adee7adec30f3a88c8dadac1487f9683987f0acf5eb2b31f699793d5dd762451ed69a7f8d7776d030efb97c60726f2835d595e8a6e9d5e5237d875eede2e46f0ec168c4d82a4e1459fcae9f4d9980228149c6cf5e6dbb90ba23d943b954c4a0e5d53f15364e255bcbab7eed0f87b6981c219d687448555f1e22c7b1ed6cff43e4605f2774e824dc0762a3cc6f50f3066be26d02f9c9bc71d17916863216db03c0e917ff1a12a8eec20d05b223cd5421914eda510b41f293def42ce6338330c99d0f1daeaa699a581833e48dfe70ef182e9c93291050f98b99666d4f8863e38fdecf41bf7104561dac21ff43dbbcc6ff9dfa16aba8201b4f7a309d53dc8948dfaec13afd82bd055f0c4bbc3303d935699b6f7d0e5383e23a323f3df229166036b23ead5976a4757f054a00e964ad0212eaff600bc2a270e0b110834cfaa3fb961a7cd20e9526d04df745da142a409b0aa77902683fbcda18ecddecbd3662271ab8ca5f8e3fe29394d8fc73ddc431ac8319a4485bd455c9019691ccf2d572511df7bb11809af5b9b1c7bf5178481e291aeb525f117b7bb8db0cdfe1f1914ee23fe0b99119926dc1154833c5751548616fd003755a84b93d30d2007cdb2f94e55a57fc3c63acc8b4404e5c09b9b37b4c8a2da4a2f7c42409e4de70f9f6b0761faccc7aaeef1c898263c75cd71157f76fc1667210f77a5eab0aacd3a914a4bae8819ee9696659f05680d6109e0b623c0689b7fa41b817e36ce4a0c0a51ff826418bc48f19c76f5a702a139cfe077533daaa473c48e05145c411bd4a9c2c4dc12e9e2192935268a75af5bbe5cddda4defc1d25ea735c1c6a256bd45e67a130229deb97cf568cc91fa6e12174f384757c292791593f0e06a53de82ca8a4324d648fbfac19a909ef241024f70e3c633cd9b64b9e93737703638af7ae0ef9d42927feb0e9ebbdd851b4d73a42bc04f21a5556427cbc892179fa9218dab4c3bef29a67dbdca28d4456f768b13169f1065e0bb2206839b202bc1f891a8ddf982d2207c226efe13cbf27dbe5a7f560d3ac3bee7120e0fe211092ac1e1fdc7330d7f3c8b71f85a056eb88611b331069a70d9b9430dc65c6c2cc81f4df64ca7112b04bc85a404b6f43df0fb07ad6fa5384d59931acd6be7474a36c0c039417d585f50ff9b18fc5cf20b880382221ede50dc1200d3746cfa80d926913117de0888df63c839d18936dd80b82dc3337646f11ecfb9e0066d9a3f5a115c2981fbaf00175380b6f3aa63be980bdc60c2c915df88fa0103eae256f8e2ff6e546dd26b6923192ac74a4fc28423b3126bf657fdc8821fdf9f6d3c866f9dcd9c4453990a8d583aedebc389f304432faeb4bb3197b7e9309fea3e99aedb47b59643b03058bebb3662a6f150c754f04437053f2e1df28da4680bf3121d8ab12f792886dfff7288cc0a0189cf2fd37744cf4b76ef59f2b45de4ac8395ed064222a605da1f392dad0aa0d3e6441e70f79fee9f98f605af95674a2ccb4705f0735d464d357b91fc4c33203b8675eb11e3d1eff99ec80278de7f5b270984c148526b0cbc247765d63d6ec6ef8aab2d488393a012c82226dc2f71114b64605d5a00ae12f3b891dac6894b567e3a471ed6726353c6193f87949d3d101562c74feb33ceee2a074e6c83f01565ed7fdff000215c2badd928803d301922b16f2024deb57954d3a9505e3864f58a0268f2bdf8535a9c571221d5b96534483b5d888f16e371e1433b8ca5828ce1a52ea625e3249f7359c31413b478ac562a76df99c4d92eeaecdbdf7d1a68426498230f17fb96f2bc2b2af2187e7bea48ef740cc647bd9d367410ba7906c8152c2e391ebdce690a616b4103108de4f90fc8cee23cc69d4aca7ef075075e2ec86e10127157e25bfc06155e613d670c24f7b7b1c165b105e0859399bd4e3a2cd11bf4644aef7f87ae88b5b66d021397ac6a6391ece7a0e1245619efc4936358dbde6b690f6861b45df9a52949a17cc745a2ef3d0c43bd5e6922bda8bdc3fee244f2072bcb1f5c6119fc51835c11205b991b3de6acf7b4551a0b37756311b037a0c1adf0aa1fb04e446a823dd2e3688cc4e406e8be3ffeb234f8088073a1c481345c51f980dcdc03174ae6151a387a76d6b6b057145767e3c87710222d4735067bb77a0a43871d861d1554a766c050f954e0cdd8e853376a7eec05e6d750942fa5c140b08dedee36b64e7d662ed67069b8ffaab7548f616c0d0c43533a6438d81344a4991bea409c966a0e00ad9e92caec8d6433db9d8d2ac4ad6a22236b36d7dc95742f506535a64303751b633ffe10d338548fdfd7f51905e9cb98e38ffc144c1e285fb3e58ee09a60c6f291228ae9c10ddfd128725e8ac4839ac8d049a07460a752966cb030e04a4e8005c04b6dd4fd73ee0d6326a78d0cb1565dc27931af4a80a316cb79522757421e693abc57f346f79db3a06bb9237a1ffe6c9796a4d6f36f951dc2a549ad99d3bcdf8fe782c74be2e7abd43ed583c057e07fafe9259674773ad666180a313a8045ea1238ac4032a95af2557b7eba236b30a270dbe76d9c64cdae7196c07f3cc4dc805aa32bea122421f761c38cb9fef1addbc34fbe186e34f376c3b528e3f17f8cf9c24e98b77899e75297f81f663d5affda951219029f45d69edd5d80fd81ae64eae843adeee698673d7b447b26e38597fb57542f331c5b850a64d83a73676c8c81313560f04fa5a4c6f7b008f084b9400e7f366e66826e4030a471d5e4f44dfde244525c2f9daf230ad55007dccbdaa67505687c00a4529da30aa96fe5cb05e3988b2c53762c61a77246f8a5d7f6fe7a39e7d164860926aec2a9f108cd635bd908b5abd5bc4c363bac5e858c1a55490aaca553bac86c9593f5b602867f8971822d3712a72dc268971195b815adce19621fc58fc6633aee24d40cb8df83db5c2f89c611bcad47aa2739479dbb6d5c8b5bf050c8156517019890a9821d114d0a9563deef572708af3ec5358278b38b4228df53cbf1de59d452392f69fd08a875c7e93ebaf918248840c202821ee1fe8b3a465fef9df28b2651bf64986b320630f5b46a8b0b6c32cecdacb79be7fbeacaa2410a206662ecc0c731b2832b6858369d9128df8563b89b39b1405bc09d98373c426cfad20cee99ef4aa89554dae017391e03f7f886b557d4d4ed110f6ae7ed42d9a3f994cab7c7223d777084f3b76c95fdcc4eaeca1850d580f29ef981c74b2dc24fb3f808a342b5aa75bee8ec489c52e61321d4d94624ed55e7c76b53bf9fe2511624f622c07733d69e92829ed3f347ceb2fbe45398ffca46482160846727a97b210c2deb50abf0630e5945a3eb0cc3cd4364bd02daa31eb7f6862daaef5d658d9e5b60114b2b0f798857f37af983dd1ba58c118bce43e1c3b424dbf2979a049e3caf420eda516c9ff2bdd9edb6fe62b27b1735dda72defe8ab0ebde435ff2c6ab2dca508dd571a99d746ce98b58201bdacec66b88e453cefe669dbdeb2b68c04388f5fb766f75139e3671032d71bbef4e41d13e721502d6ed724e6128d8362500bf4749b3cde056c6b12f0c9d4d2e76d12dbbc76ef0ce031d03fb747569e19b6ee765f0e59f081d0e3035c04b9f3022ce8768212fd206c878ba5a5d134e412e3a48e7c886c5ac0ae65a6b90a64a466045f7b6dd344d166c7a3932ad46055f5ae7799fa3358745e9a3181a8bf0acb86f530ce004e308d337618939b0ea8e31f72b4c9d458b3735bc927399390b244241e5be5b6602249ebf8cf00fb2bed9016322d22b06e48ee0a004e21301bb86bddeca40b624befa8e42c102621cca5dd040ab582aba9962ce5f6515b53bf4beed8f511b5a0e360fa262706e1bf425987af1ea7c474a74e4c1f4afd87b41feef484196de3d74eb0ff510043dd025728976f3e60e7347ad8323556c5078a86fb98a44cc99a6586118ac4d8395cce2443c01007c04c25afcf4fcfdf3ecb1b1d1bab255ff565fd0e27e40b30c18dde9e6f0ead0cae05e016812fae08a3b3afe7cfc978940beb60b88b0ce6419878491786e4df05912ebd0570dd85de52837181ebc765bde58fe6ee49c0605add7225a2f64aea72d75bcf7ad316f3c34c5e38a6156396c8a22e80246255dff9952a561c408ea28e7116511c154df40eb9da4bb24a4a516c25da331b4b651131273ec21e7a2d4999556895405578907b62a18a625a741fa196b238676c4724d4e164732cce189db6fb78cd4ae5b9957c359e944967c65522dfc92953a35bb903712763f8f93fc9a9e139357bccce9907c5c0daa06a0abd0e2f18016dbe246b116c28f6b5cd7005b3072379f24452a634c84cb656ae35d2107b80cc660433ebf4cafc6705af8504b474ff66103cd03aed97bae97c53ff582bf8e44ea2adde025e6894a93319e48989afc93b86d6a78ef1b1272d85ccc63a64a34bffb265d200ef85327433194bc0394698206deabd3cae3857a80b61caeb0cb912185ecd655d5245f692ebd0f12b304b9aa87182ffc2b772427c49aeadd0543f35a824fac79fad31f3a434f5f55141d12362ad5cb47f444939beab22505e277b9fd82ff4f9cd6ab5d9d926501a6f9974cacc176ba116a9cc92867581f3ed147893d78b3999f170a12f6b263fd5d70ace91469056fb67aeacedf4c563b27f2a871d1cfa377a7baba918d47334afdb690328c262f8288393c0004cd5b346e529b998f347d512902fe5ae09e334f145c5363258d1de553fb181fb86be30200f8965438b02ebb4866551eb35c359a3d55311a9059dccd463008e760ab5efca75fca8e3e795c598255c4e1eb7ee52db46e0424529c039528a362af45218d7a5d915e07b7d60a4b764d21ff86571de5ac756f8fb918dced624c9b9f0c27123e9d2ad310f59093d76c1936d216431547ef0d76a8b302fea9951e94783f20ae4fceb6300760e73381f6d7ac463f58c1f7ba05e5017be0f21a46676008e22757d1f407e7a42dcf9b22de0c9146225ee3aee4c9768d5e912b8415ff8b97eb50c255cd8d60eb5b747bb679c9b30e13bebb8baa800f2c9df9f2b4595ee669fcf3d68d14052efc032edd9c827c752a041a93e38b7ce35943a6aa8197022a2f68011a747109093206e1a843c8a0595c117dc87b1a652f84ab29591a5f3382ab81a5593175c23367a8947a728d80f7ac2e3eddf320cdb09691a49fefc58716245927fb66052a65894435825c8931f6404d28da9a85a19bac9800539e41a5acebd1353ea8f74693b9abcca5255ef861ce57024da4daf1a5eb57f1e402220499a1f261bdf352c5466708ccd76043a0e4d6726727b6a4dcbd1fd148146a3ed8e13f802529a2db4c924199fad4ece0163096b82ea2a6263cdee2a908e326b0ab02ca4036af012900a062989e88b0ab42c42766f359912f32416b769311e58ff6afa2046720f090c985d4b465450dac649520d849d0e82f7dbaf5b157ad9b49cf4386ceb4ff6d07e94c1d69339b862b54f749bf618e2dd214ba36d2102669b6f9fc474914ff82f48f88d7159f24dd758a748ebf50802c9c724dd49350c7b279116209beae43ea6c5b860c6d19992546886532f68c171fadcb8d0d1c0d70379396ff2c194884175b7ab9d4865a07b8a29967c9a5269c42b122aeff76ee79c86c09784ee91c653f6cf06ec0869a1f0369c4e91886bcaa4577375365cad091ac0f615447904ba174167d8b79933ad4e70aaecc036993450481f3fa0f8daca0281ff9945ce106505228dbbae033a56e5bf27711e0c5fb01876f24faeddef98105317a6bdbd068ee860f35643ca26347b0ca94524727b59e3353f91e7feaf48b100f06e855c340d5f576aeb2f001d3cc7547ffb2c3d38e1aa37ed1c520fbbdc8a778c239c7404c15fd7e953eb6b9941f7395f18f8a42fe6fb549aa9fd6d0b67ef7ac6fea5854ab0af61605e38bca32f2b5a7f0b2ccd870577af96337a22b69d6c26e8df6455de3c6aacc25197cc410ef77a3735cfdde647531fd6445de9fd43404003ab4410e9aa817915cbd2f95eb4e5836a62ceaba1d2dbf130a7bf500bd9dd5bf05cd4f985ab37bd6bd87186991af148453c79f1e693ebf78d9dbee4db156acc1f9245ad980ba314135e460fa89f0b251cf40417e9f674e4cff9fa6b3aa44167a63aeeedfa21318c685fe20f4933986471e2078b488bfdfa9f861a34d22071163059c907641a21b60fba1d77ee48f27f6ee7ffaa40f11f0e7045456eafea3d3bc2f5f3bfabc40711863f924b97a7e3255a1acb3b594a08ce4df2aa9bbe7a9ae077e02fd54a2249be4cde206089a82e16d5bc2f729ee4f9666118f8091dfeb5bda17b64103b86a48822b96a18dd3a06cce775548ba1d4c86f31c3e65f4cb66b08f4871ae598bda83a08a0583d0d0e95f82a9046f5c3b85f280682ac399b32d017acd802a2a8613745095c94e1fc9977cd945575078fee3b08060488d554cea9d6d7e86b3121260ab602a40e5625573c17b5aa354b1a19ee9ce50c3bd089fff1052986cd69deb02c7ee25d792ede5de9276aba3e54c2f59db844e389f183ea584ffe2842ee5c4a7d081905cb55f1cf779158da2c29b22282f012a3bd08375ebf278c1c089fa180302037acc132ede353b0afcf003c01a96b6b950f22d940d6bd814f4d009a6ea84d27f21458dce625dd0505439df267d23900e8998b164a5f1d50346f32cbe702309d08c7555e9e85e96250814cbfcc1062a41352cc71da425ed81930470d093008f5c0e3bf0d371641e2529f38c92b84702a14623eb5f12f32d59e560dda8f8059bcfcb05fedf5f720b3f2b3f3249bf9805067833ff1f72dff685f00f78a3aac7a111a7c4722b8c0b8b35525ef9c6d14c41eff98b0b8d60761e00eae9b297f6b186a1d82487813dcaddbc4f3060ab6b00196b138756d4d05a644176f32d01b76b1a9ddff7af652c89976a284f33bad7412ae7f78f99cd2b850bea8491b9a22721a882c6ab883c1c9862efdaed771bcf463a44dc5a76c946097c762ef58246371fa473513219bf4a977e689b2b5622ec4769ae6e172166ad9e42e01ea0f9e53b54cbd8b2f9910095ddf640e51f5d5c84a6284656e4e75c86a57e9b6ae0a69db93e1a3a54245e503f327ce8679e5cbd03d4f675d81b4a10bc343b52072384aa181dc45b877132cc90f2def8fd333363b29fbe75aed229ea753ac8cf9292a2abf783472345130fe5a40c057e00074400c8c382999c6991312ddd0e7fae49665fd2c35c35a668f7f52e82545da99a12827a96bfa039e1d1e77629bcf9284d8adf7c0ff34a29b69f3c42e955c80c195a710540f56e1af39298bed31c059ef6c79cdf9060ceb5fb7e00646bc3557ac90e965af3030bcf578dc0b83685b720675044041900bb8fdba46ffa53cb5fd9cb7160a09f1946aa4ed17c2684beb3c033ae15f203afce7fc74e1fc54283786a8916b4cbe06174920ac00176698b40866a3182c3f7482a9e62c7e3f278989da0b8be188583a8e05db4e1d8ef84319ad49c0dac45f0045a37aee5fa7f1ddb6b4fa756705e1eb2593df7182b68df18a34c342bf133e77638a8909b43c0892a0f69704aa0dc189bb210a360f65b47e4d03f000960bb7c43ca1368f16810590696a364e6c8f611b8d320bc2b949df7709247874c7e11ca685d856d826bdc3acb035185070b923b937245ebdeb31c3a12ea4953b45e32732af8c01ff2bbca5369b89a26bef5e3a1306d04cd6ee899370262a25990ac785509d192c63c9a7c86caf5962e3a22162e88e12f33c69ef5f91562fdff32ca0a339adbe5628c3f57c047f234e5f5ca47490a359f626eae69d60f3879c527cd1f0ff0b8de6b4d29970a62111686c2e4859738e914e4d86067a72a22274e7b3a483055d5ac5328bed48707f0043fa2436e490a10b0f6d8427e40ded2993813a67ba83b0f145124e0007716b39f4dd8db7caf7143897c819716aab82f1eb18732e2e03cafc17874f41752e8edcaa4dd11ef24f354df4a2364edf7b4b593b469da50ee7b65f4c08abb5091f20557d66dac20451e23586c662a076e04a906fc94587c8e1b40008cf872c1c337457fca90fcf664a5d612bffa9c17ef051612e0347a4aa786836885d4d230c485236c27adbe95a9d674e50a098271af68173147a599a178a635689f4e74bf6b4219984a0c4f3643fb1eb40fd47b966f09e284a58349cb4c4fb1df51b1de7e5bfe3dc25c71661bf3a972703f07a2a5011320336eb3dedef2c71effb1ef0640226fea9e2011bf0052a3249d355caede2407cbe6726ccf22b98641d618c16fd159ad588ff41e0bd0ec3b4ec138de18f55983882521ff5fcd5349ebecfb77c31ee3f387f49df93fb48491abd24ffca8d26e62edc0f8fef28b20024c48237da6c832d980054176ac6de1625451c934cf89c2e9669305503ffaf50a04b6867562f89f315bb9d8916e44517ad71d2355476b227475a92f309aead4a18682de73c7b040f5b0badbad465e14f3e9f02587478edbabe3751107f06aa6bf2da4c18c273ca774421c4920074d454074d8d7070affa86f09925f4209707034d7750a3d75f55135d1f04dd3ec42ccd17cf0a77d917e55fe9b3f60f218dadb4a429f2f31083324028bfb5ca357c19faee99f169ba2ca4cb41e2f272e43905f7caddaeb6977e674e3c44f56fd582f92c5a018cc75413588460a53f5454600d972cbdaa60302c92032dc82e423082aa48a384da18edd1f3f98cb1348ebeabfe33687f7ba74e2532174629afa1e31ba8480f024964a7e345ccd1fe202046f246c034c78a083a687fb615260d4c101239baac6bceb773bb4755af02ccaae324f30598c5474142cd6d01c76f55e5e2661e3f72d7a7a40a085d1d9e7d40f3f50efa1e6d9d4cd5033e018d4cbdb377acb4f60dad0461bff9cab3646186f16662473cfa3a41f18bbce0e2e25a29caec8a40ce6ebfb04522c094469d29bdf59045f577dd5ac582efafa93bfd32650796a189223f2f55a8e9fa771aec86d0e7a4334ae1df6e607bd09b0435ce3c8a13a7e156f6720429a4b6a18532af2bbef6f708d48a6a2894a4c03c8824bb7194dab86285718c777f212d53e45af27e79d3834a084bbf5e380297e2e5531b97cfd2464b19ee27f0ddec79b1d45df9fee1746816ab56727562c2f114878abc48c1dc30495342715363a2c6ce346dba73331e67a4f359ea126333ab478bfa5ed359813b9acc8daca25a08c4a9548b60f159af65569074e22654ee6a9f17f736df75628ea4dbcdace912352bba2d86caaad309ee58ec1eeb40f1f914c62ae66ceafa68361fc3035c7d5685318d4fa809940a40b0d358a9e836b4a654cf66349d93189ba3ca6dc5398a1bfa1572139be5d44f58fba9fbb7901b704a1a2d83ad59b4788c29b9b8a7d5f2470d9836e915590be3a748cba5d8ef84e4ce19b45880a64b680980278e0f557ea26e7ff3cede135eaf2daf13f311e522033c48b0b9617b3843f545e5529df4564b6313e94550f55ea8df6fbc5515a5a5e103620988b546c4ac53774d68947ed855b3a06b561197bdf0906a5cef8f38f491db6677bb6167fd5e1bfacec456d5ae5d2af9df74ecc2c934f2a67859f0f8b25a5917c749f3b2e19ac01705e54a704431b1f2c759baf6c5e230cda4bfd9151a0a667c2e2309f158835cfa5f9748d780ebefe5de1e26e5092be5ae33ed66131c5dcc61b1b48d60ffecf5335d64f728d716392e4633a8f6f4f151ba7730524adcb4d6d595a2232fc06cb73650955f95a4b337afc1f875c3b01d7587933511657594f8aae8ac4e0440afcddbba1b4de4de3b7b4d041c4c9bc3e90951da3af6e40fa1f6f911f04d76176340de35db09ee12fe85700cfae901732a6a74c040729a481edd9b79f35093dd3f5760e9176320b369738790a4cc2af6a92ef35b17a62fe7205ab03b38ad5b02f40364950be4fdb526a5827b57388378098af4746b8a9ee4f1bd1ced39fe42790359532ae3ad99841227567971e72aeccf9c42263519b8c15bf9057013a6b233e14c79f1a65b20397ca37d9341c670ac906cca0d2a0da02ddc6e57d839f9876bcde78545cfee4d2d3ad273dd3bfb5df053bb1e0d15fe4efcf3504060ff7b91bd42820ddb87d7f3d872463a31671b2c0dabb7f59935db0c6d0f95b46cd0fc1514fce80899e3c978003360d7fb15d3ca881a61a99e0807fbc0c037eaa15c024d61dd125201833755332bf0d3193c7b5bfcc387b7ea1ca28f2147acdc071ef9e32949942b296ff3979a2ecdbac3f30bb6c679814333d5e40fd79bd6cb3ef0c77adfac22b8412bbc61174d349f8a039de48ea60991a339c216cf7fdcbac3a9f7250b4e4a81b03e2668b1a24d9664772da496b2448dfd3a701573436fee44e965ee47a2eff1b7b9a77555369939c99ebbeadcd08a444a09dccece42ed7406898f510dbc54daaf7653e7e3815824de01cf5c105e71691a6083da0650df9b566adbbe499622eecd19292e8f7d4279b31f76d4513b85d156eaa5251f86e375128bf21acd4865477e11bd518a48f15d900762e1c6ce4bf7a01f446d113e0c5b3a971e3e836f81df4b19626907c981794f1f3a45fc936b79433debc6332017669668dc777e4ee8195901ec2486ddce7acba87a7818665098b23ce018ef8ba5f76a264c04d27cb73768bc994372f98a915bd83024deed3abc9ce9d59fc5cfcc2f3df7ec9c3c2080e173f6b21c12473cf35f8eafa77edd6dee8a79fc6ba9ebe978a7761ee5a20329a4d7b5eb18c5a8c85ade05e8943ce4165d1147e3dea2d7a1fa3d6136fe59123e7008c9ae369233d0822e0a6a32de83a4da8f925731c7885c26832895f64102071add02068706fd8dadb6852ffbef31755b826a25ee7e2e562fec600852b0366ed86687f5b063565820b87b3f1ed76e2f7b611d62771c62978cf53bf68b2a2618c759abc56b1ecf22a42bbfe4feb3bc9df812d7b17ec242c2784780418ef0cc0a690528e506ad989505aef8acb114a09c34ea4bcc8b9ab7a772eab1913a57f6eb1fdda5c3e2a52567967ccda482771484b638935e7a5b5205bd8b5cafe5f2662cb390df42f52d1ea6ac81394e709d1e79a66e934afa1f8ca1e1d1b1f0d891316dd1b4df2248b7038b1e716e9eadac663f54cba12e0f8d563928c1ed46ce06c10b89fb1a1c7a0f1e3accf4c9e81f230476b19d371290483b572230335979c36008cffc68303cbe8b9715e9b4fce298e5a5d414df0ea9bf8339224cd23dfd424e6afb8e1d1ad3b38ff05fa97de4c3527e2b76a0b9ce5b3d2362be2d2bcf73449ddb4aafc4a69a4776c6926c7fa3c7a4a3d076e141c4f7505d48527d9e4d68d578765bfa42397d86ab789db1280aa224c4de572a2b11a78462e97e273efc5b2c8ba4b2b45ca57ed1dce4f0bbb23641ace28cbd8f802ce5635da2a42130aa425396803863e51433cc3f5501209ca1412cffeff488657a02d2a9d36b3ef5a605a28761b9f319ad469b583ec75f38a5754ed7ff69201a32a0b0304a3f120674020990156475e738f472d95e4297e64763e828d52eb4242138d62151cee777a2882b0bdb4f6120338f7575cdf138c1b0c83de0017a46c3149654869b8cc49baf5c4a3f54f172e229fce1c0615adad8161013dcc6275e06f5580f65ea307dc95f37fe3f8aa610af9a369278ae85104c2bde8f31f6337737e772a2737135dac80a45c27d922f44f814c9c9ca74815ccef68af119843f036d1fac988d815acc8a89a74ecc912f4324a757547b11de2770e39e0df42313fa6d6322f26bed22b124f9f2c2e9a6af87495817f4fce532d1b93359a403d218fa46a3c3fa56f255a73f53aea0d0bd9a7825b75120da2895c86b782c83d8d5dcf83276d470cc2b7df36df0d9955e6c42a20f55e31307da24bc75ae3f77879c9283b9bc6afb533e6a74186561171ac0d97eeb59bf2bf7d081d4f68eed0f1fc58f8f2423f3f0fb72af27f6cdadcb2054f9ce5a3c6a958ffb51015943433102ddcedaafc1ae6d387222be3f8d15c5881d1d82b20f7f4d51eebd55d91f857612956ddc13a22eba1eaa604011ae658cbe1b252f74c72a6e54d7f2740f3faa64d4ab2e454e84db0137b146c3ab9f5ffba981d61c4e46aecdeebea10e46d9db6cf763f70fcad670595e24792c89be35e7a3fb58dae30ce0ec1fc27f6bb6faf8beeb21522249787e157313df06742d139b866db667e1e2a9351c9cc609f9cb13ec6fb6ee7c3184e870c50d246715d5932263e361ee1ae387899165b3493df8f25840ef1cfc1f1c6583864436e0aafc4664a576ae09bbe90c207fe9062a1add9180524252a8e2db0288af06ca87d6d2320cb198539fc1815eb5ba5085140a189e0646cbac963c679d731b0fd50abb694f46418bee475515b6087d2e78bae3b5fc99f55d6659154b6bdbccba13d2d51d094915ce7e47a952e1aa8f292e01e2c040a751246300f63d722b9a428336491bc1afb3f0e8157a6dc21cf69677fe0c2a37dbee18a9a9f5a5102dba80e8c588b94ca54e8673776223ce3d2c5c290516135e75f2b2e3a1772c72ddea9f4a9799b172de12f14c258b2f8e0a54589cc1f84d686a4643de145d121e942c4f73af58c770646b590306772820abec642d66f37fbc563fd60c4e8ce96a794bba7375795a772f046d0cf6ed98b7d1ecb7bee7a7b5f5401ee80489ef52024db1941d372e782b9195704020488317695a12344a785e602c18de00524d62667c39f987bdac72246fc2c659e69b641993b9aff8fc6f95817bcd005d769c53698901bf8ef100b8704d934d7959903b2df7ec5b77474c568e225ba81e85b54fb3c06f285090df9d0f2958dba442d60cd360616ea2c575bdf2a96f656adf83184040ea7eafef6a1c7622fbb241bb89dd21ae9eaa6ff4fcb9cc000e360a591897e04df842d5d21bfe0df80dff9d55fe21400e982f56ceeba86d1fe83fec683f70b5fdc715ab4e40dfb1501c4268d5b72def8f55b0d4777e29de797b1da2e97cf6b728526660bec27332d60c3235789147ffffd029ebfb141a58425986e4698f0a33b39639e8c72aeed61f9ba3fecd55a3d517202ba018f015c97b44fe090dddbd4823ab1a7a700a0fd46c34029f2149a8ebfb458e7c1b0bfc56dc0f4292fb6bd46987955c11414958da2ec14f5671689a4e0a46310ccea4ffd37f655ca0634896deac2d5ded7aff1b886425cf4af75ef221f951d8627249834d6ae2a779097aba42a67e650f5cd3a2f3c3814b0142746e19452de391cf6f8781da2f870768eb9706a26219c0730a7223b57f65e7dcf42c00f01fe9344efbc5626c04475861e432ed1053250a07c1cd951c326fce88d7ee338f12a0e82435ac960b37711f7ffa2f8e1ca7fe3d85f9efb9f554266904a1f724aaadaccd940e45fab54dd332c3982fd66dba176c0c761175bdf50bcf64c8ddd868961a0936becd1a0e329e0a0881978343b6deeefd8d4da0acce2ffefd1d056992224545540a1fbabdf54f46f1e859a9d8f63adda2719e88c2de0456dbf66a85db1dea2e5bd24d50a8d08b286c14dc311b47029ae08dab6d9f083c1ac81ee2ed847af9f552a9e586edc29f16c08fb855b26c0c47fd01e8032fc2b0f3e7d2daf7490776d06c93e8bdbb843a1a2f6bbb2655be204b74598bf4221f2b6cdf46590d9f8464769f384f85f2adbf63261a210671bef6d2dafb425c3dc019778c76cb2c40b357a7531e1d26953fbbc2fed7d22e20f4e74bd404174b6b716de64e4605660f36a1a220ba3c1b0df9302e4db41529707cbee81676e147af806ff3e033c860436190ad986f3f923c23e740c3a8e357f80d412180f5e2c7db17c3b8bf19e2d5e550719125cbcded60bb57b89f5af06bc068ba31398974b91d23c1b823c71c11a2e02f1bf58b0c43c527a6f6ae692dee766cbf97eb0392fc568e0555896eee76e35e9b9df54e7bdf44e0d281db6e7cbaf74ee4970513fde748e35357c92c19fa4315496ae2ecd9c09db469844e0c1f7f22ade6b70e2105c18e92f06bc12a2ea74a00fd5d60dd7118939a124c20b15f4101700f420c7e944668712ba18c1b371a7a5051532a31b9cedf2dc0cb98f2dfd83ec034c8f57fff2854361fee4b199f05af8e0eac4df5a4fcf84bb218009df36eed81d19c398d368c7afe98a87e22d2aece7d47ce76112971a07526edfedd71059d121d5551436889e725b20debb9f06dc513d5a24df3532f47e05a288f2a70efc143eb4d662bd5ad95fce5bc166b6332d5234015e2982cee3f617c4a7cc26c546bcb612506af9277482254394f3510fc33947f9928ccf998c19c32fb73f388af49a53d3fe73e3fde36936f6e421f7263fa3833a186a1e1cbb922c184a67d9551051e7843bddd35c885af91252775d90f62ae2c286f8be05a6f631f53f7bbdddde482989b1d81a39492c7c78f7573a93e54cc282b0f95efccf4a5fc7bbd71d0cac3afe4f673fafc910df7b8c02f607e42488d952a0a96d1db6a6a60311460dfe558f5efc8065ab884e458b130ba54c501728221ab467167f712e27a50bb0431cccc11bdc90bfed826fdddb63b443a7574402f9bb7cab09bc1316744d687240a73a035c572ccbfd1a4bf500ba2c0ba46282ce6575ba810ee90c11ebe65640327a3e34ebb83daf07c1d714fa6086e28ffc0d500f329faca67eff42d01c3e16b9676bb14d080541a6e285e470176ef9d2070748db7b4efb6491d892dcd5a57e80add388d902f3ab0ad4bd5ba06d8f4d8978494cb6f17ea8c158be8c4f1b893cf001a7f07361bd12a0385f15b157e7391ced72512a76fa8e1ab621d97c2101d078a463ac4631419384ec534c6ce9c6e4fdf5cdaf8a35c84aa18fb93ebbfb8019d0acc65c6dbad50f71370f2e0d8ca4f018699d4de8185be5c5064adbcc1a9389bb3ec6286a9bd897051df8934d084cfd4ce27a628da11dbf98fe221163e0292b613395f819edf4dbad8de70bc68ab6d1473339ecf5e2e0e9b4a6933edb68537bba71b0bdfc1f1527ffa2f0e0022027a0b4a00681ca18db84d8f26b0dcf619bdd8ee4ea0a781993fd60105647c02b2ac323263f9a600e4f3529af6c46e8746c12f2961384409bb7f9d14898aa885c64e275d46d1f5676b2f14d5ec9fdeb6bc1d43913973431e0429cd5e59fa1e47b0f22ef72586560d60dc5ee0cfe27b96cca72e938b6d9c4963ef700b04a7b5fcc2bc49b994a4c9e7832ab96499e51996ed80c53e6507eae08a293c728fe98e00b63b1513b6dd1be99ad4e14cf9c834c439f7f9f3a57a58b871e5e90cf33a6d0327d10140c7858657eefcccbed258b645edf5301380413fe3d40cdb3b9cbed37e15aa9cc9946271d2cc19b09fbabe844bed05634dfd4ca1eeb099a49deb9cbd256547f0aefec92fa706f2eeaf7c0445a73dde2c0a788b0d6ecada7fc1b62df6e15f963da77b10d31b6fa9fb539666fb238984313c20bf998828c8ce48c3f25be32e998fbc95476c3f63dd7befbc7673f29efd6fe678a4f17095672620506298e5649febc8694c57a03cd14450530e9accee8d6e87d968b458214191548e811618730e203728266902deaa0007f71033ee118a5d99ef709a7c9fe3fb2d214f420334ba73c699b41bd02cbf8eb3f5fe94a43eb79358386f1d5c2679fda3a2fd5a5d64cdb7f695ae108b90ee703bd1575402d63b060c89ca9c0c916b6ef61dfe610022427e308af085f17711d898c2cc0837da1f9e1d885438ddff187f2f93daf01973ea76b3f91a95e7d374217131439626705d8d6a3250db2d5398409979e0ff3cbf0835d3f98058749766d7e93ea7a31e6281055657c83171b48dd1bc389be7345178df10b1664f4c3fd286433b8e29f49edbf152193682ce0f0fa155ab1e2758da658d2ee18f9b7345edfb992ea94266b73f97cb7177a5bd909879bfa421b9a0ee97516f5a1ef27badf3b7c95307a85e62553b88aa1597442d1146ca66e801ff854736d581c059c37af4b1e29828838bfa7835f8e4db57331bdfc28a2cff9f337c88ab54d02f10e236aeb4c020e07fd9fd2b0b0b92bd1cd8560c8db3a1c4a9fb12422aa355ff30276e7beb59c10648888d157a99fc0c042b1e7b413e67c421826bb70d7d32baf79c344f701486fdd2eef09291b8be01cb5d742a9eb090e1abd810a0e5fc1b52b78e9cc1ecfb2d00338811d83db3478f3d11a28ad7152851cdb06dd0c2e4dbdde0f33f2ca000a947ca8cfc9fe6d05b8b5a2fb0e193d8a96441ae52724088cfa7b374bbd65c81a1fc112f656d03829224668eefbf3e0684242d7273cd4b5c698c70f630334e42f0f95c8e225bc705d7a8640e7284a6289fba3beee89fec0faea82abec42a9a9a235f4095ad7c461ee99bb7d790f115cfd2aceda5ad069077b9588f42217a2f13fef28b44a3ceb6f2dd1dd7be64d15b1469e7a44323f6978236863fb6f21d53e9f755fc5e47b7aed19aa161964daa72eba597374e8e4231e9e746942d69cfbee7b90411050017831f5bf3fb656e83bac03bd613db9438b23e23cbe803bc9062d1a32ab093d6e8265f318fbb112fb9f609d3213ed0c4c5039b529cc3cda3491b2947448203f9722eee7f4715fbbae181b38aa9b4a80d31d71b722b79b6dc73f079eb912f1a3a07d8be7c3b8a897fc169f5706701b54363aa1495af21a1a8d7aa630b5ae9f990533c15b41d8d56b04f65ff0b0b05819dbfdce26154fcc54b58d697d5fbe8fe9542080f87ca59edc6cad616d3b3445f398c56018eb899ce8fd080a3abd1ce65076c6287a92eb9acfadc4b36b47b441997ea84256cbc67a441ff47daa5e30ae94715debaa07deb5dcdd02dfa8fd1b86b1a60ace55aa0783eb38388f70c6ab95eddc19de94ab8328180d593aab8a398448beed3a650739fbc39eabf12e01f9d63f25307185483b7259d53697e795a5ab095bcca5656d13b07acc873545365214bd4bcabf8c34dc25f6c515ac88ea2e5024a4e0ac50bf24efceaea2f714c328edf763d44fc6fc782157ee7379aa80704109c4d77431f07b4eb538f6d50e6d8d7f8ed36e77e83a7dfe726c5c68fea5e5a36e5e24296d8ea95c54eb3756dee97b493f48ad60bffc20872fbc429de6f013597609d6e20911e7ce7c26f907068fde1dd698c89bb3673c5f758915a79c2d6b014adebb4a8371989e59d6513370a19c49fcd7fd4c1504cb9dfdc58621d642f0b975124d808cbe5c306513a2101bf7ec886c8ab4f5cc9231904eb92444d496f22a9331e9a97e211876c52242cc425193717a4f95f73f0668af92ad4cde55b8871b32159de2328ec6e0699878db110cc5a77cd25836a69c126e907b3b8b6f64410fc49b6b8e5d812618524a2c4d805e72bd95780c1ceac13de7f2ebcdab06a628219c0c06ae04652877973482e6dbf67e602783d67cc84b9b0c215774b9c7d7b5fb5ff8591ce55d357a80db2479fae0c365a0956679790207c4c717ab7f5e6c21d5360fe05d8019a0471f955ce297337e2e009f8093cc5ae5397bf905186d38d894ea50532ffd753ecc645d74eab669f23c51a87c89feaf89f78bcec138664aeb36c72e3db697876e6e28301f5a410d40d83022d79d41a497ee4ed6d439c2d9e6029ce63aa671aae844d47d5ae7fb18fb6172cfa2b4246751a6218f566deb8784262a67fe35e069ca34daaa4f448bbd9047224965b96b105fd57c9c8240201c687204a9527961be5e417ca3fc8f054f951201f8053d972ab877cce09b852f8148908ee302b284a90ec544831bdb2d937463570dfed84f5061a44bc37f09e02669f662b65b14c18b57bb5c80a1cfc56739b8114e40afa7abe981b68c5ec4079656de77bb348881f7ac25474c172910e81af8b27b7633c95e4509b6ebd0695277625c9c0848c428d63839a7a1a24eef95f10216dfb9a6eacae71a3ad769ab1cf8b96babd31edb8a3f1e1278927c8d8413b6cf9caddac227c7b9680c858a3f5a5d509ac9b9b4042378ff61a58d36fefc43c8fa5cab5203595291bcca7b05b10d0383b8d66549d5d32d0819e207e1d7f952f59f01a369e93d9bd34b9f4945278ef9af6528e5742151300594c1daa04527bf2d4ea0d2dc801015a08e59fdaa9e491fc23a667528dd46b00666210b8624071badb1718d57ea918833a8d5b196e3fbb634c4da97c41ee5f77542bc0b302382425ee8e56b5eb05feb7b0451ec03ab46796e1b8f000a1b6294e46457430be1e781b2d790412f982e6577fe3a5399cf2ffa4d2193df51a43977f29a27a358d6c04849cfe06ed16fdd8d83d58ed20e277e644371173e38f978b368936a43af79991c5199561966f27304e3ba6bb8553b8effa1b57dbe16e47793cf44cc634f7879e19b0a5bfccedc01d3f4deb1541f74be415b9ada03b2d57ecb7525d39b2a4d6dfae34a8ee6db66a23cd32bdc75962d700799e1554a9da78013bc5185b0734b8ba9b542861b7f7e4992ac6b7e0783e4575369e7e6919d494e921e7130d8b1f2ec8a07ea2ad1630712c61762a326e7a09e8f97f20b908d7ed6c418e1a34e38bc65abb52ca0d8a4e1b5a68c347d5c45693f042c73dff6af71561e561c1386139e0f2878197df904eb5f88bb616a467c0c79a6f16525c233a58371fe33a876e9433c574456925ac3163ad2291abbe55ab59e896f894d30cd6307064c0ce1309b7df40c4dfab056f78af74942bda06675f1a4d331c49cc925dec207d8e93d04f6d82805fc3ae491f7d421eb1bea9a420e9e7329d488795d9528c72c24034371f8ff2d87857c5e0e55181ce56114f829920b475559813dc76e89d23adfa9bc6bd400c4ffe7b5f5b8f048b7b684457d06ef83ae51fc25236baf0c505b4145c7e98ec9e1fdc398271dc69457114b7fa81c80663c7ed7aa49c853d637e7a6e81640f08f5380f49ec7b62d26cd06abd44cc6d7aef54983a3fe9b5f2ce064f68570052f59dba87563df9fb4ae40c4423daabe9ed1e386bed285d1b8d70adf91d48e14b941a7fdb33aa0bc94ad66b77b5ad5fbfca0e157f5c0f1d02a01192dabca216a154cd8fc85a23f8bff08f8a614bdfeaae2e3ffee35692b04a8b9b6d580f92b6bada16a9876e56abcd3263d9ba0a579309855ffc9be5088312acaf069f97f65ae389d8c1eea49e3086a0461abedf6b661646a08d6514ea66ecc958bbadd3289e7385ae7c6341cb3c659b5be69e6309b2868e22205619c90ea9c26afeb584e1a30efb12b59d8b9ea39e26fe2850ab5689fa80c29a44e54391afb603d5c913a790eec744fc39ec0adf7eb0839f9ab8d44395a365d403675aece5370859da6d280c41cb3b121c197f905b775860838748fba1ab2a1a6d0b6480293d623706f1dec693772d6fa17b05f7136cbd5302f28910486954ad3a44409157dc403238452aee507524bca33a967921b402b108e47da64fc58d42b21f6ec6e50c959f153adab94b68f6594b92be22382dbd8cd7991723f60231821f3b6eac0af58de73c04c4e05c2a73d8b163ab1f77941f47a6e239b2d2b790881e9a31337683e508d47391210f0d01805426b8432933521a99a1b6c3ca2bdd66b62c5f2f9a57641567fe71bd5bf4f9cb56a5de8315335e8f07077889161325a0430ee9d8823a22b116b6b6f8765c05e89432c77a5ffc0308133c147a55d41cae32c7c14a3faaae981688ab9dac9c5b3a8639c4e6f73e9e368d7aa9d3b2cccb7f8a1b8106da397b177c5c31784cf956d6ef5bb7299d8905a32ecbe5121a44fbf146306f70e1c7978f5cf1b5d9a656f7096782b9c86b90422a1de48a7a7b03475a8311bc4f80cd0ef63b8b2717199f05d0a38a62f0fe84bf79c2e1939220bffb0275e3a04f2648f655cbd46b0b2a2e545fa37db8ec20229396f2d35fa610c783a63548c782c0ff974bf787b291b6d2319b9a7721b00b4d72576259e94585affef75dc49adb812bea8846bab50b2e3a859fb8ab2e51396ecfe86dc320c16ee3c419702c17b23bb7ef5388e69addded62e3bb92dba8bc4e3b0492f7cf3b20853042512653e4346ab7d05d30db1f252933f70476b9c3a592fd2cee4a868737eb057a718009f2ef978518cd289b935bce44af52f7f31e1b4c1b59da74280a49fec352061803cf8b32d5ae9666ac219710e9bb563c80126b7856733721f2fa6a4e55b857c4eb98976ce94b29278da1279a8564fca6e2453028dd2e076a5e722037adebe29a8d9ad095ee291b938101a29bbb695e4bf5dd72b80b646c6229c8252716728a0145693b74fccc22e863e2dd001450d3f8d8ec37cb47bde45030d7740ed3b14aa277b519468429913a222cd7326ba73b4f6c44e42095f73f65e827195540f458f9d435d5651fbfa85cf187e9c087c35804a2252aeeeb0a48a9ac52a49ea5176cf197115bf5a47df1ed7a54b3261ecf620e5d4cc47382d664d144d1ca7829ed2ac12a7399336d3d6c00ba5406955b00b0b3b985feb9cc380581711068308277a86b3b26b23a70e80b6aa8bedbdcad9012b7688d25b06830e1b8f6e6b7dc4830303c0f9b66467a91321b1b3e9018e631538519a8e6212bcff2f35d112589815f50ac2810499cdded9b680094d789fc1bad61ab3218067d99784339c875aa03f4d930c20a787ea16afd1edc83502365f178b0a30bd2cbfdeedce74ed99c6ec18fb375d2ad14527ba87cd902f435b4bc4867b51a7b9a8c7f96e541d65da5c45db65a6266f1b8fa3f2e6b6742b1c66c3c4170391d00f5d3f125800e60f22f12840e2d7cd5302aed67c67497d16e888808b0b14a1cdddcbebd040ca53f00c0f7b31401c64b665f43497363e1e80d619f78b596ad07fe4104e278d6c056878ae91af3ff4a79709c4db9695ea3de096db11652c8e08310cf236fcfa6a91ef07f8949214bc56a1152786a55a2e5dcfdd89798862a67f9bde7f9c431869ae20a21963d9e725195496f84bb36fb25e09e57591090f1be24508465372c9d09f13f42a6a8c55849407fc9cedb451778711ea32bb4ff8071e3ddd0b8df0c7c82076eb212ad7eee46aee9b4aaedd92b392c7ae52b387197f3a70d2243fe81b7b478f3bd750dfb8fa33ff2be2fea15802f39ef53119981f9be4550251d28ea6ab1a99db1aa20caad8b8fa49577bf6dbe19a720ad5730def619c0eab2dbed39b9ed37585d81de9327cfcc6f4a8fc6cea81e62039d445ea1d77abe9af35f22f034f06cc3a4c167db027dbfd106ac18767e2803452580288769e4f0e14522e2c38f52902bd46280ff98c4adde6d9eed8c6415ee02d27f5bd770ec30d85faac166add3b671382477cd4c784bea28cec4a92db257e20c06f53d3920c4ecee5c04a1d8e7613090651f08028a8d8d4a35c3ce9f6a48f8a88cd5af07ec190634a485fa93e57b1ae9cd5a32dce955af096b2f0fb4425fd4c0821736a702354daae178bb99d6b6cd4560e687e1119d7ce8baa348e8fc0eb4b369c168fe143706c4abe90cce7a1b893202d360dbfa1952a3f23fc7db70b10f0d4050ce28b171d78f5458db74d1582c7657babe7211b99257d1c2d3403b3d2a7decdc9999aa38101aad01722ab909a8dc6f8570cdea915c229dcc136bde1f5630293191fadae072b94353a068d41e7cadb6b09f09a5a9d6ad97bea98228fab19302b9392a99ff2c1b2ffec358855e6a40b17c9fd7d0b8369f960dc83a10a3640358196465f7423b20770ebd2df7ba1066e83160f751ad26e88618cb567613d5769d94340f9f986905865e6d57b850721c11306f650a2c00f5f97a6014177fd5f6a4f92bb40b1660f7f4b57882f9df4e66d514ef8af50b1430824b5cac5ec515de99e0ca7af08b88c4974760c50d2e58c9c25fb4cb81eb1e629678d8cd92c6eda8689ec2553d69084eb7b22d59aef71105970729fa276dc387d69c954793bc163bf6b450c7e3b1b18fe9ad82a6cd826b0c7afb19314903e5e577e4942da1f1e09e8d2ae674b5cbe90100838a645e6576f69a9303c4b0d8de0c15d780ed803c3e13a2668f5f094b4d4c8caecbaa400bf957047a1a11523992923395281b3624284f3b43ea8e116f3f593a2482829698f2c251cd9419fd56e0b778cfdf720e1a2917bbb9726c0171aa2e6e242890b76be84593d8d8b0a14e23817ae1ae4a6a84327a731e04b21fc1fb30d6712c9e3b4d3afee692d64dbbcb8ca135fcd4c4c60346b01744ca51e288da28eff16a6bf2f870669ab17a531d5b7bb1bdee948971cb908291a42f7b85f690d18fb513489ced1a294e5919bd37c996ef53e9d88dbe0a3aa3c8cfeb70ae16e3602ad3f5bbd0ed9e3390673860869582587085fccd88b068257290d8f4aa2b2835f137e57728b4cede1c6291a43f04c886f5bf86aa94981c51d11e8df9d5db4fe9cfe4e38ae0f6126ab32db009cf04e242e23f3cc00e51a2d59e2ae39bb54b4d5b25cae600d2d92ba2d62a7561c1b6fe9db13d2eeb9912176a50037e45cf63fdbe4896707b990b817701fbf2bd86de1ac737608e4b50a0ce22c33d95b83cf210613c94ebaad90d071eb63d904d9e661a3aa05c7520a1db4b674ae48cb877244ef994586d851ab013fa0e2c35ad8b9b3ec585d59f27ffa8cbb39db6f98fe3da355c34080145cf314bc3a103e1aca92da0672befd1261eda8be5c6cc7d8692d179ac64c42f609a28d67475b0a2337af54558d29d71398477b10df95dcd6bb1da94459b7475aa6490483778625cb67d8eba2fe11d8eb1da74406ef76c391a92f167dcf8543ef5f623a98f556605b5e88aa997e2b4aa4b8e9007a0409033acd315e94cca4ecb86f2b99f0c9082c8ea1c3214a6314e6db096a7f480d2620ba983c877a832df18f8f1a741b4f3b4616d77173a968de4342ed5f07d2a9774d2918ea3c4687e2279dc053746bb64715172e3f840e09b8cb1e277e8b7d7961ee5884d394b22b4619e461e307799c33f2aa99504fb6653834c96227f93277388b48a53e0d9b75ec03b596b48c9eadb1515cdb549540bf0f0a3181d4a0dd65598db10425ce849bea913afd7d347b5ae2b9ac5dbc07307e1eb35737860ef24cf0997cf0fe4f1416868fedb41e4c729cc6b690de21a06fcb34ddd2fc5beae30483aa494dfcce934085151fb525a62aca968bf1e92d0bc9b8fc4e494aaaa2abd39012ea38955c7189c3c372737d650e7c6f332a247fed6df9d09ffc272ab24619d979e67c9cf4c032ebda5fda754acf8006608d646150712a82713c0c1c4e360c1b73f7d03db6b5a2763eb28fa6f1dee15989de9f5db34cab4c65ef097829008660bf8ced91442b445baa088b6329f3f78acdd6a2ec165a06812b0d4f85c3addf257e486eb25d1ff7ffb158fcbd9f898f5754a19cd6d35ce9a0bb25f372694adef082818477ea3511617067824bb05f1de3256079093af5adf2f8d915ccab68af28c9c279492432ee832b52b12d06f802c2e8a80cf0c02e72cdc55d4de1899cf540874902b0f17f109c8f08e9131c1ce378c7020c940af2c01fcefc6481fe00e589b28a5f8945add18390ce7b31615506e2d169d9141a93caf21f936b9beab06f37ddaa521320eba26a72b92462ef73cf2a8bcf1d3de5432decfe1412a4934e82713804df2fcc92dddaf437cef64b08405bf9983341d89c4d103e055ad80c7f507f2ca8dc8f169a23791d87a8e4a1ed8c99771c61f0f9193f47d1fe93b7eb6cb15f69d75aad8722501faa3c905041bbc29bc029e3e21d9a3c66362fae4e99428c608a88d2bf1cecf734c4bec4b0f5537af92280c76ef9e4b0bfcbd7d22e6bb2548767e7281497de796729c8eefef0579d4f9554976dff1fcf7b28ce5629707297dcf7d081723c829f23a0eb76b49fae3a686cced11c9e56bb3b11fcf72a86b996ec3758e27bce86cd72380a2c8bb6046bd8c98b36a3d9fa42c9b1df6bd0dfc7f60ee045a329923dc41d8cdc393c304745c6751ce004456025d55a0a3e8e995b7a47d691b3d87b1df036eb6e6fb7be2be3c8ba842e900a3eec0521a0fe855f3bcfd12febaa8af73f4a6e249ed58c68a7751407be810d0c3254d960ddc0626eb06aaa4635ac4650e2c8d062a19582a87669648a35c43b223b451a598de9d66a0776a6df0718c46ce95fd56c7d2f916a989b55c44ac5632a3e5bedc3934d95a411cbec9d2b0fc9725891d96a9000c0aece9d22fbd2630ce082feb59464a0df5aa300e2081fc5e6e5da8d6f3d071a9c6f210bf4de95881978340c56c7893204821787d93996a27d013ee51f56686743a75bec3632d0f0df91e40e8a7b2100655acad41ddff7373361f6c680f709253ef38c2cab2bb4c8f7590607646d5c0fe2e29ca221a2dcff438696d2c4bb96a6a064871d1c6be515d8a973c3263a011fc1d17338ec2695f15a6b5b7823438f6d2f7f620cf5c5fbf18486e5c903c7e9a46c48b8c912cba3e3d823a2e9bcf5c78f7f47fae523e50495da9f7ecf7d8d811ca4e7d8e1d0023dbeb5766c99dbd6e66d4d856cebfba900303f5942c171874ca5f742c4e011b3ce7d6580e04cb581e8b9830c9d760a946497c573985c08f35ff1a8f371877d2f74056baac9bb0d8a9ccdacf4b4ce0fee98bd0aa9cd57122de4834bb45994d99383c182b214c15ca52751c8239a0c8e5c79fd5765911a916e973fbefff96f5208001a8f0e541b5a337e31bf76810fa084d734d45677066e504c4558a751e97c2027b5b0b53ebd10f7efe6ef1e5a810ae6f8aecdb6794fa296672263b5670223ede689343a2daedb13990b8d815debb8af14df6609736451ea624f3cbac4cc2d895fc2412b9f7aeeabef41c80285da184ad6ea2f8fa3e797d84bfeb753f5553ee26a6e71e8b5344bd0386b03ac1114058fe10477cb303711a243399f570ba4c73d1006ba6cb70448cc122e828905da322b49975951a08555250051638b1bc2bb8311bc58198e490639ebcc55f1a73f5371163ee0805f82f2744c9b603cbfdc1da85fa0a23449497daf874c7da2028e5afbc591affc3f92a07783814a5d946e0ba5a192e7a39971fa617dea73d066b89fd2c6c11768f397b64719df9a06ba7c0e57b104d16254283960bae3a7bce6e0c57bfffe8c6c924c1ee512b01e5957e3ca351e8de65df52b74cbfc8a9374d8eca12f47b6d21c1b284b33880ffaca14b25853ab4922a11a8412ca57af72a859298ebeec9a638bebd9f52a2986bdd89e98dd44ce11bf3e0502d29cb452ce53c7ffdd172e81e0d048519e9db7a866ed266337973279aaed1c5191d14db0581e45ba6333c10890ceda5516e786066615e286c93a35a244226a971b323393d3f03a583a274f1739b02cb0966f006804ba8abebfe16be0574f1ba697b7c8fcfa36d2724588f422e28f30e220ef245dc133dbb657a4192b721004ba7c7d451baeac7b3119d0c04c3fa5b86f2ff9031c857b18cb035fbb5f0a3a18cc2e693590d0616a4e3c67394bd919feb6f67543afbfa384d178267e541b2ff471a4fdf8069ed5142bf61ed62437f21d59b8549c74cf0be9f7f8ddc6ba1e67544eb89c411b6aa7b5f41a5a85ba947b9532ebf05deb487114b06d74aad191c29b11084a998f0660ccd887e02622f054cbe3395ae7d2f91b56ac228d73e86d27500a83dddad349449d49415d0dd1777986e999ed3199c16204ec336121b13ee29b450a76b4db6ff6ae5265d38496a3a97345fd73431999b0f8d70077e662c073cc3fc4e62b454fe94afd63ea4666822fa3b66049bba963020f6f3665a8ac2fc6d52406cc17cf9c4d01c52fce06f30642088238e54b7b638fa98242378377b540cba0f4eea21fb72e261c5884e6185d3afbca7d7b38476bef2c034231457998d268403ea1cedab4e94f00400300189265b97bde53dd4d279b815cbca1f1961edb8c6191c4ed08af06d99ee20ce19e197ce1bdd1e226a5171496e2bd273d1748c6b8b3af4327cd81c16209d8ca08a5f9aff70293a616d7155bda4d235413a5ce51914841ddf76b7d3c4dfa8b6d6c15a7d1e59c454841d07f9dc34728d5132212b16334c942aea1c16c41a9d89cf348dd4275e6c287521e622a4b078b6b1049dd319bbe5db8ef483d4daaaf775cfc50fa4dd8fbd406877e3bd26ffdd6c08f202c974c22069431518cca37fdfa22d611aaff2203e01e30bc33cdaf91a8d64b3d5e1aabdedf3541052e275adc3f46462deacfcb043c8a33dfdf2492eefb2eb4d47ee693976d54bda1cd4ccb068e8c05bdb03e2f54c1d08baaed671fded8fd6a9f8cec0286b1c2993b979c06cad4a540ddbf070f884915c46a501f64b6edacc78beb229a78f5279ef6d255c83bb88c170770b80e0327188114c915f255ebb953406a9d2164627be68b4097967e60531e3b7b41360cd500f7b2e3035f30b4804d6e856e0184c9776220ed5436163dacdefb1296707762c76f26c49bc36ab0d229f4d2edc90a41a6546b7d59334c2e935518cb880fde58e4c7325c413b08d9540cd0bed2aa627610d52c268f5da4c6d7724623de46672244f2dde4f46a264c7e52e2038efafdd72550f4cc59476196ed9a8db9fbad203059731eb516f3497cb909808a6fb5b7f8e005370bc1c818b35326816fb2c0e6628ee6d9cfb400bb4c9a73d59671f7c3cec487b60d867a58ed18e34a0c6160dd78497833c303bef1ac53881871f58330b0db3e378a063e5a667e90852514507881236a2ab37dde128597728bf1583ff52e7ce68645fbb6c8777e63e75857b9139afb9288a9e3167c05d40f4f9a0fb694363a0ed3e3b5ca931f0a85bc2023a61c750ed92085069e60ab51720529d5944c0d159a4ffd7e24d561239131cdb189c96d4bbe5d22ceb4758c0d6144caa22ed61aa5422db11ab3c45297196e6c0db4622ab6d11f976e18afbb4bd9d7584bf8b696c611e030472cbdbafe217fabd7e11a0fc239ec9b1c900bd9c586d452060c218000e6b5fd3d845086d8a211fa23ab77033b2f6b7cdb66711e0a4c7a6ebcaa6381b1f6bcd2dd776c740508fd7a3b124a4c7d81362930e59a7b17c1e6f89bb2eba0de48fa7428058fb4a8b59d8bc9fb367c7410f8449edf129383b09768562cd4e15938f0e32cef0a67757cffeb3eaecfbc1752d846d52a41aa7f97bdcd6bc00636d9f474a44b3e5c31bd26d8d130563af2dfd03c0c5d1e87dee1295e0a143eec638002d91f00bd8c5828f0517018856a7b02a9d67362196c31f0ce46fca818adc152b85d51ff7aa813dc0ba6be1eade990110e01dcfdfbd93d0b1ecf02f0e301e8fbc3fb3b89494208f81b87878fa72f8401c46f9eeeaa69a87e6a6b8a44da93be60a179e23f410823c0b95b0b26308b6178a52de34f8fbf9cad330f88705bed837426d2bbb33d683e7cf148bfa4679ab6bb1feb643c18f8e989b05f0ef367fab2dffbeadd1a5c444913c50cf31051537f86b39c00b0342490c44db666902619e8201a9b585581b7dfaba8a8677a569c7b8f77692a37dad5d06e27f3574a61ba27f254017b76d3e9872803dc206ec1746f8e95e9a12582bc14d3901f854a069228e7cb2afd873ef5042ab36efb1243ad0f1dcab4ff1eaa51382591e00ddca857836b78094821f1d9715e894bd27570309de6e740f6b45295114a63b9da4cfd16ea81a6ba55bade821648c96a3cba40db7037541060c080da405ef51d3f725e10c9e7751de4563eda2853ab8c2dbc90e5f923883075e3bda39ed46a55db974a29f38a58b9f3430aeeeb05dec3032e9fdaf02c9208c254978fd8fc982ce8d2e3ec3b9197f4cc268324df5200d0cb659121c17c9377b9ab1d550f838978560f6f1c218cce9a058aaa80ef982748c6dbef156b984143fce866d08701fd686cafd8cfe5d46cb3406ac8f6dca95b9577d325b060a466d6af5d0a1fc10de7aaca412d26c17735a8a201e8d0454319bdccc703be1e2c5f4f7e66a7cc8142ec5a03a51f860287c1bb333658230ac3d2fb1efa3dab84b19ede19285d0faea8e7c72ceb856e4d29facfaa9c2c51f983692f20f3ebd92c2729755d9c5314f98868051a73d8d64e38a27c5af6bba0fb3bb911fadb0a8b47df18c36448305cc320304e23f8b71f8ecfcb753a6e20e029e90d432a55f856ba661a8635e5a227b8ecb636a8cc65933bbd5d42479eba198557a624e5d7fc35863af127d41ddbf3fb3ba5afb50e46761e619342b293ac58400d398309bebc18b643f1acdd460e07fe865cd8796966a704287e069c9d5e7dce776e11efc3c410588c85f2009df70c3f956ca16fe1f2d0557c81f452affa21268862b29936060943aeab3df12850c39ed51e3f6c3cbc28acb6d33f5084f08eed8826941b08550eab127319ace05db6026deb6bf8d1bbc09453002f6d8172bf80dd3d1a30b3e827c63783852cff610d6867017b9ff38967d97568b20a84f87503852298592edebf03d89674e5d42667b227c53a1ea8e318f686bf4a256dbe60f1b0c0db0b2eb49b98fcf89a9d847ac6c9aa4ba79b272dfe9b998b3f8f3b414ede7af887ad9cd06461bb0c8d8639ac8ed972e06c5562ebfe630caa25cbf5c67158c91e3f7a7efac05d9ec62da9641390c2f7813a6ade49e112cdc9b54079d3b71ec07c9d8f6eddfa4579326d01798724124a61e4ab6494fa1087edf2c581b42a1dacce03af818b3afdbfeb56e36ee65b102e6756a347bf6ff7f9eea0b6e1f766e137c5317ea2f64de246a47083109138b754ad56308bdf0555aff70a14f0f01f79d984b0506a780141b4a368bcb2ecfba48002186fd6af820c49081a67a736b4ecd6260dc4104c0866d041146c86e1825b55699ccde40d9deeab74bb5ecd24d2c7f236e3b84b127517506bbe01205eb25e4798e77a1e7b7d715c639764c0592c62ce35b772e04fc273ab24c6172cd2f303a47a019a1b9fcea77d5117a235383eda5317a1e835ea6a7911018d3289b4c104f0aec8e218c35dd9048dbc2f476920f405c6c6906fb112b98eabb5fe27fc4326ec05959acf564b5fdebbc668af566d4ee89ea63077d23ef40b61b2a824a8fc876707aa0eb669cba5f2532a9df3af359fceef317ce3629eaadf73a3278539846ba76f79fa68327675341fd79b8de704eb457fb78dd8dae2d74bc88595e933568794abb583e18c28e623c14ecfd8b4f5e7f6059e80d69428646759b1ee44a0ad360e5d34b62675749d7b2b8be6a493d6fe54afeeb8e952ef08a1b3a7d2658e7118bcdca0a95c90a13a10253544d5415e9ca99dafa1d7b8a2ac3f793220b08c31ef76b4feba6ea13d1244ca2aae614370c40f630eecb1cdb27602e0d186e93132d4e922fc8315133d9bd30b56f7615f7119c08b5a99b17e3c1e49327b676496890f5d98147aa475ab2f292ad39d8b21acc92111d0595a1b4235fb4ca79e25d64b27b43f835fdef8eb02d9416797ad57c51471e5310aaed87372bb1fb111db95c7f7887005f00af3896f60b946c64395405b706d345aa74e883bd23284199a76d2e6f53a6d73ab6099fec5d32bd030b214ea8400b8e549f724f2a01e9b2466a4258411e00320775ca7ca4123bd510a4950f4d46cd34a55b556c55d114e2a714aca285658bc76bbaa3a20a80f44a82839ce6c7870ae06104b9a094e4c71c9e01b966730cc3503d831e47dea54b3cbd826fab192432c23aee27459b3a5432ca4cbb760b71d8e29507adf55a582fdf2a3f6a43d370b66ff8e869c002606ad0b9093534a225b04ecd83343335b7bbbfc9d54e9ca4f67f5bceca7b07325eccc6b87b12fc0e64e6bd2dbaec124b0f822d6103020a456e60e1c9f9982d63de8d8ebf0e670ac8bf16b5d2362f98eb2044d5aa83ce50a9c8e6cd04214eed23f214bc319f666db24f197afc86955c8007ecc6377712becf95608996db44142aed160c6702b698c1b324c5605017e20659c940acc8861524daba6a4967a376bb3c4e80bb5d3c291604a635c3f0b593554632f48ce82a647c9ef300d02497dd6a5dc610e4dbdafd16404b810818f9ab79330447c51210e1a3bf5df734d6acdfb628f6962fcc0683a3ddf772a9136e50032326e8b166cf533a4873608330897498c677a5c1735407b278d45350bce7a1de1ed6292f92f67fa21e0cb83b7b536490181fa3f2578ce143d742b6f2bca7c2bbab868672b789063f832e6e43ee3bee1c70f9ad19a6a2e9282305e827cc0bc3168401d64a41b4537b822e5f1f26c50fbf1d60167f8f249fda80fcfc438e91cd0bb038c317330942be6477ccf7d0b0305f6b1d1cd1f7736e0c7938ee7fba4806bb3d6949067906c4f3caebd335ae15d1f5e0cefa32768a1650ef4fe3f55f34cb71560a258de5b51759b59f4ffe1f15feda76923fb4ab44c6aea9cd614e6fc78bed69cb02621741e9006bb4e4bd3a3184eaddf0a62d5529b916ca9f298fa892ecea15ba376e103dd338e3e59283b45d080f49552ac14bb2dafc06ef97df789fae30cc0ec20d4f2a7ace6bd48af9485c075d44c47146b6b8e176af2cc48898d8d812cc283765771a05d7f0a151772f85c4d70d3b38f9563eea9936fcdd13d4b0c0879ad2a755b73f259566963e734972b482b900f721baaf1a42bd4eb9a131ff0fc38a105ce782ee9696f13418d313d2da95cfefcb5a5c78b98b3448ce5958577daed1181b8409f13eec9b5addc192ad64cb9291888ad49ec06a59538b24fa0a64d934688cb54047952edeef548b18396f61e3415ed8a3f7b860bd22214bbb7d703dbda0b57c360dd03a8d4dddd9ccff2962bd32e9d6dc0e5767dba778255b2db69f13dd8f779fa151969709b43987b2c0c51d1e46e33653a9bec3e81112da1aafec134bc5f5dde6d22b13da15778577a1fe48c0aeb60ca152be00699bd0d35a41b2455ff8bba5a1ad172ad38242b18d1c0fb7425e4b911df2c8aedea3171456054d38b28e45283bf68e0a4ec4054d43bc3d38f1b2b195e142bd28107b5764b0fd0989814fb39ae042d0ffbc3e53b89c49bccf831bb2f5ca796df84462574e56a3e958e5cec18f7383d0efe4b3b8696ece40f5b5bf7c4b66968e66ef92e2454364a2d550c9030bd07908aee72b18f3fb5a8577bd6d2ea9f89d3f8e86d2611004fabb429a9fea0243d01de8032675a2d38b354ad1cff594b1e1b6952c02f9ff14c3a9619881f12919aa3735d0aedafeccf6fb65a2079d373c4dcd63e7e9e0358b3a76189743bd632bbc8bfa04aec796cd012e581db0ff6d54a625bf218b78a04b1b8d9b1a6cd04740871a65b2e7d5b81cf1115322ebe6fb26ecc68ea273cc8ee702bd81b0a0dd98bbe150f12c63544637d2c79140df2d34fa828d8557b71b2aefaa49e29d4e7d4ed00ff903eb23b0d2f0d88fee0ff1aa97407c45293fe7e3202a34af68c6a6bc6d7fe9797cbade22f632d9f2ce3ef1971f01f4dbbbd6a5d684e0ce951ce99d2d2c727731a9ae8a14c6690087980466c891306977b1dc9b6bb17b7ca00ca1bb4170070f00800326a88fe04569dac5b2cc1325b0fa4b1c564d68722a0622e6e0b4809a5b7d545f3a6bdd71e8e437034416c266a399aadd176d0ed7c1bf700f915e82ed2782226fa731d036f02ac6b91578ccd53c0abf615ea2839e4211616853e70fdb4a4685413d224754c84c54e4d7e61884bf23ba7357da18436b4179c0291accb410fa0f374e4b8852c3a8f5d0b8055bce3b66990e049b78672f1b4929f70232d38ae501cf980d46f8b9781f53554be516a52fcb2350e3d1ea8d4754c2e8a2b7fc7e5305b289d70d364392a004d41653655881dc4f54f8fce789011f0db3c9e6043156e31146238363ca91c82848a19dabeeee7b185c3f53d28aac1fb36f3c034e855019301f12880b548d810b3c2d6b5f2841818c1a008b33c36aabd852615a1af9dfe69e322747dbde74954901e30bd09df42cb9ccf5ba0afd8a3d86c063c1235b8a010a5ccdc5c42c71d8b4360c3f19678bf5099164515ae6ae6c5e5feff37aac7f0d944524a337c0a2d0642366fd263002c6305b54d49e9674d4e8596e383db3f4c87be928078c5d8efac7d27eae53cafbd721013c31577853115125f4aad448db8d5c576c08903b20377e3662032cefbd51fad71e57942141d0c6bf26d3035dae7ee50ac94269fb783f3292870c88f3d42c243c466150c11f3fbbd35d6ca04a40ef3f25835f20cec26c2ef4f7537c2104fe5341e47b0fa037bfe0d5df90058122e7906cdbcf5e5aad61a2a9996147b8d60c62b5e9fb5bb11a6040a69fd9974424308a881de1c90dbb545e389ef78c1a310d8400196b133e04b513319521fa6f8df9d80ebf0b876a05f2049d18381ba2eed42ec478dd7be3090fc538cff61b2690d2860ece1fbd2847a9c0be9e1d0e590cf5ec816957c01529c72450913f45d0efe0fa8ed1c7e046b9b410b147c0a6aeb6f3b243d4e9a48cbd9f106aa88ad00b3ffaa85ed80be79bdd9a6571408587142461c660bb8517ae3650e24e36d282a3553c5fc08449a28a464fa7d3227e6952004f73d63d46296e507306b21be4556a10b21c24c09d79640df7e277071675e8bf1f7a7aba9b2c6448e12f4f7a7a5b1a2daf2e5d67c9c723248090800c08fca5a40fbbe52edcdff01194576987e31ee76bc624e9381f6bae6b6831c5d3c98ef45f9145fc78c6131f581abef8f989427d8c90d021d5b557873476a399b4abea4367e0b98145ebe06dcc5fd9a01c2bcd78e1283428e450d05022450e9cadec33fb6bdd8ef887c5d37500b4736dab55ddb9d47ca76499cd7e509b95a9b8a2e6c61851b2c751339566707a76799b5466e863a28dae504084d32dec7c3211ff901d2b0c2e8a14011e897fe46919c707da9c802191e41a32b718d4f395780add1d16db58da40d01d3f5277340cd4ea1f80f622309f9fea2ee26ae6448f80a70f0ac2185f129f0a3ee8364cbd6787c6f45622760cd3f852ed3b5776bdb1dd4001167f77e60f773affb3ca9d033bf8587bfaa563065b82317bd4aa2e4a74e6b13cd5c32ac6ac0d880a0ef10ddb3278521c3b1fb6032a0de442e75f30b0ca9eb37b4e77bd478eb65de5f0db6de1a08c656a18764ef072a096544aa06ff5e605a811aea1f3b60122de54a2c8352410d8b3fd4fea50d19f8afe1b2c1a5c4d63955293949591735849cfc38889398cab52493652e30833da5d36b8b76b1b6090c118bc56fae19b2c4e1b9f27d234480d557c457f372dbb43b73a2e6ea99107f49a80f94c85d9043081cddaf0e1149001414d4266a92148ebd66a0652067a4b86fb9e53545dafc44bb5ed26361ec28866e7a5423782bc01d9f097b148151f89d96559ec1db572c48fb836097419881b9b233567f4328c87848b5fb6c59ea4e37e63e09200468254d1bcf6ff99b7a9f9604173a62b6a449be970b1e1857d45b351e77ea473ca67caa225f1264013df5b75b20a13d01e337175b111d94110ca7d80e1e63cc8574e2054b7138ce2a1d9b8104b7f4ea1750b9b6da1819147ec0e0691da02703e3c7fb248373ba8570cd37f21d4441ce2f9870d24b7ca0c57abba79ac434ddd0e104d88eefe6a21850e5f4261b195d5844cb237e5c28a527455a84d410ad38db0bec95ee6312e287eee160f245a695c795917392acf6a84504161ab22492b879729609e8eb84d5b88eb6604036015df32570944a52d82a793f9cd19cb593143f2b82759746684d9e1ced92635fbbb1443c1d3c1154d09eca61777dcf8c46bc5ed0aea84566a3f93ca556c6d282083bbae4e4340931be7a2a3c47bbcc0833ff82fdc913f2f1cc6882dc4b664f383553fc6d1d493a58f82182828913ae9933b8e1d46ba734c3ed02e1967dfee76c6d7a12e9ab78efaeb6b90b4df2198f4623967e7a638da16a13aac64e5538ccded2eb40b098aabf0d4d3077985410be28dc5b9397130f49a8b2ea4ea2e5f371a2e34ef2deb34ceebf350122c31ace49ed85b107dade3a26f5d9e504d36a5e1d9e30bf56d049934d07ec7f0e134feefa563461bb73a41a4ffb335f24495b28780f64a48eabe74a909896b3562f3486c4b9d51827984c8d8118da988c0d083d86049814cdd52f6e051c820d8ea46de63e7f710b8100651538dff6e6824ecd9df0e2fcbada7abef10bae6b84e462b828cda7b60b246bcd2eca97382576ddd24251decf7cc7081e7829609bdee43e338ca441daf9701874fd3bba13782fb05c23e586a15666b3cbdf92b6ce34576e37e20af4c59ec34c22674978d5034062abc7cb39ec2b64f24bdd2129f3f72710c18dc77a964e5dd258eae832868306b16c06c6aafa8267004f1327a6b00333a367d7f01e6aec58763cc4faff9be95affbbcf37611e67ac5ce55a440cc487798be697f5447f29d5899237192a7d74c9d3b2270b3a36ebfc49c42d7fb229d29891357b01e2a2198360cd0687b1d2e0495589b2f4e38406bc4f9a6407de17444f61315ea0ffee76b0d5287309c8aaa38ad546748462d7c5f5b873bbc24ec9fb7b0bd488d33bd92eb6c4d65213546b563b4402a65f8efb0a0d142fce89b317fd4c83fdc6a14791126d8f3b10077194be3cb9b2c2fa918654acb5017f2732e6e23e21cca18968afc4eb9b3aa47ded521b40ed8f2d2d6ceb4714340cf997e15b632822853b23f8323e5ad192dd70cae2a22abf02140273e1f7bfeafec0b850ee30cb250d90ade5d084181e4b87dfc0c15985b2256d3ccd1a219c78ce8597fd3b024baef01b3931926ccff32e4002b8b1ad5c6ad5381ef54ad7bb3f78a3d1deb6ca7aa6fc2ba070c27e43c55a1570528b7e36335160253c7d5c8ae9d68902dc1e6e27d96657834d2e60eb8ed831a260e07e307a6bda9a44bb1cf7de86791352cc78667686e60e05b2505c4a4c351e696e16ec514af502860308fed0da485b92caa676931c2864ea179e987bb0032f276130a8efb7e7b7cd8fc43b30e022e160c243efa72cf71fe146242da622207c09659fcc2e030c3ab96b73f2b67372c175b4add20e65f39de4c034d4d4bfe4ffb28901360c70660b3e5b0101dc656ba8fd83849789172e4682717f05129129d578f44dc42a38593e22113ed464afe39119515063bbc14d695accd673f1872c15e6627609501ba83982b53346ceb56e825ea3a880ad256aeda33be9fa068bcc863a7dc678e77f2d964dbc78b5e39fecb9556426924af22254a7de49e86b926930e7f8ea227d23e364c70b765b2dbafff3f9a9aa22e4fbb270449862dc3dd4fbf5148bcadd455b209e0873db0d7ceb19a73c51c4faad04027b6a6d29b91c01344d1cab9ab294e42ff57b70bc24623914d782683e123f15f70a9f33b335ae90b6b90d25da8f65459fcd60480f34b85c82fc236a210c15658ded7e8ffdb61b1ec433297c1b88d6bce9994a6b88a4176634b6ee46b1ea0fd36db333438aff92d4e0d3671dc959a9c3607da12c0cfd60729876a1403210f2ed0e4f01cda520c3eb4c1ef2614d97c41c74a520c930db356fee2272947eb0e68c9d16bc5573d964346011a961488ab175bb7058bd8a2e7def7fa689f5b68ef6f72f044fef1739adb41aaa9e95bdd71f9763242c6cbd47e40f4ed083d7974194a0051d38239feadb547cea974a63fe5f25e1a253158ab1586be76df1883d40126ad57e07d1720ab09b758d90e37d03e7f00a51da1b57978d1f587ad71d61a829826cb0e35c80c6f17a837c37ecf005d3d6fd189f9ce9b17ae165642d2a580676c91f79042e6553e72b48cb6ffb6ffa5f316c44d7544b50da218ae3614c6166f8cbd1b03a7d3cf2df1bb375ce65f9866ed83aba88d81618ac415c8fd487079e94498f306573e8dbaae8895d1ec004cf06b28d051019a485b4d65d71586fa60b914e044afa97f0e71d6d640cdf310f05d2d2a56e9c3bcf09f8ff5a15b81f59011cdc4bf2858f4fae92dd6d8fc4976fc19f2e3358b2bf53e043cad030300adeafc520dc1a212acab466f47c5a32769c71dfd8fb05a8f5f55ea5a40bd6f18e39ad0141918902c2a9f1792e5580c4bb4dae29d5faeb2ac5f48af0e4ef4b9f672122a6af18dfebe9f38eb11723b6c95324eab77758a7b8c37d373d5c9e0fd2e17a4071163f61b86b796277a78588c488c1b5e655decc259b1dcd65fb663998e975e483cee28cd696560038adb6e0eb3678c9b9c6c8ca19c0661f8987bb7af1e30e29e0e88c69c6debea6e54ca478aace012057f31736907848638b5e142f52590148efc6f2169ec92d83738b0d251d27e1b08977c2fd5932d06d924bf98523707554fd42e212872e57fa5cebf3ec99205dc43d926b2898294fe08a6ba0a04137f23eba685a4440c470ad1d19f4bd23173d6e05e67ef3455f1ecca221d5997dc05e18b9bdd2ee99ffaef0b6a7572df724b17799c9d364abdf7aa5cc1b19d31a0b1cb9f419f34ef9410f482dddf11d033510e451283aa2e95834124067318e53be81c847103ec8d957c9480b8f195369e55547abf292ac7401283ef99491ced3b347a0f1e66f39474702efdd0e52f1013a8a58cb1e566e93cec1e436ee56ba3b52a6ae16209443646e9a10b42675add41e7d85f638a867e2fafd2bfbb8c5369fb737c93f92130ee6f13017a62570f3a2ba7fd0168461a4d0dca4c28d8c7a4809b4e359d7103d58977bb6944bfce59d5d9105ec39aaa812210dbb70fab5c1d3f593606eb0a40a77d4482997406948f0ac76575b12fd02c36cdd5f6e8facc6b5d3ba127a7d5425340e8954d9a7353a656950c25a7746fc149d1e94625f968dd3890d5509557d69614d0cad0fa5814c8b23a9eb74e128c8557537a3e0c3dce8a7ee2c74bc37bb6d5aa841a599e320699a9d1236f6ef0f7fe0058b2898d5d0dc0d5080e69f8061ce9bce8512d10e077ca44d585fe2e3b0d861372feb30f35f2381ef0f8b5e76833f0e80c17d0a58c42d44de81c2ab231b867b45f309f942d8c1d2802ff9d9c18ca7a11458791af411c174aea5e7aeb9588da540b6312129bde25baaf82a6cec69c45af46556dd9d6e65ce518b4b60d49e48d68a0dd83fd2ab650d8c950c75e9401d041d4fd3cf10cb90ba0c1e892d450eeb2b5c3bb439ac4fd756355bbadd8ab94249a19d9d408d4632545aaf4e4e0304ad8c4175f07aeb66c3c32bb477ea09d69f212530c1035afbcf8f6b09c924a7f901f5997acda107d7925ae5daa80b815b5d1663b85eda40dbe1f7a0516e58f1f579f5177bb25839d1673c5164595d72ca191da98943f20c9cfbc110bb5289418d30b4444de3220107aac14f9a192c41e7777759b73cfa0fb49741070971551e72eb9ffaf4564ca2ee345333a63c25b5d2c54567833f499f6695c581e1906cb3954ae5e9796802a4175a8c478bc1cf7bb23e41de1cc17f94e0bbbdb3efb49a4dd8aba0150e5cd8528f29c646f1708aa4e9a8605baed7dbcb64d49d83d63a04de49a4b95584f29f3a55b208e3bef99222e1520090bcfe31a7882d540bf3164a7dfa304fd616858bb0df2c83408416894e60b084c3d4981d8b8099511aefd5efe45a019e977d63044ab0411877b5057865751a554c8b8885617c3911b2234e28f12c7d54eb546e7d65f8dd3f15b5deaf06ee737a1aca7a021614a8c68bcb2a212504685afad327cf9f4d487c9aae8d9fb55fb4232f6fbc0d1bc454598f05bb002856442c3fd3b4575b6e67e4fa40806f5cc09bd0203bb520e679221c5ca7ae0c431bd80c0b23882e40166dd11ff94c54c58e10e9960f62166f309bc0c5dfaadbe4fe74790132b238bae25692649cdf6f218bea15cf7d6831b39c4add51a9e0061e20e67f98030fd07847f7231b43f49e84ff2bc61da59d2235ea11ffbb0c0f8c266225cf9c76ccfcec9278500943ebbf6698ebe966611a4d788786a570339bf3d74c3a9eaedb02f6ce028c45e5e93fd723cd7012a2c9364e8602aeba144aa8592f7de4cebfdb59bb99e8b6a4dc072cf57cd844d561fe06816cf5a80efd3259acd0a5f30399aef31e0dde6f4d104a42e715413a86424833cebb021f20b608a6cc801f6fbd56a11884ab09fea414650990f5f1ac1a7d86459a09e27d027e706fc2a909efeee7ec5cec3c734cec720fe23508e37c4e5aedc50d6e7c5d83c7bcb9dfd9fac8ee8edc3aaa4b68234825ccc139a05185f1b814c14308199bc8b7df39824341f20e64445b35a6f0797c1f858c6df088929a875691247799fd80aa2d9e1d3db3b9aad447062ae152d1ffc4f516b1903a18db4e7d05d461c67469aeef3c5cc00fbbf61e707672d2a8ccf76de51b94fc662a75a6c23ea3efab455905c6126febeead6ad51208f6168476e4e731ae7a15d67a03bc1de0674e06b63fa7da3a198142f22e61087fef8ba2e63bf1a9f1289e1f5df9d3e168f330be087ebde34a9e6666fc917eb2dc38d4b239fcc1b724aac04313dfc0acd4264875ddd027f63c7e25c1022cbb528070d9280d4680db443e86d3e4346ca5dac7d122458cf727d1d46df48e01fcb08bd84422f165c9f5d75862a9c275560a66ce08c01d46f40291458f9dd4a7f911c636045b824f2e62a4c2a63179480e10225aad04872a18c7de9802a6ef27bd7d77c8ce311e71b3e1619f0c43b30cbb0f33e18733821e127dba91e0236c202756f69297bc62f35155ad6f2f874f9796057772ddf60e85c5d8260f66cab96e6c3de1f61bedeceb7448c9260f73ca1f6d5fe716fd4c57f3c7eb573f78ea6c7c934c3f715ac506c77e4bf51291cfbe16211bf676b884bc60e3a715861a036f26f79900e3159c3730421e66a6fdd1bedb283500eae449d7679831fcbaf8e18016c90f998c5571e80d89aa28267ec4070f0c672fffd9c1bcfd9702848fdf53b0518c92af28e5528eb891ce79e3a63ed289e9356560f5b71a9d48dcfb3f4165a3b097771b36d3d00c7288f75cebe7ed40def35009a918dafc42468ade2db632dbf0974baa8ef3814246158c7b071bee2f9eee3fd56b28054dc3de742ea5754950100ae967dd5c98f1e9d0269782fcb1668eb461f72b8fa160eb9503c1d9df970aafc4de6264e88576e1c5a01862ac7bee59b33a277fb80f25a19fcfa31cf5ed96446e2627c7b8b0fe34f6eb85ad2f658f4f6c1385e79524bdad68a60565dd0d7c11022b693cae37313cd6bde8ebedf8b531f5e65e351cb6a12a0f5c4d8b614c11d7511f9b9ef2c67b97ce097036315a432778a1346ac5379798e66d48006fc1588a7da466506e7c7541bb4bd61eacfd096ab2ae95270e670a80f4749b5d9a08bf687fc044cf52dc09bf6ea2562ff2f1f86575d8b1adf707a61a7368dee79791e20fa224ae1b90d55dd92bba3af9fbb7379a2675b74e87b133ee52b00efec180c47b06c49dd361d6a0f82b874b7ea4abb4b33fe6580545aa1971b307417d961f05191eba5ff9bef03b2b1ad0ae402cfce706ce66138ebfd407c96e2fbd5238dd0d6b3281e6e5e70e11219988fb6bb734cdc4f52911fc06aca5748d70edd1edde4a20de7820d3e037bce6bdfb8e59ce54550568c9c04330505339ceeecbe6a020d6d091bc07c0efe308f98ec66c29dd78d307f2037c9ed5a24ffe24cbb7e85d25360e0f4a662d20610d5d89a23348b41226d8f0cbd7fb9373d5563616dca4d93712ddf0778857c1479ec51f00f18107b29eddd4a577ee8bbc015ddf929232e73afe2b1ebc571ee6fe0b733471ddb0ee5ceab6a3567ea3e1ee9427ac486c47040ac05c5bc297c2e6505c66a0e34b2e66787c2c78e2f57c3f4fe19461e6c90bcc69b07fb9d23ffcdc0f72b5b1ab55ebcd403ef1ffdac8d85262785d75f42de48288197320882ab327cad449a75e0d7e6ad2845ffafd26674c69696191d7b23e8d0eb5b2cbd695d0ea8e0a18377d427fe3c18f5ec0236f3461ee22eb14a34e37f5beab45cb622d358cc2b5e909ee1f4cc4441223b97df8334a61493392bbced0eaa7b8e3832699dc3a03e9283a482f9effe9888be32455aac5c314c95049b5933de3511c2a69ca9c601d63b7dbed8a7367e25d6f50f8e74e126607a3ca7a629fe5e6f8044c0581bc3410a5c06ff6487033d91d56a6cbe75d77b30f842e97339d7c6514b649ffc2d12b2214a271e96839825c3fb29de5291dbe0e6711dee08190c0bef5a7d56b40f682059a78cc48ce48d1d53ee985ce4d87b2bebf6df247ab311200ecd376a6dd64b60c759f61eb116186121129bd6e24eaa6ea936eb4ec8e5dfa74865709d8dd000c6f2a7085273417695c2e493d7d6c46eda6a78822722726a1c7683a3ecd671c0a9dccdf80cd79df2522b8bb0d3e3e28631110616f202d3c55b152e1171d5f11fca4072a0ff2fda9cc2f7be1cb90c080e27d518882d7e9bc093b3dab6d9fcbe470a7a596e288c2625077d0e92a4f01c3ceb2664575d1a8ead28daf2f6b33493aba5b7d8a1fcf49980b699ec56dfb0a278a35c37e17356cf96b53ad9711d6395271a4fc80dd85022338dcfc9af0be917f01a7a2bf6b78d2e4b2a6e094b208b4aaf033c13faafc10e777873d95a0b8d17f1e303710279355372994e18c76876720d640f19106ec3eb14d095be37d9b78a853fe88d736014ec6752abda8d94ff219864a461de51f1cbfa18010351f9db45316d01846369563a9e31a1e9b2af75f5da8edfb6e31f299b941b51b0d2eb4c281609197d8fb1a561fdee9b8190d51cdaca6c814334695abc85bd06bcde92696182c08455a0aca47a57aafdd38fa656a2c3070a7a4752eb5ed04a53e78a0697342fe90c76e1f88f340ed675112bbb954e1b87325aa81c0d301b5886ebf3a4d987859eb8e13a7fb78e110d4d062f2e1034ef771cebc24e6478cc8bb7221b07d4b90481614b9476ea8c5e2e9de13916c424e663dc1ae8b5c8d24ff61d4819050edd1d63c888c537ff80905d5c0f95f16edd9c6105d9fd65de93afe0a1ae17be3e3ba96b2c44a57a68fdb3f87c9677a51f24299d3e4f19acb79f1ccca6b96a78a7c8c1b008e7d92850b51fbd8569fd01b4cd8a66e96e65e290f2756e271b35d8f1c3381ff7b54760cf094c5392288a425ee947930a88a21675bf4ecf72f097c3e5b62e30819632a6ae8e0d2f0c6921048e2a6e041de0fd27b3fa3c2dfde9af446dffbe22e0a6e08d043c476ed9f207d4b0d286f2aaa40d7cf419771f4062a4c6819f8258b52191f045853ab275f4e2cfe5557fd6406b2877777b573ff5ced1e713611d39a03bab1c3b32891900089b09a28fe25048088b03d6a99aab12372958de2611aabecc5b6f54a94e892fae6727e9073c6662022117ec54bd9ab131ef5e5b702ac551b5c58ad06f09723ced92c3062db84fc28bbe1cfd33e29170cc6148d72723d0d3f2975fc62d42962586e6fb62fd5441ad32f4780170ca6cd0d54d9be433e06ba417b219604d34bc987fc1d3e01ccb4de8551dfdc87cdc0124385c46edef04566a7af2141707050ef56d9ef100c7dc02634e23b4e3df4cbdb155f662617c8bbfa8648701a4e51add62e58f0dbe1181e92d42c6042962d7fa89618e43331122c0b2edcbea7e2f27dce122bff45d297c497255e45e71e3f78ebb5db7828335de3b2c1c3ecb9a7d1fde237fb52d678776caa2f3557b5c5c0b70212f0f0a6dcd32b816ee5911f2f9411dee9e5ee2ab11c176d375ff8c926441fc05b972d82173dd775f9367827b9abbed7df632f7f6b8287f711d7fb11f8c6f9819321a62bfe13bb0dc49d20f328dfae56620347821bb3c32a0dec2a44e04570dba085e271d117305f95a13a994060af00e547b77dbbc79715de28c1ab42e416cd0619018f77849446baff2f184f5e34dbf5f3d7cb9dcc1169472ff0c2c9645e8db6d734875eddc8a24e8c9818ba84d21ba4fc549e86f905fd69c15c9dba9d1fe1420b41d7dbd5b0a1c1f7c47f0d77c488c067ac14c81f8a64f19d9e225b2b1704f6060073636bb61ce7fe35d27a2768699960b4a25a0e7b928e88c218f5e166218c1ce01330b7480528442366f2d7c00614e113c72bf1f88d72d4cafe3f680f5c94e363d7365656e1a390f76540b1f2bb4567c4111d5ee236bb0a29b73bf27a67ba6b9d2d8087d5575584d1a2d71ec996ee51987a6dd7621a04d34902c64bf5af7af8b917404a3ecd8bcc04373489318877a8b107870862578d1805c974412e13037374b647d60f104cd094f8ff9bbe24564509c46ae71293aa822f377ffdba2842d5a028aac249156a62bfbb620026f5f793e905353265fd6bbb0ba6bf1d921a676fa3b9ae0ff0c46d7171652857c2569755720574759165b3abd256181f910d540ff55f6a128fd694ddcecda64912ec9b2a17c34ae5935e27c831fdc439f719c5bfa58b9af1b1bd8395ac728dbb369d68f0213e9ad8a973b6c6c71963f1e9d32d0bd5449bfc69a677da16fa08837528b95d2afbaaa2d56c9c98aa24bb6aee638f6c399a92a6cb810b15ca84e48fba42f1a9a2da10dd61a31b51e3879410732fc9de9d5f2b597022e2f3bcd092151248ff754e3fc3f056665cb5ed691e30491126495c3c79a3f668c7a5912b491f2f7cb49fc384a3d2036f248e3a48b7fc222228dee909e0827e801ae1253596f0e8dab422efc5eb747c655878ae611dda1711d55f4a8218a52c2b931205721eb8b77860ac91f33f64187579dd22f151e3250c05a3973eac8bab1eb47df6ad686539735812bc3a5b64f3f090f1e237b963ab3876998005836aa6707c516c2ee4e5b271816d58bb93a56a083c103058415fedbba6a0c5695b7450132ab414b592a886b5a535a18eae59a18bc996bfff99559453b7d1bbb84809dd611f914a52000bc1ba185ca61dbe0300c09b6538e2c6232c7ed894ce3890e407bd3d84a307733d9068fe0dcbc704dcb2444087b8039746a53304c9f9b77ff88a374796653a7c3fc6d1177948d915062507f01750d3cac7d32d06674f6a1720a752199e0af655d1588537a8a7d1ae248c47696c8415f3ceeec014a57618a12fa5cad202c1feb599e16ebf4e49fa020c60ad55a3cd9e522613447c46279e8e364c73364bedb3004b1f47ca24e24c73f0db6bae4017de4c03c0091ff1b0b839f66824dd5ea4c205467494e247f9a9ec747b76d02e43372b095ce8cb1872255faa4cb6089e31a8b013663b69ee695f666985c53285a62d43ccad52ad4e62106f414c0e2afd920457121f9f0d9ee3fe3adf80aaf0c0885ec7125f0053898cd20c0c413b6c21fabc8edeed51f9ff6a845576b6349aafa74bcdaf842618488b0058bc09a2d61350eeb552c355915fd5cc6c6bb6a5fc28b1f5511c0f40e84a13c9d846053ad880afaf9673b7eceeda505cc7fcd4f5d4d8cd4647e15866a4f568a329d89ad94da2e4a4d04382f9714edb744130a8689ec5134df47c9b39ab5ce44db89bb09255e2f360f24203f00e0fca7c8fe28b31eff50f4532ad78594566751c3881361f81fd93986c50bc553a9943b5f64c1d281592427450ef746df8621c979f879c5d39ebad24c9c553d29374c340f6dbc28ce423a1d3a89577bd02fd6f68cb5557dd641be0d4cfde12d0b2ae99799a4620ddea22148a2f0a4d5a8165179c2012607ba3a8001283f168acb6796a987453a2b877cff83687b425d60234532dc87bb60eb1f693501e8b66787cadabaa709df8d42d7b9e0c6481158d888374e55d41d724c1d45353804243499c242ad903f895020ec2caba4738937d38765f8a2c724bcee5a3ae3dabf3d918903086fe6cc730412ee33429e0a3fdb0416f0e5d51b0197bd331497644232536a6aab28d01bf43e48e5c0a0293101334b5000fe8607b50e793f079593e7cca45a17976c8c0fc8fc7bf296c0c00cc789f55c1031e67848017faf3c8bb15b58fd227beee45a76696934789ef56841f34309ffc20c85c7d3e65e9a5763c4daddd6d653a09c97c832012fff81b3208b734708a75b0446c3fa49bc172f667103f42607ff30b6716c6b792d22dedfaa184c96ecb4290959f74ce2382c76caa2814f0e9be7d0bfe46672a5f9b2756b5d7f825898f21b2bed12a5e41769b3612aec83e1393de76fbf7770e400326b97689d6e1c2fb799b74fdd6f85e7c6760e21dc9260dcc718a7224ca3811a42f71a0ce007aaeedb6e51a4359c76a3add2ef5c0a4c621b2c9dc82b0ac4466d51049ea4f60cfe5c080be124083f642d1e896ee8b00df34da13d30e573b113fda33023de864a5121be16859917b3832e187c33ee3f159d4d9262ac2273143cc6078d05c96b723870054b7769b1a417cf1c893f4ec4bbdd83cff72684fc1c043863cdd6309685666b35096bbf1054bfa8cd61a605a55b9955e1c8d9324f734c5d01dc50c5240ae5c1fe382d0f6e092c7a5491d85d36fa09a08e03492b2838d4ed19419cd82f07fe8d3840d949062d8fd111f985d6e75e702f9cd73b63cac7fc05f72dcbf48738232cf825d490981e281339439ecdbf3a956b24a28ec734200f3a2710960c76351769be3aa16782ef8df1cdfe21e35f824fb3643879f582ccdd956860a1341fceefa80efc3d34a516938c52e2b8191d13a3392f998f30207e4b8024b4283561c3c9bce328a28cbbd053c1da0ddbed10bf1af500cb748cbdc086911f785cae60485de7a1073fbb8f6a13817dd78f647e7f4b04ab114138dbc41acfd909b6cda6ef2a09fc1093054649735f4eaaedb27bd0fb561063101fe3c599204a5c1216341164c92ec6ab56f8243206845e6a9e813b6ba134d6772539c5fed0fa527717a73649f9ad4c6e4673f870867c4bae602a27f1006e0613db03928d6ca3a721ba993b4088a81e32f1667486dadf279f4b320a3b5e9b39ec6149bc94012413c556108c28cb5249c1f1c7eee98556ba674b19b814a5b3ff366cb0f1ea133ce8fd1755aaf3f146f5da21fe3e80d0c8d767af6dd1c91a0b707228b44479fabb46e9c77c27e4d8482a9aad2642603b551568d14b7987d65aad6accacb25dc56244dc504c1989278a6951de0bc9df09461647bb217c0d6da1e2fc4fdb1e1c546a294d951c149c490ee4a111889c72b4e89f1fb226f8366d89236d3b6b2667cac611f2f78a44029de8f865b756d2094459205223d4773bae45ae2e9e688e2c58b24c0fe529b13e2d6b71b70d602aa4f401ddb9ee0d6457444268003b6214913282fbc4ef5e3b9c139566f8f83176a974c8760d4cf26532a73aa2e9dad2ed2945d854aad02d70eb66471fb44dc8fe8934711b8b7c96f1dd8a7c3619c2fb1785e589d18bb743052c0df4acc35c6e6b349927aba830d8cc3de46aa6085739e7a9c5befc58597a6aa7d1ed0053fb7e6db95c7ff6f3bb2b18d956337c60184a8dbd78a4e68b246b19e3fba915be474b5753a853696fc7b2e646ab39404b7f68de34ae7c20878b5da99b4a6c1970ee3b51bfb998f232219895692941edc6c580f9c45f3b0216a4b126d202387181a7c1469c92a61bc36f13e154a0ab1e78387ce55a2ad7725136041cc246a4d4e354c0c8101a13a3bb7ef5079a6d01210bc76e9234ad751f95edfd0d0196290400f8a1388b673ec2fa5f4f4f9f4e9b3174b12175bc9b43374d520e674f6cf1007d62628270f1e1e5ae9d40c36ac477f1043416a1af4bd92e3a47bdc18c9ce475185ef650bb2d4843486147771ec7011e1483da4bd9aed80887c61e801c08d67df8419e96442cccd669ac4e810904840c79aba2c694ea34916370ae0d6c19635079754c16aec221751332fc31dcc5482d1669a5b843549ca3755fadc24961f913217bc62d401bf6285ef3a43c6602a6e647121bd39f02f150648eec8db9d27865f8841935fa6b4c64a71b07bde59dd3cb50bed5ef98c5289958bf4efd01b64649f736a4c4d8c043f1aa1cbc65b5f99f4525a2001f58cf46d6991c060390d3d6f97967b6ae3c0c5ec24c0c2bed695c3a29aeb833ca5f84b4df97a803b34925c6201626b4be9880c3fcb4a2d580e0f48d7409e914b4484a9bd32a4bf0e40e692342bc835b12e4d20fa25ab746c65caf464ccd7a62951c4d004f6fd1f09c1e81039765b6b540297d2517dbbb8dd61450445190bd7280f7272059838f41580353bfdd8bc1595ba1c0954d218d2802d9c395ed88e18f405c0fa3261be04f229cd97075b5a4ba130deccb056455c25153a30da0cc4c53080be25dc0f7e09e5cc4d98ea9c7d0c3a41517085ef9af1660d1ac1a58d8cac970cd00bf95185a2135dbb42f44caa43f84d9e9e30b90d036e2b979fb38511266bff2d98cd4c6bd94c34d8fa21b34c0395b9ec655d81bef56929cd8f9af5d9f0a4ee7c9b1d198b75cd8e0deee528b85a4170bf8e634c5d7059e488d8323131c7133671ec63440eadb20ae4e3b99a755c8b5e61d2d3ce5b02a9dfc8d2e860ddebe57483487a4ec7fcbc2c0c75710ae22fa7f461c286f4ef8e4bc1cdb894fd2527cb0db5e6632ddc3093ddf6eabb0e1d4834a72173cd5be62533d2ba16ca592aeeeda764ee78c740e8ece9fd494c50471ac0716e3b73d644347c82c67ebb1688ae80ae3244057a011deb7f1c48ac0d11ed584f301b18ebbc3630e3fa6201ad517881d0f55e89975d7b6cb53732d597989b7cc1259b0b57ee57402a8636cde95480a381bf1630e2b1915a02f70148d39865904cb037388679a43b329b38546ab1340378f09fb8143323c4c22c2684bce726d8fbb0a8df1be2be4ff8e18a8724bfdd66305d240859782718b6cebf6ade449094da7498e06e413d5620787581dd4405536882acd2d3d540fc817e5552db773b897ab19bfe09c91a0c6f81fa69b2fe1d27a4c94a4f1587b3082ca23f4deabeb4ac71917c2d2b15e7134e481bf7927425cca9ddbab7a8a9272ba20c4f6b538dd32dd6d02a31454f9d0e4eee7017c1203a3c050fe7678907c0903536708df499fc4ac9e9e2fb7f204dea9eacfac757e65f4f84c805cea9b14fbf17054db8bf3d27b1e9505e143474ff8097a44390bfd7ecc6bc1341006c6ab8f4e2252fb13a22c039dcc11ef6710a81c5314042a2384d5886832d1ab92be0bbfa11f4a416fd3fc3e7cb9b26ce7adafc068b229325574b6645732afbafb2b7c95c84f2aa14382da7f5e34db02e4645904767a8f1c78339e9cc6daba8bfc266945400d45b4a2abcf39ed5d3b654fe17433dabf80f4778e893fc12a2851090de76d1ad136975abe99d1156426162825e0d58e1a4304ac5e8956d4c25db884406f5b5961ddefda31b839ca063a381e305d512b7d9238c4e31fc82c35596197b05e1c52d07f08a90af9dc1ef63a53aea4e8de8801f8a08ca15101e69522e1ac65eefc6c4d7db5f04d9cc4b058ec4699010e281b2aa7e99468d2efaef3ca4ca29dac3041461155e25d049e9a7173f2984ca1813663c0b35a0c8d1e84d4ecf3e9c3fc1c0baa748302c5024151efedcb2a887116ddc9207c40c19c7c33efb666a8ee12515b5dbef4fe665604bb7f227b6949091f7f469465e9326cbf0c1287dc15df73e7aeae07f4d368e8f9c435704e012ee41a6b50b32928f7606bf9a705ae5dec7bc6ba0676d019124ffb6984abd58a0bb0bfdc9703a40314224bcce75ee21dca371a304958c128021d6640ec5d5cdf149bc63fff0b67cfb9cfd5b2b49461491b005fea63b8d8c2229bc4f02c530356c111b5cc9831ddd46eb12f3bdddbaae2e826965fba39517a7e9e393568b62d7ac74dba8bb90b18fbea84d115a8dc9f35986110cd9e43dc0d6c9924defe73c57fe52141712500c9fe07ec4bf18adc82caa3e44dbfdcb6d2588e77fd53696e85ce66a27672bbb20cd83ab7b01d131ea152d4b05368856df4f2a66a4d198a91d439a8c42c5070d9d834cb75b3ca6643e75e34467197eb9476d3f0d5799060a4da5859ce45cced2cb71387cd8dc79a7a8d640684e3246fa6827dc36cdac7600cdf5c66510d6488c5692167c029c7149f8b2ae0feeb72d178e552b0e5b2960d35a53ae3c17f665dc8b7d0a87fc4c7a6b971c09fe8ab9e3bf3355e81d0d35417383ad46712531b7490b1f0cb20ffcc2f4812822794c15fb9ddf6ce72e75551b37c5e7c691fab4f38ef81f679dd7763e9776ae3c34267d702d683a8c76c486f3a87c131f7d7758747b18993a0b410a23e493a7920a25c61b252f5451158db15262ee087345ece6b86379eafac554606d70d366cf89a83d3e6f10f344e5c4cec5f8ff9f97e24f71e12189e8eed608ed5b091523944c53e7fcb107b18698e3a25dfa718998b4cbaa34a6bae8b20c9246f0f8abcef524bc55de67ce26cd27367a30faac5133d644d99d171d9ab5d1ef1a745aa004fc162a474f88f478e290c1b9d6ac73665b3fcd8d2a848600bb52ac31f27e16e82523e54e21c035b324e54e69ec140c11b6b7262c8c21345b5bf50a0eb8618e2ee1e0e59a073a0d2704a87a1495bfb1362199c143991d023a811e1f53d92ced3113e9ed5418f7c9e0877de181f41063592862e1dfccfe9c86ee488500aecd82d39510533553b7a5acc728c4c8b3a05bc38f18d13fc3944ed1b36bb8fb50997d2db472079d1572a01372dabc3e7fa6cdffcc7b51dbc07848c3bab03d1f20017e8cb04ba8f3a45e222e49cf5f908976acc38907ab32291e8e323d7b203640573cef3ea39d15a88ca628a33ee118060e9a3127c60dad0973e0f54dbe78df18a3725606c79455ac0cf0dce7981a36b0af49f436f328b38c104d414ca3f552d1857c832495dff3a2a88bf0151689e74490e902fe6d244787fb42067380a664dd2e5f60ab9682f55c2ca07f3e941f8002936503fafb6596edf44e8c4d1f7cf36d602b301deafbf3143e4f7907c6059430169526585bcc645b5ae1393a533c23f983ebd2f4fd0cd6e5c628792bb19e9497cfa50a7a254abc8d96bf4eeddfe15fbcce8fdc1d1b11614feb7217f67c70be04b955a877810affe6210324eeafa22d36768afa78bfaa39b7934860ca90c715c80cce8f9aa68955ed12c85f195b1636a8f40d2859c8d139e484d05f9e022f30395231c2e22e4b75ca3eed6c1efd36d89b5d1f7d0a6d8cf983080f787a5518722f7a2e45813918e8cde98771ff5b36be5ac2eef84709dd06a5251483fc874c39880b2b265e5def4ee807edf0945e61a173a62784c0b7ae903c69889ad6720026a45fd602936b1097300d72400ad38b7139842b8c9abcca37a13edabb4d77a655e9ee3755ef01ceba604e47f86085be8a45616804eebf205011d88d66bc7dd7eb40ad8ab174ed0e7355f10d94e9a52181f1723d4f49d8eb1b2ba771c00f44edb6967677dcda5ca931936d0b89cc7f5d50dcd21f8c2b657e717365b9c148fc27175c8b80200be301d8be9c1c32877feb686943f10c88b9becbd3ac4169fe3a7990081db091a54998e0225bca136889112b3f04bb404a969efc15207effd9354e9892eadbfe7b336c1d2cb8212513a081dccab63e36cc7417986a23d4fc1752f341f8ec0f28148f63c0efb631fcffdad70ab8520ff560a175b5e624643e0a036b65ba13f2712ad91c1ea19e50f7f8ac709a7e2a2450cb25fcd3017583d298f8d1bd5e1131b62d2fd77e1bdb8abe9e7a5176d1ff1781c4832b8717af68ccba9daa45ed375001be315a5c5b6dddc2e5801df32d2886cf783f350ee9b1cc3cfde6a9c87465620634b7079f05ec1db3940b517c52c137c4093a66211b2882a30471df3b850086d11a4e81c7de13b238c6fb3c10c1f512ec0adc910fb4a045ec03ef52eeb30434caaf8f8b7550e878b06b9e7c644351fefa0510c8bca037484f46e9700d191fb46664421968ea6387fec8d2df4963252a33f50ce03231ce363ca8cbe049d4da470a624ca8e5ddb89a0df380030ceffd7151ccf0a13b9ad3bfcc1d449ea284ddde6702765a777f216205586012167a25c8e3ee2a99eb739dd91928f16f7dcf33666b52fc7304cc4002c5370e681bdd39dddab4b6d91b38504b356560153ddead041c97f329a7e01274b923741060b2d0755ceb254290ca573a77015dc676df4bd621c579ea067e6da1b9847e5d82d82b2058527b04efec6fbe453c8eddc181baf89909b9889f8c10612c16230d54f2fd3b241fd57fbdc73cbe7aee65c8dd7bdffaab40baf61f9546b45e68b7b9e6e73e86757be7b44d66739612005f9693057601e47d0777f64615e89a5e978eec63109b570147dfe63b924ddcee8bc1495cbb32ad871e7033f1fd7b43b01b7bf355b21f694383fd7a9829567a4c750aba432b3b6858b34b270d4ef0ee69e05dce6f878682451152ea37b421213e48819a7d4cfd819fc20b83b3ffdd43685be2b4aa299f1a0006272db3ff0527fe541654548dbe9e3f747f4e232afa1ee2c20a40bf8e6430e1a05ebb0c2537a759dd4b1215306366cecd41ba3d02e75ede43a06eae3cbb3d47a69a302711d82f3233e39092478bbe1735ecae096bf48bb788034a3b8e11bb088a047b4c9d63b1fe269f0d9ec3a24798c226fe2f1632749e8f17996c071408ec6900daa637f0ab47c675ad948d36c2056480457f3475be16d1404c0dbae72131d85491c71b10e52a6acb75d1b4209ac3ce38e66bb6336976b6b2f22cba9c6b0676917c211b50c7d81591b336ab5d356e4266f0881c57246b6085969da2caf1105ebe9d8ef596ea1825604ba7919ccc531b218311d8687773bd4dd2762989fee75e9e59c841e72ac44aedf68e94dc83f6349fee643bbaaf62f031d094c436879a050e53f2e5452147df6ce55858a9318e1c69d882fa69a4e106b427155ebff9277849be0d20c7c8267fc28df775c8c3841313bd933b6978eaffac3cdc18553304202394841e049874f995c25e076a78f8e1236e6037e2a44e2232d71125cdf699247544a1287b5d5b8ea74108442e333c791396d869a2c02fa35e299ea4120dc3b5576615579cbb9a7dfd08722d920d56668bdf8b526aab869c3b5db39f61565326e66b74dc843702e5c4b59d4b95e28214531832c544fe9e53401242093c778dd69c8eb6ddaa6bf8eeff118d79aafdaedf918b25ca47e497cf24e783d34a78aaee9900b8dafb15f7505bbfccc14545fdf650dae30e61c9e4bf004a72149da456e94aeeb9a62f670b1ac59ee74a9931450f67cc8ee60f9fcdb2f04d4c3af72d32ee5003ddf0512f56d19e2df05353a091e537ef6cda2ed1ee9bbb32165024a353fca5f4ad5cc4418ee4a492fa406f1e9f91f15a67ca2e67b6aa32a016527b10c1e44c09952a3bb17d66e57da1b76b9162a831a28e9d6538e970455458920ab7f91416ba65707652801d60507fcb7741812e3cc39282528c1f5904282a2d1ba36c314348d1e1b94a09e22939b83a6926d75a2eed4ac0f9f9426d0bdda08dfe0efe7f139af128454d1e4d2d176fba8d01f128d70e2f16badb146d0f1a1102586b872283c3c2ce4e91d7b6e2f74e53a93e93bb1f70e0ac3d37a734875a9760541a5521c760a2b34378e38499fc03ceb3ce338f2d0aa096688be372ab5a93bb8167a845504fd8174d5204c92c49bd2f272f7c7ca8edd3df669003b046b62826390a35c45e610a345fad8e2f3d60b346f198cd8acec9852f3c9818a40f06bb88cc8ca4b7705fc4615221ec3d4afa9d465ae2b8923f92e3f776e884dd87c47985ea46126b150af8d3c8991be144f6397572f783b0aa54bf36e91781b798a8d16cea359074efd579dfe2b42d4c757bc7e6d8ecaa86c78a10c6883598a068f1f641f2e1a29fefca7ed303ecc826cec58e4aed985b504598703cd93f2ab122545ecf28abc0a71247266229e2a50b075f55c7be4ff8c399af9c5d813eeb91aad945cb654ed8c22b64ee989761115718b8eae749d699c5d90bf81aee2161b276ff2b0c122dcafb85673c69db486f45f22f9f97d7e187e905490824256769cf0adc52cd29e8eb5c66f1d8c065e5b3618995c82e8f80c2c674a7f8062960b3c9ccdc4103932a83eab8996d324100e843921c3e8f8d987c19453215e190eb41cc2df043c039134f6d72460d2ea42fac9f868ddf5013439cb50114529f3558ecc26194e9a8a48bff0c71cfaac961d6afa2cd320bfe9b293d49e1dfc4ce162cdf201f508478f3fbb9bae5729e197b9838a321a62960f0322cef4b3926fdd8871af72090ac957939946b9034241953f29f1e8d2a20dc85be6d91900e56a97feaf05a65c1bcb666411e235981dc22470ed40799b0dedebc64b61bf48ed6c3442b891c62c553962f4dd7cd0f584bf9adead76586fbd559d0b2f27d841f4cbfaf9961cda98ad4b7a812b4eb5cc7595f7f603e42ad9f794f88a1bf7e59d502314e56c716bba9e73b058e9c0a2dc8d14026a1b2e75abbc36f0196f05d2d676701b0205f60f462f6c42b88ee10215be46a3f507170c95cb1c41b940d47771b8c2c8909a1c6e53fb16b1328fa57cd56a6d44674cea40a81c36e62eace9371f3a7121bb257617be78ac31a428396d18390777517ad29007980717e07a4deb5369358470423dab9693aef8ac4449b137bfcb7519808ffba4aef8f865930e5588e4f58ee98f75207653ac5b89467d7a639ac5db4576c4edc561e39be0fca3ab0117b217b549a9c3aec4b04766c0fb92a85441c44a87c89d1229f849557fbca2b02de09bf1b2abdea01ef97c4cbd0d5a90acbaba9a16dd805aa063ffc35032ced76e2cd7ee595c6b8d2edecfdde31e543992e71febf89d862016c065a6e54c106e4164153d71efd8d95d055e04d318b2cf9bdb63706acaaba69ef76bd0014ae8c3cab8ebf8ede4d081e697f3444bff2c8a6ac7490b0ca51f526306714a99f97e169f8099f3141158fba49f5fca84138f62894a9336267611b93d3347f710438e9f5f78e4c638535caec48b661650ea4c2d0ef42a91f1dfc97ed022111da83c00a11c57225c63e7d75e801d9c9d0f4b423709f95c7551a224c58ebbc8f50150c2d9025044a43d55dca895340d5d3b3fa72fc67662ba39f4f4d4d3d6603b3e4a1161afe9bbdec04f2f9a1e15f28187fc0bb2070827818826b6999afd32a60ce87db7ed93d48f05b54710d93103ca78287237bc6e7e429ad05ca907ab123e9e6e4421f64c8bbbbbf45e2041375a3ae5751f79fd97dd09a9ce550c4d94ddacff3734e5df1b57e25e6234a7f51ee79bc91ba2704fd5d6b42e3e887f7905512def0c54d4af76c7e26dc2f71480020ebaa32e53da0eaabaca6a6313e7863a01a898a54272a9020eb7480249ca41605079e343313743cfe1b1120a52c429a0d7e86a564c1eefa405f19071d6af7501454eb04d1d512f4562fef1cba3b8a8370432ed8b8b481167c54ea79cb18e9ac74d17e01102406f5ab3f067f974466f08c8e87ec13b38dec4d08b42fffa8f2ec415b550291482688ff5541bc826747b523b4e4608b4b7c252132b55b7b9d0e1ae1a2f0cd3ee1fcd80853a55e9e51e62b0f13c4e8a9f68790ff2c03801d4e89fe642d3da1187be6776c334682f80b12b06f4443ea6194159dd2765af6530991aa020a3fadcceceed88472ee5ae92d8b25e874d2ffcfc503e533a7da68e47d33ef9532fe96227a53d2bacd87abc18cb66eeaf8fd78cdcc41a0cd3f2f550227ebe535c897f5350a2d236eea3a4fc9c27bf6136e14d752dbca2d4bf8b092b5b4bc67d99d0dd780fa03eb6da0a9b58d2715289afb2751aafc158ed9671dc690aa29940bc3693bb658f62d01eb9d5c8013f0f7c6836953720226e9372e9decb7aeea50b00010976c8da8517fc2055be588fd5785d824fa6f1fa80858fef2d5364379c1d879a35498c16fa578601adc119cfcc1fd44b599311afbda8e2cd8a3a7e3edd2a7e2ba4dd68804d76eb01f9cfa3459b342fea13781418cf054a2bd4f29c19cb346ffb954869a937c1def3e6d1204332e0d4db20ea78cb20097596225a6962856e95c8baeb5a3318d52ba9db578c401368982310e7c91136696c1f2436a4f78f4e276a191d97b8ee55613f9ed77251556320acb574c3aa99e59afae1d0a7a1987cab99aad594643c4e615ba2254f5df19c02a5fc38965b2cbacbb65fc98c11e4daf1620a0ea4a3e4fba65b076696797922248f1594ed93334b230747a2fe7764b5d181a80b88e9e0d9e945ca09a8f30d58eb858dbae3432c102e94c07c2f50dc8b1ac24566178c86b1b03fb680f6d2f7b9b7ead39ddc878825ec2066847aac7cdff133e9dbf089c3ab1fd4d2c431e0c236d7a821c1b849428ed85d6322f4a87ac84a809c784727c2c9c187c5f63182cebf2e3de5c7c58a58a0e68d85328620676ec6fa3b8a31a485a2f0c3604fe7b8efb3220918a79c8cb22bb810e8a37273183e6a8ed498b4b77c1bc93bd3700e153faa643e5675283438e74e503ea1d741ceec9c675c6302557c00d1155a404343b97a8d3016b403baf0fc631165b0f9d6e174a8d8a71ea93bd9b6bf6150ef27e0601ee982d3f791fb50d55c30119b993411f3b3ee1fd1958a2fb37392acae9b67b6f1695094301a4ce9181e8ee3398151a6f660b3413a768c06c154feb4186e95e9a466d5a094e8119c996b8e80da0de8c316f3e98d0493ac2de9f7777726963543ed5db54d8b8150b878b7058de301a90d5c6c8e645a5b58225b554e46d1baa129d07fb802ac19222c35277da335e8c448eb12d76f631e69b794d1354d93863adfbdd7a302e1fa01915bc6b846a852d37d35af8905cf8abd3f7e3a6d86ef1820b23f0133447a65e9e3be4dd7d383432b9def92f88631bfaf51d6fd93e7cd65bcce059c9f48af7f4549935969f245fb9f130067cfdec326c5352e9319163f969f885b7cb55440410bab4f88ba709e89ab90b7cea044c763c6a5c69f360195af3db66320fcc72600183b674ed7198741027f7308a0accb6e436e7cab6a9a7a5d7525d9de6e848656543ee9fdb91d49432ec3d384bfc272af44805e76072ac44299e46a1352dab237b734d60bb96943984506f57d7140e05e4ffa95c7724d8b9699583f90b5bb626986de95b3e45047b069bde8a54c8a7e5f4422be915f1725083a678c0bba33906629c7d59850d24af8e37229a43c00a0e5f9ec7500681d31adaa010ccbc0035c9b7dee730fe1147d058bf97bcec87c36e5fef1dd7bc9be9400eb9361aefbd61187519a94bdf2496331052da4d0c875381ebafb3473b67b59e498c5d4d0ebca840790a6ab5623c8b8fc8594f92b12caff215826e8e1c61cdb2322bf938683aa9059cb372bc22007652a1e99e8b81b8bf1d6ed21de04a37dbdcde5d047e280736447a1f31223a3cfc480d6432ddcfe4e055e447ee7222351cec5d637a37cbcdd56444d5b8db09399294f8fab9c03fd06301fd3ef7c60210e1fdb868ddb688db429ef80b3e62c63006f246e9a70adfe90c845cbdae7e907c11ac7ed4b150cc19eb07b3909c932e5ef74f64fab23eed4e93f9f52171c7291ca58b364d0139082f737cfd5ed2220e6a58b5e11157b4c0fd26c0665500524f002bfb5512d86f78ae2f4c491d80986d4ff17758ea54cb7b7de5d24865feda0338454190d8c9bda6ffacbffc0a6de39128dec20d302c30841e65f485447343649927fb9bdb68c0c12e276abe7004792db90e6d1071a630eec32ea9f5e14c39fa3f1bdbc740cf061927134ce84ed20565f4c51fcb194e204e3a3d02d0e5da6f8b0600b7f8361a3e50f197c54cd90aae892f1acd44daf3cca17c8337bf2d4f686799c7e6ea79eb6d1b6f0d59b74a01deaa2c7a76895d270344ec63129a8e44ca759cf5f2d0ffe74496b6853dd3bdb2aa61aa2343f531c3221c6d3828117a364c87c62593a9a13675889f40c161043ac277b0aae65b1e60c37b8bcde4d8e5042cabc183fce67f0721294d357ceae85dec4b63765746e5de685c4de3641a3973378b16518041be0a57337f8f524fe2001b16c242d5c0a4e147ef14af203514aa641067667acff2f49e5d3d2c47ee03ac0a6d569f3f73964f1ab10658661b7eb6fad1c2a98fbc4dfe958600201e3be01273af856fbcfdf0d30374b97da4d361074a11ee9b3c3a10679b28d530cffa2682c732e76baed3105d861193ae84e84df1f4430acbb38362ed8ce61258c88a03728759bc9ffb0edca086b618eed5d1162dc86e2d5f0c0ffa5893432d4500dea2fd1ef369e9a3f5899360fba53847a0545523d31b91c0454af0fed7d1dcd819d46157879c7da72b94f10b3a72f7f175b36bf1b085307c5e2b01349500b1d1fc6980576f93633dcb9cb950d4878cb495b552311fe11b3b32fc5ea7ea0a910a79de6034dee8f759dd81ae0b5c55cb5c13d5540e3d36cab72645dbeadd6ef9952cbdab113b0f6985ecabd782cfcc7ea26eea9a883dfac52988cbd2c967539c9ba18430d26ea63b02ace200ca5b99e1f1a25cb7ad0f642a54ecd59f75a803e04a8001605807bec643ed97383757d0240676e61e61a57b2be4781b24c2c426619d1ca2d1565213b5b45da651e765a6ed530faca7b41a838e1f277946727e9a6de909e4bfb0d15c87cdc6f66452fa1dd34322f30ed13f3f2be35e116e94d9c687449ec8866aabbe117bca5b4af0a358e43c350f746e471372f182eb08ef60f9e5b58f30292af795fad4553923eddd301f7fcffadcdc9bd9e2904882077ddbd6f238151ace470be33e24443ad3ed4f06fbcd97a5af9c1ce2937af4f05ffa843acc8f3500406e2bbdac39ec6b80e4b232dfeaad3639874684ad18d7988a801beac7e52d056ccb3a646814929416f5cb71a7dfc86129870842ef14a2290433545ee8411a25e4d053b769d8f67ec3692fa32619e47415e1468e293771f0cffb887f6ac949695f846711fc1481427bb7bdb09e5185f3f40b4fe8c293a032f057b19e3dd2e8f7376046cf3f5bb74475920c11494be56ba219be23320aca22f033f82282c5eae6e9c3b40ca72407ceac122e51d45a85b15faf9b62891384af5bd8805c10d25dec99f46c787a0912915fe8874ddfc1fbcf508eab7de7e0bb0444e62cadf26e92618bad30795647fb7091a0ac457ed900d8fdab375502cac419c06375c0e7adc091ab404c40a8976f2889f968c3bb0c0720d1dad4caa30b770f66d8a8dbe15d15f806472dd0365661305347776111a3e7b09e02e4f228d6f772d679206886fd50e44fe1ee6f96f7b26a3ee88adaf25a6e5e16e9d16204ac04b32f683eb9b27d62f85b88fd7eb4cf49a25dc9a56d43d4cd7a538a575f3b10dc3696fa46b8f3d3a714a56d341e502843e706fbe990dbca462b50013d14e7841d8fb61b67900c5dbe93cb6b16ae7ea7452b5555592007218c236d0061edb5d93a2a3bb679ad730f0d2a0f8704b424062947aa2c35b209baadf4efd9069f68ca54cf56315e60d27d7258d6fd93527f166ec02716f07e9424942b7d18ad6955ed9ce33cfdf14389a9c0242ce965239e1a413a33bce35652155732a4a7eeabe6f19627a72fff0d2aa5f0b53f6d22ebe56a007bf9c23c745eb1f846548c30e3182fd32a5330b2cffc063885e07979528c6c8154eabcce93caa84fe1622d1ed2fc81fc2d827568c97a2b6a10051b4b891ed88d8a372fc2a31b212a91042c36a2397c538c7950eaddd1474f5d4959597bfb4f530857a64e83c86ff3b458cce97b88e2f00e01ca6161654cdb286baa2508d646bf5287daf2c2bea46668dded103f377b69bde68ea0784c976e50cbf1db813736012b45e4dc10dfe417b2f793b4bba72cadd87bec867dd1768adef0106d91f924adc1d58074017e94de456ffe7109ce6b3bae15dd9de5f37ba32704083b80cd7d38b0bc663930e4516a29f151f861b6fa06db8c2436f8e39cfb8e56a6f8d1b635cb2b47aa6749cfbf8c278dd06231fcf6a266bd7c9e398a27aeded7ca9e0b8b429f8905321469ee1f024637a271e5523893d80ad242d1008457999e34997e7961683a493bb36066bdc260472de362fdfed90ad88fc65816c7024f64e38cf17e2d7ed7a9ee3ae0502727a5fafe6fee57f21c060ffd6b72cd1447ca09085a0aef85c2b982045434383b69e6ef432bc54e6ffba49ea85a3dddd2bedd0d853b6ff3447ed771794a47a0ff4bacaad04996e3267015f5e5963e2fab42b517ff25af0546ae53db52b0d67b4ef46b14ba918ead0c58908a59bc9d635e92d1727dc82376bc7a3e6673e45bb24f3c92f97df4ab549578c85595cda44b6655408b9ad029e8d9d2837e7feff9a2c30be689bb159e16459fa353c5aa4a6e689d357444543ed4c466ead1d168df9c1d7f50d77bc08e458eb4c9066c0130867854e7640d83ca05df7952d37f55ef646ee7475d0a8dcbc6ffdb76f32ec7de022b416e350c01a91b5c47193b6e3330b31eedf1765d397093c3fbc623b3914c85778a7be54c8dba0a28f3a28475a274b5795f90e30e76df3a6d4562a2895d98656be5e415d1d531981d41926d965ded8c077bf347e4290e5d189a0b042c96bc2dacd6648d209b98e28c8c22793e76d8cbb7b07babe76d6330d7e312db0871761bcab16fb66985f2d711ba6d709c034efbdb37acfa0fc435fe70be1642a0edf3a36240a91e324dccbbba1d25dc04b83e19f2f46e86794abca57009d255b00cf2ea123b5af8961978a67e8f698d660e15163f7ff1812eeda015833debb67110d54b3afe3f991d3632030428fc16cf010e56394f72857fd83ed4259fdc0ae4d7ecd68c2434fc13826b11b75ff64dff804a32c35f6c0decadfe7da5a8157a2aa787cc75f1743f813b0165308a95cf0f10cf7635cc4ef99a3d303c6c456733f8eead244ab5ca24a607bc66ebe76a79254a23e33935550e63c63754a0b8a5e60ce5610a4b9561910d5b0ad04cc86ef0353507ae75f6247463fee29912492c48c1ff055a7861f2102da38c32f9750a1adc99c0ff03778a362c6640f67c0c9893a3a61d293362c05095d3b3707be729a33cd1d673c01e11c676a861e9106ca00424aaf33db04705a2b23865413b6ecb7afabaaf9b61ba37766e080802563c6d5b5154d2903aa59f7d1db250b1c3dd50513bcd647b6358b2ff8820586858822b5ea52461d145a23bfa7ab668a159e50a20bc70530cc2e881feee7358532740eb56e1886ce250cbb858edc2b542e29514a2f51a60ba8329a97274942c93a3e2018ffa24d45b982c43791418919e194e9ad650d89fe613aaed4021dfb4079e2a46dd167c9c06992071649ee62c0f1987147f6b3820656def62af4911792746ab7b12990c2961627cbac1674d32c62d3bd4a56cced1ff140375ec36388a7ce8372d893d31741fb903c9602ccb2745ffe07b1269b72df1e3f6c6875fbd5622fdda069e4659b2a7094f75b2c70545e50535a5d6d7d80a6ba22781f52f91414c86307934b9692e932a8dbe4e05095351514b01c416c93b1372489a0c63b62bc0be3d112da9595533efcaae6e015f3a099fa3b5f2fe93fa8108813d89eaaf0557182079f5955bd7088aaae86f19a4eae1c039963d89cddbc1b928af8d6793ec7f8a2d87f8d6257d49bfb48e556c78597f53cc21c382a4fed452e8f81c33c24cb930601a28182cd061e7ad4f44ebf070e0c4d3d26751e7732ec44c359df68a021b3927c478e6dbfc6ec198a608fae9e7d3aae36c50c82c62399c148a5dd43577ce22f768634c2e66c41551c88e9fdd287c69706b6a0c9341d151c2bf0c203d47914c3312657e203a6828e84b69461f9ed329339ce388c6a5c75e3dec3ea7b41434d34a258197e5f696fb2c564e0d9ff42320a63caff342715f2a8cd5eb8921c71ec02215ba65e3383c620af498a796288b2253a7056200f9d1c3f088efa244a0c887276c9946cc597ce2a45ac4cdc179808feebae16cfe93cb6cf2218b80993b40d89fbead1368612e6862f7f63d04b7498478aa80655f4eedf31de4e2ec8bc4a7b71aae071fde885bc74595734c9fab8263ded2e69905fc4cdf86997d06a66db3f212a8ba3f9219c30e4860250459da9aabd6875ba9e4b2ef66539fc7e838792edb217361eee6c058aa993a1ad7361d541df186e9e878c9553fdd2892e441f275565bef9971768ac155f5aa05dfad324a3215e5e0d716d2ee33310991488e8af93bada7821a9592d8de6ea6bcad6abb65e3e8d753a404b09d91f7c1dcb95ff1c1035cf342912e2b2cb8679bb8ad12ae66bfd9b199bf8fcf3d610fae6f1c135ce7c4f66f8231509d805b936e9812a1d06151c2c4cc8290be187266ddaa0b8ce816845c8def76f2e94a7c358e9e5941593dd765a44de30ddf7134d89ed2e266f713d2acbeba79d3ec350a04db305ac97235483c6f3e37f3b1fc631dc48f55339a31ba3da4197b740c9f0357e958e6340359ab4859060a0a44618216636c9839b8db00f12bc4818c321c18900845fbc4bc0ebef3b1d100aa9bbc21fb365b198c9a0eb2823678068220f11ae491c881a4348aea24030ecfd0a120ff37cb2ec86726dcffaac977b0bd539a45f03ad89e3474598e13b5ef3a94e50fed05491fef242bea702d380622e895444ac58b0e94623cdb908ad7ec3120c5048e0318499b3acff25e2ff2eed46405533652232a30a9fcc349984e40edb2fbe9de5b812c95165642c56fe1fd069cdade2459288ccf9f5199b7e4a56b264950a191432c2de9cb2e3c5f3a4e82b6e56e2ec15720bbec38b93906283bae5bec247f37d7991300da79d416304bd3ae271243990d69e240120d53657b459224d5f2cd3ba4bd8b478efa15c33e8fcaa4780c4f408abc4e74783de92e7e7c6196d35347ae666fa66ac2b69c36627644635ac13841ed7af15e00bad35d89c7abb8af159dddc5b9561af95d1b9e089ba1075d4a1ae141f0e2d8d7ee3e546b6ac30974f8f4c119f9f4651befd82e341b474e3d07c3b12070db1a304029f2659a4013d42466378354bda358fa2b25b4251fefcf9136f400ecbaae63f7e6fb68437c0e211c7d08e15a50e9ea4a03130fdd160a4c3c29cb639c00f0b9a79fd0c20a18997edfbcc86b743214c2ba627ef8f69ea3c6c0863d4370551ceaaacf70c14976794950167f02694a625770dccf6bc02a31f110ed086f3902f4bf77c5d4fdcf3e73e25775207272659a2bcd1c91c657b5356d453144c1f775ea83bf25934bbada2b4f3934da4131b3c4b1e160fb7bff3cee4684d7d15438c1e6087166d78690f91e638ba62cce614873a851f8d53b653f0e69569aa227391b9481fa54a8ba2910a362be8dd5bc5e9c9a2332caa4a69a448c1b4ce9c7d2645e427f658f7e598bd4fd51bca38a13550bfac890d126e945547edf00675642c58856bc320b2b8215c188622714847b8d561401029a24d9417d063a876b18973f3b4b942d8f0d8bd396e52ff602d77b5521b81ecac58e6d7e4e4f40057e109cc63d414bc9369b4db5d742bfb100a0b4e27318bff38aecab735d98f6d2dfcb7934f81f87fcf034ecca4ab0726598fd64013875e4eb311a65e2c552d4bb02859127fa459cf9024e0c5a7a836265c5dac57ec2538d0a99575befa04dff1c0c16aa1176c98f9482e2bec837ccce6b798903074447cac4b2a6abde92f20f7a59bbd8a3c248f1a1dc3d91e714d99a32192de09d56499423037a94d63e9270caf7a5cfe4d74aee1b50118019e8cb049afcf448f074a2e58024b3f319cec0a931285e7c8ad42f416571dac6354e3e0ba2ef26687c7f62d77a45f1151c6fc22d0de4f51fa37cb1dd93a799c5c425f632f8b3f9fdf43a69c582a62fb1a8d2077542550b4b35b3f37c47aed8766610aa9e9f7e3f0a54cefeba6c1db4241cadba7d80589b674104256b32150130c6f6d5aa4576e010c846fca9e9934d5851385a00b803a4ba7a1eaf3825c002a67c839f7f293925830614842ee587e2d47bb167eb651997072152f73020674a70a6a6ca4a1958d117958a4ae155894ff09dd822f86b989c3f9f17afb8a7140884c211be13ecd70d1c4b626e7a2dc09526ac73566a1b2ca5e6f687b4f55aa9c561a1bc975b0baf2307a337e146bc6559fab7cb344dc6b94c188bdf826296584defca6351fb134d06db730a60b20946b0f6ad2e44bfa3d58c569103332fbe29e037c603f5f15110ae1b51bdfbe7594440ac4b6b5b1f144a3f8c49f70b92a677bc8c1b61e114dfce42671f9b31cb56d5adc3f359ae2c5a9970ed2167a3697273f4bfbe831ed4785016f962ce93cb18825a7fef50ea6bae72829f16208ed22aa09473cc34c284b5d16ea16ce801559a6a73467ead62632a3e1e53f094a09244551ab72a8464e31d8321fcd05b71def6be4f13cd534d806995f739805bbf7b423e3a6daa46a4c0d30afdef840146d934431fdf90250fb7e5c85e029bad3f28a6dcf633bc91bb09dbfe03c191943a0340d3e864888d98300658a3c25fe8d7b26a0007c9a840249fce6e773f9237d9dc27dcd8d59af8d1968e7c46fe4a10204c57548c2482724cabc658b1dd11eb8346acdfdf58a25818d29da0fd2eb14aa2cf91d219c4af0a5b6b7a32cfb6751b0d86a0f2a06f32a1c949b3e1847b7f607bbafb4a0ae00d30049c33642cd04e11e036b33fb09ad34ce1f9419141868c408b250ee1268514ce6dfcfdcbac1fd47eb9e6b78321d73ed6a4ef30b9b47d567d35a51b8d9dcc601324999938d720caf6ef60a59066cf1c4260ff4892bdd8c6ccb130199a47ac47f41dd11aefbf024d714cea19cb7dd9ad9332c5f70bac687789ec7251284019cfa6d146cf29c3b8c9cc72ef5cae701c8953ea42004723abff30f726b0aaca768725592ff3920f78f146dae7834722775210f2aca91ccc8504e30862a568898b44b1a6b1fecb006c9a03566f96b76d02e0d94c38bd94d7d51feb0eedfafe0377a24cc947d7e949ab4f8d2dd84bb59448c9c5a2fb2b6f517d382fcd0f58db7d61983d4154fdf4efb7c94dc5b74209fe8bc36c5d3586a042d88ebe8e6f9ac31ce0178f645de2691414a816d72f611144b77f6cd48885cca3c3944c66e39c4e5f47dd93d3a0f0b98fb58b9b3af6623f93d0f7e58904ccce1fa0a3acfe5f7f66e3652259fdf070c85b6221f43d5d7898580a3bfd7603a236a6fac55bfb5c05cd7c61da3bb40e686ece3fc21e8ede3f3640cbd05e683bedc5cdc415f5e8552a959bb07a53d2f0b8f9a31e1077afea51c88347bcb36a8ad3f20647a3bdbd489ea5026a1a00eeab55aad94d5a4606ff0c0d8985032e47557c689e36741aea53763bce64de651eec0c3358fea6316f444e5dd0e75d0510892b868f99ed14f9d5ff65cae23deacf328e3f6e81f4c467384920339657f0bc9181dd2662e6dbc0b04e8619bfa8b9a84a93ea1aeca4f1c622bf547260c8add7d703b674ecb3412392d1d82a5b7d427da3b94528e90d8b055a6d0b15f4f57d3897318a8fc7bc3b5e64f1e400c409b754887ad415c4d5c91698a7ea967f1aa4d2205187a6748163ec47830bc8c988917d61dd0fd3f61dcad0e1b9ace61c52ec90d9d112e4059679f5b89e01bd03a909b274f3e449c47a0be5c2db9d349d22234288973f20c7badd4ec23b4c63fa30a04cd5e833b9a95ef268e034201190ea9895c4c35b635a1d170c60d7c90bf2c0978c767a51dfb0ef47ee23b12adb9f85a227999a6338f2ce509f733e28ab0f8878ea91ed86f4ec33b52b0129c47e9d5d199eaaef8acf93a7d689eb896b00d2ff8b7cd3388f75ac214e3598b08c83d5f0dbd47521b7f08a219c9c00496b0b1966626bfeeb1cbe29670445b6b6346b888f491a770804be7933b7c8aad053d335bb4e76fb865373c4ac3113f7fa0260a0b3c77b4c684046aed2e27fafa9237dbc7ee787835d4bbe0dac10bf88484f33fede3f2225616ee7a792fba4eb202eb21243875ad8d8925631b54147545108acf75d9ebe0f184481f6c9840cbb0f9c56f474cf2c3de5a85c852349928cfb3025e1a45606fa4cad77ddbf238d9f0ce98f68612286219d82044bd11007e6f15c7cbc40db3f73dd488564a922a08956641be199250b30d00810ead161b5f84c3550677c26df8343e65dc0a7ef9164f7b1f47d534662b3d8961931996d0e8fbafa8ee2ec35322cf80f213d8d9d42c8cc484caf433128034e7fae643d482de39dfb548d10891d5c417e9c14e96276f25ff3cdafc95cf4ea86853927b16baf0c421f1b6d0ea6e8755ba15edabd17b669b8543791e97c6d3d8b63bd83a9301d215e5f35dd6b961aae1e1a996751bbbfeb9e5c3ed5ba0e6e0508445fa56359e2c8d849a13f406766da3840d370b8f0868918d5c7cc71d038071648c105a58ad64aef7e248907a8a781d49f6c2f1b7cc5123cda6d371844bd8326062d1c20ca5550a234bbc2b5f0ee3e0e73efc833ca1b33e65a56dd5e34ec6f36058ed7de06e9e390b456da16579ec9e931335e3267c1e23a98591daf281c927c413c6d738e7e6c38ddeb5cd7fb953490ff4eec0585c50c55ad78a9c8f27d3723ad9646779632a7e69fd1f3cb7d703681375c69047bdf8cff05a4ebc423a17342ab6d174fb7a7e188816fbf2e81cf8c09e808c70f42cfe6d4ded1cc4e93081c3c953d67426beff6e91c3ce99232b8ccc055fac4a0fa4d59adcc64d83addcef1ce383917b49402d1de9e0b0740bbff310974ffbc93638e2ed538e2f570e05aabb25424afb0f5c9705748ae2980cfc73f8c6f13d32e644d6c1fdcf832463f24d0521dfe7a16b7b6be48f0deb02c210b44772730339c8872458b48d757fae287984abd7bc352e6eaa5e980813e6fed3910cbacf046fe7c40efdc61d23f0a73ac4398cf52ab0ef2f13bcc7531633d6ee6786ac6030d579315bc3cdc5b306de276951ae6537b2d2f09f7f16120de8e65816eb3706efcb642bc9da9c748a8a0e28e6ecf0455672d55f8341869448aaf9926c156579e1d8ba44b71ce0168619b6973b07623ec1553007a9420c7d652575179e1d25f4c436df697b693bd687e5d349fdfb0b0f974073134e3a64922c963dc90306e1e2f8d8c1c33959903111fd940bd356d0a03a576cb40a28cd0a1352809f5b0cd5f7d9e9f1bfd50a02fc4d45b52ed157f085d05ce17466380d26e3e4aafaa2a4da14032559d4490c77059b1767018157f7026ab6e401f804dd9ff7c0a2750140e9677f762707715774a42ea9f665508b08112cd08d814ef0e02379da9cd49d34aa408dfa50763ceb515014a6e435dcb7c6a03eedb52a535367fc0df5382fe24dc67844542f7045bae7befe95bd90fb147d074d04685ddbbb515c1199e2d24c3062db8a2557beb6d22fe54ee724008d0b04cb67c692f4102849fd48b28351c95977810a996f125a89870cd3e2453419d2cebf5e3da70437c1b895c2a6d0860000d80cbfe335bc5d22278ff74cd31ca86c0ac1aea5a4b4f1191551e311898fd290caffbfa5cdfec97b414a37b20be8a80ff066fe0f8286086591a321c8bd9233ba9235a102348721d2512d897b41f18b5ec5f1bbb5d552a52dc0041605345bffa192fe9cfcd820e232095ccd6854299cc3e95708bc880d39d80c5137c6f58264e42da66ad4e211e867d96b3190d54bb7b9a790209d33f3b74f73b713d3613d6722a5e8fef3cdbf14cb0610b3ae848b64cbf5092334388d36124c74c53957d3e03d7aa2f76a509af9b40143400bf5761d44d51c957063909072ce44487251d9f182be10740052bc0348e2356e62e7a57c5c229c300f1913c5fde9e71384482f31b75d97ddaf861d5666bd798e10cf22b937e7968a006b549bee81883f1fabbef17079d1f2586f2748a7acaa8ab39c1ae97355799f8da2ca1c6f9063034ffdf0c9ec3ecf51269c91b719a6c8e469a2e41ecf0c9278f1663f0d94733f4d37d87a590d8b7a65377f3ed671881d4b483453a69dc4da2b6462b5ac6b8fd1e7ab64f2264f9a6670e0222f7afd0a1fe1f0e555b7d7a54eef5e8363fb23fd4312da55836a94a94c018182dfb8de0b74fe7507054cf7ac2b2d8109d9688e4bf8ef518c92bf2d80f1e53ae8ed6530f83e0507eee58db5c7092721e5dd6674ed676f99f494065e5d93315ffe26e059e1806d732eac3fe3bab361fbbd56bd598c958aaa287118e8a6a2fd4e46d337bbf5e084c11c3cca39eb22949b949c4b7b1576fdde0b319403c6bba6c8f932d249591284ef29d4b7fe63b81418362e5587cd8afe944023c9c6d3ccdbab660a1c92ec0f7c582de8d120cee58727cac95f37bb40825aa2d618b386f516958013ef6b54ffd682ab13ae3a3e91c761717158dd6eaef5ac5f223403a4a94232249dee29f75ad7a7886471bc83aa4dd6a77651d178a4116d7928d91f8498992e42dd90a7bbfaf452290fc8265681bafbd042348343b6ed83052e2400c9bdccfce48e2c9af8b7507d445090797ebbee7dbdbec2707b6f997dffa7d7cfca0d538bd587aa5558e2048b2e2a83c2faf658a027a5dee8b484920ff59c031307109cb99bfef23114fb9d86ee5d6954b96db04da48935afc54c22c53be09009a58dd74a703ef52c8a9b5f141919337d1f20bf6440b494812cf3859c480c7decb53d903954ea35fde4dcf2bed6d38a4ff0ffc9ec06f7ed986c3ad41487890c8ef95ca9a5fee66e2903e2a4d2bc5dcabd9a79588b386b0fc30cc5d6b8d8251fdba06b6d95fd603c00353cb249528ca54016c41284e43a251fc9e1fd17c008aadd1be9a21581c84c282b85eee10e800aec781518d90feb4ae4f7d122d44569820398e049efd79924b826d4ef8569dccbd9d013c619ff3d0c860c39c59256f16a866776efe40cc6ac2f2c52886f3a268fec777a5c4d1c2ddbf605dba63b83746f2313075098f975a9f8ce956297397b41a32643bd3c8f1ca91634f9b28a71e8e8d71a4d9735c80d136d553230a14ddf8dc84bd5ed8706528b6622c92982a9b05af507253a593d201a6be5310db6b1dbb812c6a9e7e03ed4e64c19e52f9baa7118e7d6c3355e6f91083ef8d7e70efc2a2e0d1c9018401b7184647e47fc1753e11e72718d6289809d832f179a3362e7a6d1de52823bda1d929999c4f8be7b4e219776732e6bd12383406bee2cfffe0b8f3d23c4e1a7162c9c800f1c4e2bd3417c4820a286fca7660e7806a6806e5a0353dea78f3365841e5468a9c3d6fb6eee807bafc9cbf3c697e086ceffee132386ba58fcc438bc79dedf8a5384863a78061290b6fe61b858e3604966c6ccc9c041910f80e5f46d3ef84bf16e586596b9112b536a9904073814c2d6296ce8430cfc22de876dbde714eb21f1a2b2dc3f624938c36f6d8b451962cdd896213f6c3d32dd02f31410dea3564eb4a3787a63c3bfa02ca6c31ed28666324b1727f5dd76ee64e8b168d96151feb7900f171ad7c666609b96dc23d54f1230bb12d29c1250ebcfb9c21b0e6423f79702cccdd83d43e786ba091e59cd223c361b9f27eb65d8d41a7f0e90e7bbf37a71af91050862ebad51b5968a935fda83d21ac53bc78bc1d4d353b55ff1c5593f252aaba64c0e778a5904cdc51ca8d47af5b54ce784f85347c4148e451ec43af39fb82fd473bc70817304844d3e61e72dabc5a0e18458fc1abd3477dc43983144105b1f93b9c20f9dbcca12e5ae38f53a69a195c5a2d631c7f246e598d315355310b821f14133c5ca632bfb8c5b6f6619c95b27eccfba3abde7117c8d455693a1678624fbd985dfbb2c252d62599096e6ac90702516abb4773e3e1cd36c9416a604069ceb37d05143b20df4343762a38638d7a83a2ef04bf2e7f6f4243695fbc632a72be884844ee03ed530017307ecffcc49162ca05f1aec22dc2f301b98419d8a608e761e43bfdce95d51152b1b96251c0e9f4a9c0da1a26cf6382679b2f215939a542791f2884e72e020ad5cf8eb59dac0f2773b489c6155fcc1de3dde1cbe870fff19c7988eb9a60453638ffe220c5942a462e2c9a5f8d29b804d59200e9687076edad42450bb8630da944d339dfcef1938837b045ee540f60b1113c54a62307d20a2009eaaf160ef3507952fd6963cbc1cdbedc45db6edac5e561c573464259deb380584f8a0def262389188ed7506824f84588cb15a8a28660014fa4ca5818a4f8eac74d8afc7bb5f6db48457d098fd935253ac4891142e5de21c8f79bd84b3f7e9058545ec14c740e0951d2932456578708f1e7237bd9fe346759b42e9aa996293824fc724468aa77627b9e3fda8470d570394916ff645bb16a9f4e325a7e754bbb5bf356eda0c5536951202a8b8354f22f9461ed76604cb538b5801f3926ca818695d9d82901afdb54ce48cc37dc6cf3ed2cd44a007ccb67d10e48dd3a150b781c6ce78de847906b8e6174a5054b3a6ca6e060c88160cbaca923d4c4c66f042f916c7bb122751c2e35c189ddbf5e93342994c19a1f842c172ae9af832933907a1155e6764d66ad0e9c8c303c54a9e68e9ada6b09c60ec85f301db03a12eefaf157a310f8e982c871cabe49f2568062b5f0eb70b5a4ce42088b6070bf170917a63673d3e4f578dcd3faf0136e7a4d240f78ebf5f6ad3c9a34802169152f9f408e14ad1ef876094ad6196da5551a6fd190507efe4d18919d724856e80a36e6d3c36a844601f7e409734b10a6ceb6a83b16edb18300dfe7c41a900777656c27d851454612010498810767fd726d6e23f3d49532118543751832f8aa7530f647e194c8776673ae8759a7761f16818afee761e6d1624647ca1939944df98f5ce169e5a844a45f0afe65b0387cbbf4f36f7d264d5dbca13029e11f6e8e33f109ea278dae3d1833b246f5e89f568eaab774b08b90c4f5be0beae0ab0169ef90cfed1dd6396712ba42db148a51212807881134944590750627ef00f8c0cfec1760d66a58b99781c5dc9f9f772a41fb5f3e23a51858a893869629045c8d16257cd6431a8a35262c2ca52f2790b89f632251bf375b896a1e8fb12d6af4422d426d2c7577fac6e8dfdf45390a6fbd0d47d45f248032eb10fc2691d73e382caad251c2c0a4901a8333c06beb4933b1756f82ac3a399d676822c597ff89868aa40442dd9c799bf204b3ad4771f0dd3ede08cc9da42a2b37d75ee3e43e5159125ff17b2438e97ea4d891210e7f1aa3c23ced7d3c5f90496bb3a896e1c35dd86dc2c38424a2735199013c377aeef518d017a89bb7f2a52b67e9f755b93638dab7a74389cd8363cad0aa60a6476b4788aff95bc7e13237ba63f2d97ddaad0cfe00f38f97f467da775a0550a5b72f49c2d9b2f334b08f58afea1a3fec78b2686dcd7d11fbcf93ed51dc76c60c0fd1f4c77e6c49b8bdf5a8c55c193a450a55050197d1f05a7c6f556610187f5f181c0a3fff8a2d09c73ebfac6cf4ab0ecb510ff84ab2983a7a7e7186448327899f0165297360770b3032a546a23916d63a459f2dc8780f232de8a297d9f28e41f592749325af89c47e15828cd9830d019af053bc643d310b5e903f64c5af2a026f0ee5f613fcf4aadcccd65ac25106be738e404c68d2148cb9bf9f9112d04965e7fbcede71b12128968bed4f602c76c80f37a1db8f8e250e4e5c08ae5c4592973959fdf224112c77a0145fd98a1d8358fe5407af8be7bb6211b2731f573ba13dc7d61cf048f75b87108bcf876d76bba39229f03980dc3e2d5acb7a1a47c6e8d0f623e63aab6ce22ba0d244c207e68fc96461f73c873018166659e1ae87e12400af4b95ac4f09c7ad2d80b8fcaddf89e3cdb8170b2c750501d42c866503be0111df2040efec262fc83a5ac7a7865178366eb0abe3f1e48f67283ebae1bd60cd99f7badd406be368326e69a0290dab0c2fa9512093c90db5874b4640764b914e2b6c0211bc6cf3631e119995529233a2e925038c189d4b0a4e09e776a457181bac384d0856c144827d93e7efe9bab029824e6d9be2cb12e5c192636f0a1e4e0738570b0499f170ea700627e921fb5c734ed26e8652a10a5241f9e8f25af2f8884f4b8356c3b6ffc5059f4dedfb212e51722521ff97e6f7e409b866b898b87b2704999eee5a7f791541395685e8cc6b4bf2feec4ca55277ab622af74cf46b5453984ac6bf75ef49048ad1b08d38ef95e186d8ae05ef1288cc3a10d8705a97ea3eba6bcf98ef5c72e6b40b439a47539b91f772eb16e745e69e370df3eaf6dead921a8df73b86510c4a4cacf8a9a112601fb24f1411a7813540fc2af614ae778416a145f1c95c0c9cc6381ee48bc81c94d6a737dce6094efbf6eb52fca1d5aa89aaeaad00d2b700337493efee97a7b5d9c037b49b9289a969e9c7d7c56143551c290de12799db54623442521a9d36f53b0a1296c5539183f9f24feb80021f6a3d0cd783636ad663fd36f89c4f274747f50d8e706b769940d5a27d282213c26e3ef0e138f701a93aac6458c8c07ef9ea85c5e118d2d64d3913ad07094dc857fcbf4dd829ca65af1e4e9942f4d802e1731a94062b1693343ac0589b6fcc45dc4000054083d3bc64032cd8c7e2bcc2714942a8dc86e09cc604bab4a01c9b6e917d7994f4f796f79c15fbe3dc785b75eb28f66d4bce2a1db0cc599d6d3b7c7dfd88d0143d3bb15b1733afe4a5400e33989feecab2e913dcfabaeedc82d623e80d4a135413867444dd6f1d0521e8c992df924a1ffe83c41e396c08f1cd5fef4e074147aac21895aa388f6e3209c3de0c61b885440f8a63ad6370913ef1282e7b81ff47617eb0e5442e8f6167054749c5021dbf0405b31af301cdb9be43eaf9adccaaf5c9f01424544f1d5e2d9c8d921fa4a24fc415f32a6a5f7bb16e90429fc1c37bb3580d8dd56b671c573deed540beed78b057540611c605e6320c9d2f96df3c3f3663a16833ca02227d6aeeffd7cf8aa9b13fbaebb98d6f6fdde826960acd00d9a259387627110af9c040a8126acbf18f00c63f65c717ebeb5fde53bef40f6fbefc94e98c5872b82126012ab9b7fec45bfeb39f7ce41af22273ad8cd4c16ebb77e4148f0447a0014de5e5d1c0e08fbd8fe69da1fa2944ce51a45155698c8cff22a6c6b53b791615e25ed8119ca4d4058445fc6aef92853fa9ef13ffefe4a331f2972f5111b88a0c7bdd0238e13221e2e5be8d642e810efdcd2a66ff3f2aa26fabc894dd6c5b99fb6728e86dc98b70893224bbb6cd512056583fd14d157225f427fef924b5a6fe8629e6eaeab47088193fdbf73ca90131ec5535806a92011f79d303924c5935efe1f7ce6e4aa2e47aa4b2945815dfe10d7cc50d3296ba551cc232497a29497761903caffd10bb3ca76f8852da9e545cf0ef1d6d9c53f4bb788659d0db1c7a3d5a95173a1aafc7c09c9e90be13ce2fc8b680c51589818d7af9f9716ccbe066568ec4daabaabbb6db80aeeed04a3d5a4eb3809a32383c358996c0395083c08a3aebc68b3c25b31adcda589ac6284204dec2866c71d13b80d9713082eccfafe6c8d8016662e843a844e7aedf6786a021319e1481eded94662f3f63b11eca96801346f959e46813bbacd480dc9e0a27e8e0967f10ea8497c3899ebe40af38f0e13bd3f486bc1ff3d1308c6e065e959f548a32d7c75cc507d81a2b493c20338a965faaa8c5ca61af0c73e751a4d6b002a40256c14648a613c8d04058c8cbdfd1b094d9621f578bfdb12b760c52ff82cadc9bf2c7a2e637371c6ebae09a52679444f63fc654642992ac4935257f6af99642796760e3a3ffb83f322afb795a7788ecc05f6c56d20a130ba3d90cee6c2912e1f3080985e8e88603124ffac59cee44457fa7dfcc50636c7f35905139941e29090f8035fbb394031b80c8c8ef448c826f46044090216a0bdef65585496d913acdfcf366270712c39fc54a21fc9ebca79002cf8b31ab1ea4a335491cdf9a40d283b22fb9e41e494d8bead59a0616dda651d366f772db1c736c8996120f3ce6e4488d35627d7ea3bb3f2eb170bb8291285f780af6c959ab5f7b53e3bbc0a203467a06f31d32caba6e00102d64dcc904d5ebcdc296e1896dfd37cf57b82d5afd3d3c780644ba71182876e76d14bfca2e502b5970a0185e6330a8b2aaad667d41bcf5f1c851bc2a508b2c0dc1aece9d7926d7e44d6802fa2021fe5ed00cd05d729ed11c0e07553bff6814933ee806236078ac2d7ab6e42468420aa12fdfa179cfea3e304cd095947a3d008db9c779e0dfaced9e5a2c398c8d64c2017e5065c256a971f61d1d01bd35fc2ae940e5cc30d8d3dd1ee98831d65336c2a6a56c7c33583cdfe0506e138b2c98add8b26abac1b0aa46eb3ed72d84261ec6c2565399b875b6c42cf15ab5afd9b809c8244f8f62e166f585ee0b44a5de09319626f5446449b19f170688ca1084b2a3e721fdad2235833bb1fed5c2587bae8aff5d4859f62b95b423f092a6f3ec27424402d20ac0e2cd6e7c9870a32078b4f99bd9c587b2d517896c9b3a90f7a8b2936fdcaab065d4de6660ba121c04ab78ce779de8d386d3f4690bf8a747e0ad8ea7b8d3013dbab8ee006c82321382d45cfb4efe5c12820a88e48b6a927a30df8c70f4c4f4603eb7c09e80e131a2534b1e955d8286729ac3ff4d7026214f94a37f47d693ec62fbe1f1a69ec93298fe6d4c57c0db05f1c3f9503bc6a4d6dd1bcdeb69d89a829a81be8aff4129c1f737c4c31e659ef2e456e4f508cba887b1455e0775b4ffaff836802097ce45b97716fa056e8b938e72c6f7c355a0845b4dd9f5f6f06bee487bf5cd2cdf0c475102151f62ebd4cc5217a7d6d384109017a491ebc9c1e0fc43bbf92c844db6057536e5d223e1e5d8582205ed8a3d630384a09767ecb5fd385460216bfcd9116fb3e6114a6bbe95bb4c360bb053e941a88c9dccfea5fc6ea3eaa879a26580a00fd5997154e392af8b46cdf8bb763aac12dcebe49dc2aaea18cae51db0f1c5d996d30a69cf459642d824b0684ad4a1fc53144f52cdb23709ecf6fd6538072bf86552a6fd2d82a282374380e7baf2d7fc597482fe9747ffadb1f1e9649246bcd57be811b80065b438a1b231da27f4550a6cadb1d59eadc3ff01db36b7343885d84f84a156a7d270c3766b89b4418adb4a78e8fb20237227f12f5487a17db517ef6df2e14f96029ba75a4ba67feaecedf0227ff6b0112c325939af107ff09d089de5171025daab9fb0391375b04b2d5190c9b82bd858dad09b71461d8042d47017dec1a084c2be0ba127043c4e60fc9282ecef9535132853e2a8a289b8d0cf6c71e103b73cdf1a1073f12b11692d21805bb0e0264f518eeee3d16c477e59489fd8b0feb1fdb8ab052987ec60e02268ae6d698543fc8bba393e6806dd1713a3ace1b3919cecef79e32d9fcb68795ecaf35e63d243a009b8b05d190ae4ba79d1bde454e9ba05aa89c9453522bf45cbe8eacc56d6f334c01934f671e489da6e43ff69dc14034f7bd2790ec7d5ca5957bc71013f4115257ec9880b922a0fc6836724a69dcea0da3138d90f70df46d3b9892db78117dcf66330d58929534b9789f5df06866971992f2f745c4707c22f83bc05ee9a6371c6f823266e3ffc2771c0e86bcf616788fd3ba2f7c1a0e422f3714aa3cfbe0c72906362ed7e85f61f0e40a005c56793ac6cf7c2b48913e61e9e45a5f7a323956e4c936f7c943ae5c79e73156debdd9cd7825aa922c968dec0f4890eb3d5e5666ad75f548d71dd5f43f8c2796a51e915b824eb095a42d355ea941456cf06b8825faeed17bb2b3229cd12c3d543edda6a326291d59cd412a9cedc2ea90635d1d7b978deca07245c3d32d8877546898bdedb283649c87c65d27421a61d58eeca48d96218a3538f0e2f5542f10644c098d87ec695cee6e5679237353c118795616594482ef6ea8e158aabc90d50130a95da9a3bf0129e894791483e324b9ab6482db3afb2d7f6d8b9b7be23ec4c0bd87c41e05d81e3c060e8fa2adde70a5c6b85c47192ffbf09f1c02fab5176558ac10291b8caeab2be23e00a34a3c8d3ddcf00de00fa3ce795f00b97a3fe2fa570c08466bda9f9252d1b69f712d901d4f08644c50469b0ef74df16cb6d2ef23fa37dbc593c3f72a94880aeac4595ba38ac4e935c16169f11609cdabfe23bfeb78cad7c01ba4ecbdd6d6d14d49e22c89feca4cf1842f64c3a7c55d383702702cb997c0d0b6b4d03999d5e5db5c7b6181ccf7be2008997e276c9902db49835e824b90bfad2bb64f9590042cf99ba784eef38fe93fa60cb4393b2c45b2971dd8bd8e3fc6036c944f44b5f18ba7b47d744304e64083b2f21210c818096aa2207ae478f0f1a64e7c64064a92502f7932f7a13fdd75285d430ce52e72409056b9ac2443bcd424d1c6012b76eb18fa194c3976f7469d0fd1db5a6e8782cadf91d5d7132e74504f5a2630fdc6757c62c96009265a6133dbd51760c072c0416139d21e4c599dca3476815331eece765f3b8151bc9c9d0b4faa8044860e073af9a65453cdadb233151908cef3d7cd876e170da6103d9be1298ce42d604d3963093837fc97438ca62a4f265b9bf394424f6030557af1654601864c0ec9f296445d419ffdbb2c29674829846671c697ca5f90eb1f21b81a3468c7fe43e0d6cc7b021dbd94bdb8f7518dd402e0facf62899630036343663b1b3cc27e84dc10549695e94ca0f373794cc9bdd994bcfeff873d89d4dd619c7d72570d9a0cd9cc74672caa13f7da5588e28a6167612ae05e3e4167c8af1acfb3a4f43061be88a65bc739074b90b1a12aaf314c6e187de0ff650a88a1e0738134e03b292046ab9f79b52c61279b5528c2416947ac0766986b9c82986f12de16667a07956e721517ab64b6a967e4f1f6d8eb76ff61c59ed0a1e198d82075c4e0f6567329e33fc534c3453d4acd7728363381cffb15b1a14dbfdd943683706786d6173b9ab1aa6adb49dba89163ed1d5a0af1320543dfd2a602d9ce0733190e2a37743493239c19258735f5f2b060fc1574a0a3e3eb14f6fa41b995cb2d5460a1998081bdb11f201c456e45c535d7cb344c40ff00378d68dd40286da88dadecaf96c29bfc3575399eac2b6e18cd847dc22eb6491e8951ae6c2a86c712fc3b1f0b34eaef7fa0818fa9d54e322e852044e325b62586995025e65afab44302633ef794887c3065966a315a3fa360d1c9c3fba4bca57b49961f5577580f0ec4358edc42cde755e6bb5846ea8888bb3760bae846bd7862b4166d45586bc1c16edebf5cdb9e628f0dd378728bae0cd2a677cd25bcd9a64e6571343c39b88d937b9081f14cede979c231df5a8114007d1d19df991e47a0b2f5a2cd3252d5744298d33e9ddac3070b7aeb3f4b6e488eaa0e94f4a7c91e59104c73f09a79068d59d71d0ebae52e97589ebb544fd0d18189feefff2f43d87659842b9fc97ab82a095b9eb21d3d9f46bcf0a4c068f45776a6d3c4d06d70999e6d28825bae532bec5ea7891cfdaeed28c399c8ca85ee9ed34baa8d108860718e9cc2f12aea97b8b49c74a2d051755b584edf885f8c45532bbcd3127d36588f7e9553a33029ddb508d2c68ac3b217b3a4a037e1af0fccdbd0ffefc84f45e79a0b6707ab8a19f9944ac6c0e5c61531f36e70697391da6f484f43da58d335fcf4ffdd3c3fc8c316e6412af35bef44c043a3f79983f6247d416508ad92824d70326bd69eef83ff993acbd7197ce7770b5d025506a865fc77764477a4b820a3da1f9d4d0a824324168f0bb0105729b645b9ca27302710df112b312b9ed876c89df3e00dc1aafe1789bf6a1030e0a0095a0cb153faf7bedf6ba7a32828281c4b95096691258396faff1baeaeef6664db2a5960b04321bc0eaae33bf297a9bc04e9dd49a2ea27c8a1c93f7678fff91b12e06b6f188a974dc4630542d91217f5ccc5095fa9d7b86539aec572f85f9298bbfec058e824c381e64eb0005b62052eb3eb6aa37048a74df2820f7b9c22e4c21e237c70920f428dd06f031bb2549b7d9afc4fcfab748da4266050c3e109843d054ae30a181761a89d0394e107fb4aa88b11ba9f8428b3a5f1bbae4a48a6378ff9a43428b3cd17054c9c87f16fa325323da0f7601e4cf3e34b1c293e11ecb598a7a6c0c7ad8236acede494248df0923bb43c7ea050f629620b7baf0d6123b3c038083dd27527abd6d5f4feee50d02e24608669e14ea4ca7515e95ce6d7cc8655c7a3d07c29a6758d38019432775c67a5aca4a903dcf1d7b9cfd6d89f6c9f7c210fc2d2ea6157045fcdaee30ac9c92a58e305b9beccd76911d8ee84d7337d3fddadd5b55d31c4405b1e70466fe1c31ca98616056bd267182482e22bcf25a51f166b8e517a47ccae43f89f85fe3234ab3048b73f302d2cf9a83a031c8b49b0bb32e6db6c2e3f5fa19f783d603b5f201ed5a607af03efb01dc06cb37fa6db444ade19d494014bb88b1e2ffb3fef45fe7e83f94b3aa897ff509793800709f353f8349318008c7f85c970906d770d5533241c16053278072b9b0b0953142a403e4b99dd384863f48e143c40b6fa57f4d52b5970230a3a299f04b12340e2fc7d3b85e9c73c0a6313b1ad8ce61ddd877deccb99d3b7ebb2fef13fa3f8cc4ac6db326278bc3de7e53d691b267c08d4f9b2b6cb51bcf9d417024856532b74fc5278210c279bce241a51212c722c45e3fdc6ebd45d408f60b181971cd5df3e75d3e73b955bb174bf2c65c2f43196e9a4d0d75df24ec34e255d8658258392203cc2b8efb6392d546846f14a349c4a17ea3c422a18ae223ae8080d3631b5a49e5d9442fce8e77b8f9611d0e76ce20a655db9ce9ef22d0a9a24712e2127d5614c19cba851cf738c7d0836413fbddb1a81e48e05596f30b2222b9fe4302e3d15168be0714ec2f6dcee68ed26199d89539c659f9834acb26e9d486595f0ccf63708c1cce34d3ff59db78ab3d27676d2accee1fd9a50c14e7dfe57e30cf35d9880a8fdf238fbf6a7cc36a05770a945daa38492de380d52290eb4e405de23f2f6f57e26a51a70cbe549bdb28c6a3a352c73b7d5dd4ee945e4974357c171a0b843bb6f9c80b4eea98f63fcaac46ff775181f5a13f885d80d8eae9565646afc9985629ca74492b132b22cec9dd3d1d2c9464694627774fb12fce3985b60f0a02ca6ed6daa2e93cd323592bc65ac274bed4e5ff092645d813da835b339d6c4b614a1c87e56b884f5e0b777ed8d59b6a8b52cc05c56fc023a52c76c811cd99c45df5812b077b04877600d2b12b6b15de971c782941eb3bed01bd94fcba5b147ca9e3d75380216ddd6214cef4f03f999fab6a5a0182f1480845f61bf86ffb71ae742b88b605f2499d35bf1a3a645b50f23b0f25839d7d71da35fb6dc3e40fcd47807669ada0456fe5622687bae0ebad10264c0d8d244fbadbc62fbbe41ce0ac6b0058aeb71f4cdd29a72f776e056ac1a53994fd5e1bc1621be403eaea57774af04e8c53f6168a79b23d9a8897e94b67539303b91b82578eb991d9c6e50d07d3e16d8c32b170e6cc1a6798e437613af42dc798c24b6d45426d5a2006eafbdbd0fc591f61d08da952d13fa30f9551fac23cdb78515bae6d67ff0d000c92403834352dd33163f60f4f9be682c6bd157c21fe11c8ba1c0ca04fcdcf2cbe7b88a48d311188fc1c9a529610f3c49cfdf2c275e184e180400b15934975beddc64a8aebcc0177d1cfa046271b3c1b9e6a9d9617cd8ef7a6e0466af97934258897152975935f5e5cf9caf6fe7cee5c2959cc3048970667fff958a18776a2958799ea384d4b52be883762d81446f32399e6de58e58f4947eaea52e3baed01860c77abcb7e68fb4eebcab97d0161a6401370909ff1f3150161f7c50a6ea1e1164364963fb797781a901a3fe86b09dd7d051bbdaccf7935f63ac95b36e6e70a81f894d2545834b6e48f301adcfb4d3c6f6b4d36d11ac9f7834eecfee50828c0e22dc10cb8fdd411cbba3a5ed73991df43f631a74e5b017dea70509364cc624b0607a72394e9bdcc45b90aca8e770c4fd794378907712fd0e25ec93e8089a8dcfbfc87e4fceb694ef53f981812964bc67aa792288973dad1ed468f41d3f39db8a09da8d744410737e5557ce1f940acca3060b8b8a1f27af2a505bf5e1f24fdb66d6ad11c9093fa412f3874094100e411556de678f4f6fbbc9d23d7d47d4e1511279ad15736c458f82719e4c2ab547ab8ea119ae83dde3f31954b3ee665744680479af8acc5616c337fd4511edeec49c80a3c170c3a3399ee0f3fbaf1cba086ac402e9bcdb8b7346ffeb4e52a84e3020f2dffe4c05eed522e9646f71adb02deeaeaf393143235e3ecce93efa62de1a74445154d22dc18278e2aba07830b8aed4bbee5beb24d8060a571ec6040bfee1282eeaee1fd94c3c9aabe85dd10977c63faae2c7debdcebc60bd3fb4e659bbf6c071fe8f25701305c8ab07cd1c98edf87e9c63435619c2de29f24a474a4e43cb6521eac18002dbbf8d69ae61fba126eb2d3d680ce713723b850669ab09d7b254386b7b0ae3544972be901ef676d6e4bd288d82a37a3aa2cffaec95585829aa83c1fe275535c607876a2a7882ff71b92e88cdc57e01fd722a0ef5854972630fb1bfc5ae46bc08695cb1790e45c8bef5f8a069d273e4359393abe8b8b31a05c0a30b3e73a9c6b0bec160c01d4bda6ed0562fd0fd14a36f42aa1dfb346c278b1971f393e775109ca87b13a064c80c78d90ace892ae029c24e85ba46b485b6d173e60d7ca4c3adbe6aeed7144c8bbb56bf3bc8d89f5e74dac9f222b70bf33bf7b18be72ce08cabd0934907d257ae45ba9c1762e8ab67e282961647391822314a705d9218f78b83c45de52e1a608592f8382d407718ccfe708cac63fa2122be7dfcea41395037fb71bca5f635524da28d95f6420f511e9136c259dc89114eb883ac9e7c52db1dd9554e3d8663f5a0a4dae22c76698ce5cfdc927733e19512bb0b8e3be8bafbd588c931b5216e85ff1c4e02f34200dcdbdc967f7881ca7ca539a9a76186437b9b4f0e3cd555e85897c869c7df2a66a762590f4824f4b7225f3bb83923920dbe67e1eaad3deb7123bfc8c4a48e178cb9eb385dc3fec12df703d01550038da89b885ebac2bddc202eca3a1fea3f2952ee9053a254abddd9f5c869ede9c82bb14d15b2c29b8b8b099c40ab4a5e5aa2eb6c9c3e3e487ceca3b07c970cb3d55131aebd8482185c67e3af3aec27645a4a29d7615bda6e26310e5de23ee67d746bdcc6097e05a3cd50031976e53680a0ccf800927704ee1722ec98058a6262dbbd7d1d5787fbce8a5bbc2850f7cac0424adceebac934ced26f1441e4d5304f172caaeb11c62fdee6b8f903db87a751bc240816a583736565d43a6b28c792dd8f89c81ae6c4ad92d4f32931376de8438d0d674f6658011b72bd1653fc39887d5339dfdbc13643a8cee7a3c04b93f47eacb93f8270f56e9b8557a025845ecff5cd955e6e3ff380bd8d87a879ad5887eab45527215181e7a4ed794e88187bbc14cf6d4828ee4b5a2bc1369010e83cebcdd64d12fee4589096d5fff0e50a43ab6db6a42c3f2b93efcc3965c15105db8312a6033bf13de6fbbf6cf0334addafaefb669c612d538d32dcacfd624c065798c2a08cc681d32bef502bf09e47a0bebe349f9bf0ff56243ce061e1e37e57f58f4f13e3fa44a1171a5f1f818b4863de89107774ba4dbf9733dd4576849dbf2c8594c76efa2ea50a276befa2b5ada1adefe6c1219399805b300a944d58999ea12a2503bbc492849a551d977528d967177a25f4f93fc37501ca54b213907d6f98b6f30ab10347b5833af61d6a3d7dbb35eaa7c83557867ea0dda251eb79f92925f8806f6419ab75fa89502b101c4bf760e35e85cf2eebba5acb5bfe12b384e8de71dc3e23d3558424f51d557aa11f0f631e62d590a3c97997855d8016886aca694763b4a8979266cbe39a14a9bd7708f9576ce4db23076a2f14c458c3af8a5fe66bcd09a8ec3d649441687c2c3c1d544a72128e029a6bedd4b94be824b813898f6f117553c4f87ca63f3a62907d2713c8712820ee2c0cf2f34e90057c65921dd0b506b90f31452e4607b979e0f4f3c3fa83225a6d31d2cbea4d33c75d4f75e19c88d5374c6e54a11dd239672f0ca943d00fc96cc25e41275c9c09cb8e7cd19fb5dd545bf066154cd83c22fad986ba3c7c8303d7724f5c185f4e5c8f0d49719195a674944249d2f1ac75ac18a15dfcd94cf9aebf486b4e8dea2ddb362be401c6a7b2eb666db9d0bfdab3699f6a4a59cfe56341f61062388ba9030a748ceab3aca1bc663a1b3f1a2fea69cb015989b7782865cf43344459a13de3c7b4cc1e9067928b54e02ce10a7ac88b4eebd05edd3446bf937b000116028432d221141a5369caadfefaa0357d2177a6da5fa4cddc88888a94d76a15a22fc54f231aa0ed07bbbdf70404e54b37a5409778188670c843af86607f2077d87d7d96fe521fddaa441e2bfaabaa466e8fd459f765c0c24c1671b3ef5eab7b55cc32d5021f2bb023d00e2f0f649cd6b05aeed274a1ca2163cc2daf925f44d6fd710b72f658d717bacaaec54986ff8623389a8e43ca2015a102077e7cbfc45f4dfd06a1236d72585475946ca9e30511a37b35534ff7d08c81afce913bbc21144ec5c61557ba9fcb2d208529ff5a6010e32d56727896ffb0ebff4d5a440597f044fcd7a8e7b5040efc1170aba877e8179939b5165a9f0b8dbe44a072f65aa6fc9a383130ca30b3c06dfcb0806577b34d4c33802a31da9bb279c63842c46efdc2c2fa2c8e60191e6c113cb206b93f258d30ee171bccffbec549b4b2ef4d43831719e904cd77b885ce0caa485ce434d464bf94d3ec362764701b530f1d4f3e9eca78f794b42f80f2b6fba4784e02188fb89c13c5794936249bc76e384369c3a9d96b0cb5f2c7d356a55f186cbb8dfb7fd0442edbac6103937085d5abde172b622d40fba1608047fa716b19a44832c7e8674cfa54205562a2e516c316bd2f1bc47d870bd04900366a018f4a07bbf739702b6f42f7f25c79806c48924cc341901a7f7100fea1e1ed6ccf57f1798d48050e00f2057a58dbf99062fa2968b706b0ce285502fe73720b1b9a60a63e5fbe60aaeb06e6e8abb8a786456880d5363d2b927982060c1307f4a338c484debc34bbb8883c4c9b0fe4234996677b3ea2ceeb70a22eb2a3730d4eb3fa4b08216ae786ddab3474264b763ff826e51d7e92e380aa866e8d764f2634fea73630ff1a866bd687a28b5e6ac74bf570b056af72157891df879f73805a028dc6fe8ebea684c5db30d5784ac4c949067b000839a62fb6150bf30dcf86eb29ac8ed646eb2e11692a2cf6a14a9e4ce3faf343ddb9ad79c9003c6981fd5f090bea6e886d21c6299b68512874eab2a21d13a903452f1137e6b3ebdc2198e57c920bc219db99e1431b7610f7b1fcaa2bcfa57f30c6848966a18037ac86b288c344277683da654e68d968fe7b3f8e0b6a4a798a15bc40c48c01555e23676b15602171a1369829b16d1eaaacbd84e1c910375a53a26b10d9e76af8f045de413051c8834f2561939f1375ad0a1121b960ba637e05a349cadfcff8c0c53fd1dfc67b0f3ee48e99b147517262cc5115b0247e673d6bd5697a24114221a3ec3d3ccd7d7a724945d1b7fe9039fd8b8dd906bbaece6834c04d94695ad3d42a62c77a333a3694e2af3130ca49dab0ce85b3fffcd97fc64c1903c79e5c27de0e04bc16c0c95f13cfe1cdef980a2429acd4371ea528a5859cc4bbe0dac95d50849e42c096e786ea30e523d2ad091f3bc1ce833466ad95256e00a00cee2ba0ebe96e87781f8c4fe714cb7d3aab8666038c5d51614ad9274e914fbca0947614278764f254b3b44370002ada9cd54abbdc6eccaaf4aa7dbfc8ac51b85b74eb961fb1a88fe9c8478a10705c1abd1086d55d737f118f2f7bb5ec0d86dbf2471cdaf7dc3958cd396593a6e3da6281bd668059dfb976e50a8bbbf0ed9b6ab36a58b441e70fe9fe5f557f0e04a9956fcac1cbf6c33e451268c5f7bbd49b29e3a373b8bab3e46c9e8be3b2a3e5309cb0dff48096c530d67fbbc359f68e1ae9346581cc3b929a6e3a53b6040d04373051714d47b8f52c7d87a82946f6394e0a82909798109240a863da401fe570f1de486e3ee45e9856bd3df3966e2ea98239fec42012c3eca2793dcc0ce25e7b2c52a7c74d1a647274b08720e5d2128cf875ee27ee680e424acac805db4ea8a0b6e025e06637623a1572d1295b10c0337bf6f4800cd0cc93414b8ccc1e476c6c615760484a89ddef9f57ad69c1fb4c45d7581778153b49e43c163ed9e7164ddf7a6f87003e29952f8632cb7c62ce289eda8e5a326a42ff5323f9b8d256b45ee1b52da1654d6fdaedb8e054bb6e48f85c85b122ed83b897f438014db355b1fdd964bd5b3dde9eca7c09cca93ed1e6a135a51218366d7a3aa0e07c59adb1fea0df33286b85144a2f40db3c4daa512559a071e518d1940ae83ac94694d889c12ef6d6bec40f176ec1d82b5409d3775e83eeb7d84b48d83b4ee3ff6ff63bd75f8539b54ec9c69cbaa9cbcd20f89e39fed6542c4c16e00fd0f2d63fa4b84180f719f29a1cd6889a154485cb407890e42e2063a38b0606e78f0b32e6c53ddf34f45be1001bd35500872150d127fe142f57ace025f210e25a2d952d42ee28b0c47b1f1b5f34ac21a0d23241f38d75c88a01c115786851aaae723aa083a080fe1a1e669cedde9c2ed11971e81cbee87e5f8638ddc5d73191ae979f1b44497ccb91087b00a6e723a21da95e737b6aafe1b959cfe0b1125ef609c5b0eab60a8a58d1c2fe3d4f2a162eee2f698adb074c8f1fa4b7f881ef8c0ed4d9cfa55a7159b34a8e9bdab829724a20b28d2714db9c499c4a6bb101df075c4bce57aa05cfc819a52b675e4bf58217687ab5645c66a270d7d18f473b931c70eaa30258c9a42c8e3330b63dd8f4b4a529197fc8a0a7d05681ac0cf347c87e08e564db732ad0d49b6fb507ceb54bd09f95f15eb77b869f19a0deffec350df90959260bd2bc44931184b62a08c195efb5f67e1289dbbaea703b7aead7c5bcc0a5e376188908325f4964011b1a8a2c234370acf7aa4444bcbbeaf35c6cc660335f11a9b74184c934fa891a37deb8de11de17c43925aaf46b2c4bf75033313df8af2d12d8007c7cb6077384f7d817897d95cc1b78b60b52842238990d3fc00dd8cf8dc8c31e95fc8c88fb1c359c1a9253ee55139bd1efd7edce6e85b0aee5d21c15231ffcd51dbc9e018019f498ce4ae5d4646ead4d2ad595b84cee3633c9cf0435f6722e01f27d2fe923bf83a6fd1bb9e2b7e8047d332cf1dd6d2e5d6889b34e8d13a640ef8d529ca481ad6cdceeab864e8a0c50e62c17824fe2754b7c037fbe78e738841f574d99fe637b61ea8f374487d9115b8a885f13dbf93d6a81005f824fbbf281bc70f3207eaed0861b0c8a5e0db4be4c2b7945c431f06486153fb01f9e3832dbcb8c44736db6fb249ddca621aabfe364fa3e2de646406363ba6f68f96602158db593f6ea682ad8b49a039a7210e9b251c17852086bfa063980d3e41f94ecbbd2bc44dc4b88cb9e1975e92d4d92f025fd6fb7bcfaa35b7471453b459664e834213b789a76a4deef40cf0712ee6946ea6b607131d739a988c8687f23d7d597f25b1033c3a1a96ce78cc2e845d4276b54e7054b03da8ad8891d5d24ef7d761d573340336e45acfac17c0e1c4ec6aa682c26f548dead8ad74e7987bbdb63b8ff59282115ad1d9c876ede18bb4cd15c3bfff68c168e105f267d68b508d4b59042eb82d46f16f52f1a00a57ea2ad27ab5ed16365eb9625e5c74d9cfdd3bad1d4b2d71bbe5b9f392e80ff4592b20352cc355af05a64ab30cb8aa1eff17017592ca3a1d3b45cfd2909d0b22f5271d117fb19d81d9c2759d12546f070f673d634ee8a66f2c1a29488d6abb828d2a02e683bd78db54655aed356defab023ba8d775b7c6efb57075d449e166f33c0c7b198f6ae51edb5794f4fc38bc556d94f8584b57a9792bc27dba4c39bbccfcb2c6715169a641fee6eb08bba39804c52ac411506397bce4e51c7f3592a153a03a6f9c80196b4c3d796801ad1357ea841a7d4ff41657bc63d60871217cc6479f522fa80f686add12f0fdbc25c33cee1f45c904d8062251cb84581d2078cf57b09a888af70b09a653acc69cca83c02ac3cce9479b9133a6a534bbd7b7625fa933087e9a8f6382ecdc4f5d04e0fca26de8558cd23e6ad9bafa19cc357afa76546d9e7c157674710a939672dea5b64eb9b8f82a8314def9e2b633ca94a8f472e68f03bc9a08d085ecafe02c17c48426c63e0c13db3fb81545619c4192e851e5de96bc47ae6c5ea69561483820c721e97e69354682c6e4e55f22cbe09208e5cb33f119713828524299b85caf6fac0d997fde63acc13035d77f1a7fc93bd43d32cbfb1ea1054ac696a3e6db2dfc7e01689c9ca67806bae8d048084f2af9f86921054cee604522c93df1cc0670c4038afd5c6e02366411dae4343f17d37bfa3ddc30ca84f60c8bd571d3da67da4a769b10792cb4ac10aa74c20ceebfc3333718cb119ccd22d2c0fc0e2f3c2727115530b463d088b3e59bd30771b5156ad9fc4f2661df5865f3ca42810058463f233e45e7bb56350ce113acd6578eae60e9acc2624d343fa93074ef7dd29e5cf78ddbc3f0d871d89ca654e5648400150620f130a0f417fc652dd3d59b48a9af00e0df2ed0450c10b4fa46009b16a9b33002005edded0e490ef162d49c97a6c0e0b2122194419b4c88479ab034fa103b9957f12282be9f431e69ab0f81081ba92a1b6df1b73d414cce1b75494276d37b52aa22e8e3661d6839e479a94a79eee08639460ec262c0e715a9b5df5b9f14efcac9ab36c5d4412b9098a9f6aeddca474fac8fe13c093d9b9747c24351e447676148e353756d983c341c7c75d0b7ca8ba61a2250cc40d07440eee934a48146a38bfef218218ab8bb19ec810683a37747fbac1926924cea5215be5470a40db6bdea857c781af74fea42e9122a8ace1871d88f42024c21859c7977847d97edbeb26ebe4d645876fd24fbd65644b77951f5bd75959295ec26dd8000655af0de1276d74b2505353afb2c15dcdd29cc7a0f977d11c433828ab4ee35e3ec683d6f4353771487767c6c990cd806c9768cbac96e419fd545510e149badcf4d401d84689217c3a07832aa8820a5c6827276b80ea3f259d3c7583f9fdfcd15b5433dd5169b45473eca1183f40f696e0782320463343003850c6217663cee2c9c201f6ca60f1c55ce1cacfc6dbd98b8bd6ae6c56ff666603d9a9c4541b9d82ea996242bc99ad0e276d43d0ea67ad23fdfe73f348110e5bab687fbacb48a3e581f2b2b447169ecaa3906bd25f8083b047154381f559ff45effe749e2a180668edfa9d3bf078b1aad286667a2c43055eacb6b49673952221d761783b2caa8b6d4f8956bf7f2b1e4acaa17175a9bf96139b56209827a1dd6cdd6fb039a23cfbdd6c8eb501d6d0e8cb0cf91f21636cd4e1f07dbeaf9215c4dc65689fa4ef6c58c61acd692b4aa3d6984b509a011430fd62ae9732beb04fbd666d11e1133d49f82e83f3c84135187e336fc14017dfff3bdcf98f8d1892287eb8d048bb3e558452a00303fefe63ce31c9a4a769ffea19f10e7c16ffb9852395c22ddcc73b1bc106be4991b9bad9881e5c7e245602d95d8bf2c678d9cf243595d8cd3e0041b375919f1fbfedf3c43cf669846e8c61682d665c15a494b05cb91a139582dfa5a3118e72bb6bd163a86303b2bec840dba2af8bba7469c9a528010151a5060fad74812d0b71a88cca21cec546602ef9c8e146aef4945a510efae8a0825a1cf5cb2212e605f0e01c54489002c6d4532fafb020833f109bac5069381c5d2ceae383d09e39d113958ff45bddd272de33514dc4103cd5099234954e85f969fa6abc57e68e9f560c649259a0e3d1fd56f27f803c01766fa91897eaac84a468cfcc38db106974001b62505927e911557ea9f57b3f9598fe7044ec570dabffda0776658c41b91dc50bc40fa4950ca12ecd0b4f727b3e74af4dd5be11dcf5a2a626e30e7da32bee5c9b2707136ef610f8d4567140a8305e360dc29cbc0315cd160d3da94c9dca1c28f9876099e25f3737a422300fd2c73824f1bf4c27a85cb45ac3128a79c328c9de82278291ea3ce98b7de40cb15cc00d04b2a6ec102b44ebc59b9e5ac112847e2beddea4925d34a078aa2b98b5bcdae46beb56fa61fcc10a0035c51911bae2b8420dc92967703c8fbffeb83f14abae22278bf5d54311052dd44f9762661a5c8a3bff87602abdaa2cc6463463053d37a842f67523da9547b8f409b56c8bda0852671a6ec60ae1e8f67cd7f55eb5c7acad4fa4245c58daecea11ac3e562bf77ebf5b2501d993ecb140c911b2dbd3d6dc996d647b642f5710ffb7cc53d9528a97ce71e4ca793707829c7cb18d82742d0ff8621429d559080bbbd143789e46f8d47c1f8563c142b1120f9da15f837ac08f95722de9fc220e8afff116982f13f599c6aa7a95f4babd9685ce1315fe8306747677f40d0fef9d0521daaf5946bf7ef901b3397a58effc5db9848e16d6076a2627ec42e761e86bc9c75975cf40c29863c49ccbd16508a20876c39d2a67b9b21eb11e3c7a4e7c5139bed96af39431b14d51f44c7981f85803cd823e12aa5af161c79afcc9e5fc6054613e74e5a484998189790b97e3b7c10568b3b1d0ff63869764660784b482fb6c318d1b50958e129ac904ad7a2f7cd246efff6dcbeb1ae70ee4112e93da9bbbb722a1b57d85d45db5afb3210ac0c2c22035291e2dbaffd7e2684451811aeb6ee55d39389a1f08d6c5ea9a80161b322b441ef89904eff063b80d95e631ada8387bb3c8b841ab002ce1b6334872a4a89536f8e232e78121dcb0ee325a4b4cf168d3917566c9421a472fdc2ffda2725121dafd5e53bb9108ccfb36246d9a5af7b9359e4d06e43f9a572559257467613bff71a381bd8bd7d0f2df1bed05e61634dca8d29d4ef9c55ec7d71b22f628e16690b6b465333424357a73912b8b628fb3f5b49cec3c59ed2939e4a189ce7d8c23b87a5b7e114414e874bca0857e14ca797185688038611836acb7a045910ddc235b890e45c6fbe5cbaaa5667e84512baae7bc1c27ee117ce724a626c4487aa00aa6fe14d1f05a04d7be411540b2bf98d71f3fadc1f119de762e3a67cdfe8600bc2b95f0ac91c46404a81de6245bf6c22e382fdf85952741a24e27638d0d3f668f9e37ba7bd45d6151e1daff47eb11abf99af9ed09f733b6359e9397c6189a2a69ed30d1ca32c6a72dee161b1815975f43cc05c33ed7052ebd1949915001e862455ac38eae57f9430e7d1a0c27f7022de0b4b46df3e207acb15ec0b36c03981cb95c6ac7233e4ed5589e290fd8c5958a25614d56013eeeb1ddda44940d7684a162bf6c1c800f99bfb8ae6595821fa6bc7043dd686f2a9fccf9a355ad41efc3119b520240c026d044aa148d1df9822111f0ec1b9c0e5629cd3edb71f099479307e28240c6ab5221e9bfc95d1e0b4860fb50146e36ef75345492e9b8cf5db9c22bd4b0d3f6dd9641213bbd8082016466e658039d6c5bbe02bfbe626ed06b9e754323a9e4d31f02a208372c247cac804587cc03f5766c5a1c10a55cdee2499c2c017e324ce62c16df336aba69b3eb79339080bf2dc352e81dd01ed4d4f4f082d9dcf8ca18ab3d88be2606245bdf23129dd543eede95314e71a91d4a75d3438fe24614b3ddd8e03bec33587659e796b29845c4a302a4217f6412566dcb63a80b5b4828d10b567d69b4c6d7f3f2023fd7e41dab6ca6d76ded7ca25b181e89bd9daa10e9039fdee28b02dc8a4b73cc36383160f1bdbda969019ce1ef824e37863ef6d4ae84260de941d5c71c0682cee7e779439a7ddf0915c18f450317d6644deed36fafed9a1c8f9523b8c967e535e118c04369b4a379df17d3522876dd17a325b87d48cd5088efbcf98055960ff47467a58bbcd84b1e606cd7eb2d8d7ff61e7e6e78c228c93a87ff9abc9332b3f1626ac652f2718726ed3fd8867373f2077ad9bd455a47af08033fe8bd5c0fc606b24f52c1b2648e70e962a799b6b3aab43d69ff76cca3a756204cbcd36900dde4693da30de5beeba33104c19ba6fb6e68dd785cbfe9fc98d71a50f3cf0a89bbfe12b6c1c5eeea85f200506bc00f0089f3ccb2c0771031e5e3d0f53dbe601a5c62988ac3c161659a5f0c0831dc5d2d2ffd0ef3075ed290f9022fb17ac6e0872e0c2ab898e18a2f1eeae8c64d39facb2b60c27ec18d9198b87ba6a11ddca1df0b11d776563487395e3f43cb81d1747c7c9357806c3bd2f1d77ff1e675ff914dc4381352ede54ff05b58ca40b2b6f2202cc6c8c96770e9ac40ea31e0a0f6eb7bc19dd1107cbbfd06155721d852115f14959b531d24719de1abf523619e1f541ad3dd89a80938b0333366505117fb21a6d04a4da4e816b53caca497455cad6e4f70ad946615101cacd6af43d9ea25636e420201cd254e7a354a09eaca64f654580c1b0ebfd415eeafe4790a96a904c9664124b32b256cc5029362ef5aff2b4fbfd2b2d47221c1e3e0adbb5b45f4323bc420c6a7ac67afe2b0c63e52a74ecbc1307e3117a890a0632f9d1b9c97e1d27dda46aa2f6e76de9decb6c6d6b0ae8468feaf6e187e64c0a4839af11db5d490b66850f74cc016f78644847ee9962af41e25a0a72d67cc1a6978a623bdeb31eca74856d20c7b109bd8bc93daedd19fc9d75e82772b548d948473ea18c4bf5dbd35a8f2a98d378540979a500d4215fdeaf264cdcfdd2b7b8f716c59744a000477b1f49841c847b94742437517595e76b2bc4815fc8c3f1867dd68dce33deda0630fd56e07c90d268fbd924c6692cf3a83794b2d6a97eb02df3ec335c2f097cc87ee46d32bc506bdfaeb6f6610e1c61ddbecfeff829e1a80bb6d3fdbe4dacab80fff43522f5447815b02ffdf54362d4c71721bda0232b835b7d97abf79c52ed5c5206fd1a878c7145088c878b773ce8605f2c6b14c48163517b5b95bf9a0e7d350c2439aff16fa564c76a6a4d71ad74205f03420ff86a023134d319f438f45b92fb0ddbf946d858b23536d6abfbfce2f6a7af7bd5cf5455defbca5c91a19d6c957fb270e7c8d477da4eb57db43135926c9eae0edd820dc7aad89e55d63ea79f5bc2bc17911815d68d2f7f0cd968a64b99906f48d5824a0061e0bf2d6cc53330a7880340135a72ffb1987fed9d3763c7e592d737177d2340d10233efaa1f66f0e4f0183e8ad265988a2d7561eb0fc440c9ccbfe7c8409544609b15835522e8a5deebbcad1ed18b4e7e9c74e5fa40e21af51273693d3fffc25a11110067e46876ac7167a44b272859597464e91afa1d95a02834af5bc3a5700f45ea48cb6c1564b775c2b3c80fa0892f0281d6c214988fe6c5b736160c309bf185f5fa1c2a26dade642dbcc126ffeb3f36da65b101bb5f678da5833d2d2c5146549bacc251544913893a8f460172f77b38d3460f32b1b4a06f196a37f9891a1e3fdb7b05d55bd4f70530621c05cc4e9407a315d0bae88bad962529653d1b419dc5c5ecfa137d0c5d4289105b5d0211ad37d7a1cbbd933560d94d55424a1aa9d44000185cd7febda67a742e804bafb91ba8cd8449592b69e8e009938617a339ad814529a463a9cc6ee853ebe19f9191d0d13a08f97bea6977a04821d45334d5a7fce12d10a94920f7633ec4abd70aaf4d22a55c8f004e3b947ae833bd5634e2731337335aad31a13ca47f06cf39a0864db1e91f86e19ce511743f1e2a3b69639e4a3cb5494db544bd4a1422914f7d0b7b81b733b42a5bcad920954d6a5f80a0dbfff95c735daf2943b831cca140ef5fdd6611e4ba8bd508fc8aa7137a27885ead830d0fc202fbc44640bbf1e2ede915d99fd05d811399f2b7302641472577a8674f294aa3d4fa1dbab0cd794c2181ffe827207538aafc8f6e5a3690feb9a427d66e92d5f0fb131b4f4c37d895cc708f70cdffbba0549334b175efdeabda4b0c041ba2bff10a2ede8a45fa6f8f1b95c9491ec1e88c28ba23ded6e039b7e10c86e8f146ac0cbc37313881cdc5551272c3f0508f8cda1f1045fd3e87b6083e6dccf92c8173051ea1020be79b82c88aadb46e6ae3195ba4f9e8cec7da1ddd5f53b7e8b6e80fe0f5e0f186cf0346efa7919766030d3b67c41b9d3e64c166957a8de7060725ddc247eeeb7a2ed74836418a59e538b178167de110acf799000b92497f193775267209c7d7a3f19dcfac5aad9bd7874730556cfb09bc22b2ed32d19d6a9ade29053ffaec5248ab116daf7a2d03478c9ada528adc68a348d2ad1957ff37aadc288f837de47ddf08a114a890e719c1df4aa2023f3b3d1efcb1f8817ecd8034606c55061eb7d814c6f0fccd2bd7be899c4a4aa84592ea84f77b48b023c7e1082c8132df11712fbbc453520ff4a1267b5d6264ab55a25cb1ff51342d939083ea4a0969b7afa59680b257b98eb2096cb5ecaed72a75ed4ba695c89a889b3033d8253a127f47e777752d39a15eba7bb07f351db70a2f3b922a27689c67b3950a0f355edcf13951aeb8a86c66642d84ccfc77f26776a3168fca822e6667440da989525219857bffb2da6a17458d6c5690952f4718118f4d9cf1af0acc9ef2cdfbfd0a071d99e893d9c9df941bbffd48943a9785854ef275920df062b3c8276d969b2805ec07f2b19c5920f3fc2e555499df38ee12aef7635dee998d4d61406a9b0c19e3396b3284fab6017593073b9879d27f1430d214b3a0d3b459224078a3347bbed0b7b36d38e15b37115389f9d5a8cfd75e0fae27456cdab486ea781bdd9b9db10db3eb557dab5312f27b0e1bf344f67c8aac437eba00bd7e493a8d8f3fb30af971033f310bade230f6ec9c427bb6fdc22523ac4cfba316e5cafb3880d62d59c0e449a22618cfe20131c6ef941be14e4415556412b3859d25a6b963da1248cba7e47eb387e33c675768db223b26e4e21b10e117d5d0b304896db90047471089db03d1ade72d20796497f669c3126b0907226964e4061f782c25e6e7862e3fab3f66d38a30bb6dff696bea7d87f88d780e2b294f0f4bafe427c58af16761a098a448fcaa30d6c12307c974a9cb174248205fdd66d15c687eb8bbdcde0ae20a782575739f4cca4cc69945f19ffc505abbd6c4f8cb02379e36d36756d9d895e9b888cf30ef196acfd25bacdb874dab17f881b72b99039f3914879ee192d6c153f4b8cd6af1a88aa788f27b22e3fad249126d673a66309c0ca0e327d250fa93b5a832f754172d84ec07f565493fa291ff08415923dfaaf836d7ab41b06bc99a0892916b2e3f84b49bb587d05bd7715cc7995ae295a926c29a55fa1cd30dc94c9ca03e5ba0ead6fcd4488a58af916ec97f0be2b0b81278cf60aa1f5bcb3a3d806ed09d8d6c722c412793fe4b7603b211b8527642403f17348d0870989d6948f2136301d96b9157ebec553e2c22644d94a031476bd5d7a7cda3c569e0cc3bbdc3303e16afdce01069b8b64e31969a71528b358cd3cb6bdaba5c589a33351bdbf68070738f01206eabd02420c529ce9772fcb360762804aadd68c9eb05b6c8bdf4701f758e0f6126a6bdc1d1067b25a9bf4a292f23f8a00d5d46c890c0ac257f09465f91d76c753cd4e49e1f48119d8f25d09a00fef59ab22525d699403fc2e9e4d6b7e3283f33bab13562fb90cf1f2d39aade36fe81233a65db385a5576fa211bd457c2514a6e203fe623ef19374b71af0aeb726fe75cc110eaa7274528acdaa5a038ceb91ebe10833f9752d3798929e1178faa72f3c4305d91e5ab47b3cf815ebab415d9976b7f067ad08adf5c6b324f5d37bb78dbebec47f5b5f308887d5086b2528fbeaa1f6d54aa398514eb6be419cc9f19cb73b1070d52e6a22a842a5142b6c6688b842dc180bd885e7df787f1874b959806b682feb2405639c01c253e1aa76c64f5f8915e9e85c8aed96f536c82d928a3979751677e0b3521d4589d917ac3bcc5c54002f748d6c6431a7f765c183bf6b0443041ab7b12e552e29bc13f31df7c1b5a08a4701ab1792be67fbc17fb3c602da3304013f83f37b75a40ba5af42bb08c24ce48ec36822dd019bf1e920384e484a0a80bf80a8df01421028e4fdc91872b2a91469bc16c8e90410e75faa8ede88dfb1d54dc0696cb4210393982e497d43785cf479d64a7dead2126b6448880a40cc7f2fe94093f688b74e381ce6cdcc028e3d4ac0fcce8eba22ed9f1b5e7ec05a4ed1bbe687068dbac8c8052dab91dcccb055958512ae495f615166f212a9db91fa727783d60b3d0a10fbba53572d252c5416e013fb3d5f76433ba97495b56b1a505943a7f3f56706ea3e34cfb24521f4500c064d5211f87ca1b349cc2a868cf076b75b06939017b7f673b1b05c08dc7cf4121ef3730f4b89414f6f8c7e46f9720116d57c2bcd24dc66a0e40cd1858d993146bbb932217019c99b847b105a406e082efecdefc5bc0a0319ed9c5653e17e020abfdb73ca53b33d78c0616a4b9f56f1ddbcd0870b50ec835dc0a47c4f047f61fa2358646f3d3e832bf3e3b8052a6a1975e42eb53a341f663f41d33b43a5aa45c92c28d0f7a3aeec0893519fbfd5da8772ff0bf24cac6ad9aa77d17998bdfc33c5eb23fe7d27b5acc9690229bc930ff9910ed5926f32a9330768fe03d0f1ffeb2f6e52824c40a85555398f98b615d3aaca82b05e16028156e9ab82d4a5643f9c819e5899d5d0f868c11826b17c1dc6eb4a83c6d9f337bb30fd317f2d08e64611d61fb743225cd815afbeb5c347307cd88429e63f0dd2d11c5fdbd55c54c711662a078544253f2c4d7d4f4cdf562720555ef136b26a94f23b4816fba81e23726dc932e5c331fe2dfd48e99bf173c65111986c062d9494739f589385e3e491220e5de85f2ed1b4cdf52a9428f765b8c0229a2fc886584335015105e559e99f6bd6b5ac48e5fa4a2780966ab50954fc88fab59235367e10b1f93b6757c2414eed470b35ee3f9574281e2ca8cc91be93c2f1d44cf444472738d6573ae92b9b934fd88596f6c31a75c4f7d74b6f550db01b0ccd04106a2a56fd3740a94e3297483685ebdaca3fe6ff7b3ac387f65a4ec16705eedee76ed2b75541b58d7c6a4ce46eb2fdf782c60aa9315f698b75b29b77d7d49fc665ed3eb7c64ac6be5c33d3ec4534d2de443d6244dd31505884b14f99a2b591b19abc806138aeb7157566efa6fc89db2bda1e650858078ede439407541e82ebaa7e383ed917c916f85279265f50c1a950ac1981f6bb9c2a58291aa630866bb80c98c7f84fb11cacbc5106a05b43b50e47e37fbedf1484917732bf671cf83c985f0ca7b58228cb7aa2361990bb3b7e1b7d15c9f8109199843d7a9e95392b372d53174fbe21439ed1796b0286e4dd9049e06041a1529eeed964ec5ede3ed04a647271cc5defc26f50002020390181e015a067284747f3fbc0a0ed990d3f861a9a6c56064ace4bfcaff90147979c772b6f2200efa295a952a18c9cc05ec51ff932561e4a8c6147e5ef5268cc0a00c84151153821eba95b7927ad676a1de857ace265aee2347fb8d147400086e42bce1a3ae682295f0289e04c23ab45fa52ea49c522ca1ae7acf1e47fac355014ab125fb13577a883c1d86c1b582bf5d736e7d8ffdd911e2856357c2fc8fae59ca363e492768d5241d86c5c08697c25d35a8820fdfed51a019d66047c1cf523806f871d00a11691a9e49f92dd2f63a45a297f7d8178daa8a3716bd0909e1e8c908b9b1640b8e8f1fe4cbefe473f88a9fdb2a8b798853dc87066b2b318bd4b916ce5952f1de9d276dc9cf43fa280cfe1a9ebf53d577f43443b771b15c3fd34691fbe557f232f6bf408a1ef3350219990702a6d6a689d9b0ac7843fb1b78f2b88c28b310fa8a4bae5b81c85c8fa6684bdccb0a88f5043a2c767cb52971cd805cc0b6cfdd2f6f069381f410fbb765c877b44cabf2a36a64dc980be55159d715b0ca3a44acc9eaff13fbf056dd8f1aca58ced2f480e45b34e7cdaa81653eb8ee8ea974e9d0985adfc918388d5eafdd4e760aaa71b3bfd0eef3144f87f7817761a542d8d4ae7c8bf374a83b13d5e187c53d6faca56a8f9efefd31a25a55f72b71addca59dceea28a18f812a0101a304c7d1a862bccc854e4b87295779b868f6cce8a592fcd26e7bc03dfb0c39cea874fa5081b4aa3923442485ca8e30e911ccc3ec2b0188052377846973f1ce9757206b276f29a6b2c233fa07214c0b872311f71be3c98e0c3d1ac597cb289a7a4a3aba7806b913140de960ab01f9ea4f4203d618a59353b97f1bc79ee892016dd171d654802b5083845d135183fafb2a06f42b132dbc2ac4c5e8daf333fb54c26d5523343d0af47bfa1ef4868055f8707d534b129cd03cba7cf56cb51985457855853a2b3d2075fa1806443d19636ae7a78a9f44f695ef56255961f08b284f2df4b5b061a88a475cd83c891f3095e128f9a5afeca2a13701d6bdd4e07f3e25e76882889a4659e0af0a95e0f7ec4a4bc0dbcfdbbaed81542bb89e9c176d1d74e0050f3d0808bf3b59d22eb30139b5e1112a9b5f428776b9e18ad6207fd2c5896dd07b8e397dd2c821286f0af646cb6248b61d63bf04b924ec1c7e6833b09eb7a705277aa75ddedc045646fce8054f81e1c5da24935c552782d51a74020c427ff792bf08500242104c7ec6e9c4aee1c1208f7d86b26b929fba2e1d3f336f59dcc59927dcd4eae126d9120a8a4d3c2836a4bf6386c272eae78b35af02e3668a3d95f226a7c7372866cd5a6afb19eeffc792f8f76fc59493bc680bd393e10373fe82a6edf8292f957a18f20555e2c8c206604edccc0a5a98194466d1f67659b674ba756606a9bcdf4edb489772d20f9ca8edc9e4608b3080930c10f881e50319e2b251595bc9eca576f5229bf450d1ec31e7e6e1baf9e587ec7d66a829cd9daddd29e15d9d0972ad0e8617c7aece5bc07490153b24972f6ae87dd9b5cfcd4a1106acff9dcafe23b434a311d0febfc050dae33c59f94b25b8a0177114b0e72db80e263a689e8f7ad0780dd180c5d6599ba8c932e1b499c3c1db8e45d74422f0497a68559205c01562ea2e6b20fcf04005b66545be37bc690e28f3047f3bf3400e94c0c6abccf03692a247c52820f5d30f3df5b5fc17b75c65be106f9855b84e697ac93800f2124464ee431d4def02371dc27a3674001b44666c9bdcaa4e584f684c60096a451147567a5a80d02df4d3443d8272426b06acb109f6b57621c8fd5dcc36cdf43f9e6f91213b542c7126d137fa9b8515414486a13907f78346c3158f09d8c94f3da78daa4249cf0629eca035e0c87f462772a4917b22b400c67ed7eee212791081e997f2bb167d7960d0741014f7e6c386c740b8cb2a91ef368089eb74a60ac5b1882957f116bea7f10dc44126da31cc956d758e56ba48a6050bf44ac4bce0d6b311ffd27d4b45a52538cc0e3fde4e78ee57c3c80049279c9bc7e5163838123d0e087d6e727e9aacfa1709bdfab11c167c348fdfc50190884261ae99b418e9fe42f727d0447899a58a2ebfd6e7e6f252c958a0272f3577d1dcd8a26a69456586c504295dcf81f1fb4b22767fd9741736f989a47a651de749cd339c8aa4d8b8c2782acfe9046da6c5269a79d87f8047ebf41ba96f9ff67d40a8ff79903ca3c81adf940efa3951903998ae5675f8a1345d982ba423ee44ebdd87dc99e563fdb12f661bfa912522da755e2660e0480c03a0e7a6ed91aa7e77d31de16212f722416f786c4e96fb4a83966629302ff562cb2b976dc17c05397959dc34bd1a98497b763e52e7bc4281e01bf7f120a724bf1ae77013bfe57b62e07dd0ad97ea285715443a68490d481518a15049cf7106626f0160b56c16320daec2079962d2769ff93a26b518f15120a7e23e4aa06168d6c19d2de1af3877c9780c9b1591132958c7f4c1dfa0d4f412dc1454f93ac6b1cff64c38c7557ff396e7e0b8931f7d448252e476204f572b505a8ac8bf0951b4e86d58b0cc8ede420139f5481a0918d6e01fc8fea4350c14b49baa38bbbc09058175dc221c6e54fe12c00c03efcfe39634e9ec75d68bca761f6838bb21c552cf5bd3b25d016dc175686227131ef7a29a5c1f4083d5298e736362e809afdcfd534f402df6b4e7509cd11700a5278ab75b2c6432808b0ee8fc327d47b62c7c708648e99ffcba9d71e9a06fdbf0ca950fb6bf0f6acae2dbfeff117ff1a4ac65d87922eebba61f335e30fb09a6a37faa21a3d5a945a913581f5c94861749fa18709055e3b9faabb035b03a1d63bcaf5cf9c842712e7176eedc0ba17f565a1c5ed39e4e5154c5a301a4debdf3ea382dc6532edddbbacef62cc221d8afddfe7dc2637552369264bd751aaf486af92bb005cca55a0ca1631743917aac2ee5a37b12bd812e816131cb42985875728c1663b917c27b60791f708a14473c6774fe9ce2397a9b970192fdff02dc8f92929ea489536585e550feb4fa7ecf759a78eeba08884523d13dffbb280fcd9d63e9fc2e9c5f3d7bdd0b179eb25c12a05b2b3ead214d908ffb7786d29eb81f4212acbcc6204cbfc54098237d741321f110c977e10bdbc262d7829218244bfca2e2d1a56bc6d573ee37f0ece726f35b7676974b6969f448a1984c05c5e3fadb75a81cedc744f2a6e5a2ec44a253d6ef341c3c40a423a15d3d6859ec5ae60c70dc4d570343d26cccd494e777dda8523713e662a7a5e312cdcbe20d5a799f8f15cb50e49daeae922bd9d12e60f67779e1cd8bb410dd98995ec498c92de060a3d747419cd2fd98efa32257468516372800f6181db554f7a7d8748ed1d4aae35242ef47fc60d7561a9ddad2faf07f01e95fb76c62940ef5e7d0f9bd0a4e19c3fb87d29ba4821d319173e72644a4d61563cbf0ba0a3fa64e5fba049184fa3e0147bbf00b27ddb676a6f418e9f7d56d535b1cfa0993866201a4e3f26cd3049680b11f4378256ffdf77cbb8ae54b657ac3443c4eb7f58d4fd22057bd213b69c588a4d29258c09239871481119b282a0f0d617631f280f4cec769cf9579a9e8de471e4f1435556f788266b158bb4b0df30271bd563efd1449aca03199b18070f94bea8d132b8d74de4292d7edae3a12b411f5c678f223934f8fe93fdcfa31c153096ac4d16ebbb84fab19b3d70c05bdd5d2fd1dd6ba65b18e44ef753bf8a75d1a5aed0314c28b54e9a02d087f26d77d9c7f8c8f8116012960e27741c344810d83f89a3bc8010116508b0a7d9f7d5c0cc376839282da687eaf4309093aea1a7c2f06e95e9499c3244ad96416af48f2dc59b9268dd3acfa0b25889db251eb6abeaf85a5b2190bc071727ce524abf3a9192c6d2c1a668a053d3a93679c4abcaac98ce08e82346d150c1667b6b5819a1bc9485e5daf774120b3ee8ca7395ec6d1247a617cc186e953b574161fd85e92080dba7ac547abf7cf4f535d8fc51be4df5e2db916562a3c0b4faf03314afde1cda2075274f38ea17e5c053637befde8f713e17c20167e38a9fba732845ebac1a60fdd9ccd6e6e8dc14a0109a219e3164a8bb8a24eb29e42a80cf9a6032d9521bf220fb3692396daefc6e90ed266c3a6ce371008b11443d40d18f1e9c960712416b74715863474fb0817fd41ed4ce946d0a546edaaf361fc38cf33880d35ee9f30dfd1e405795739ebff77df711a3b97469fb0757b9b3cdc007138951d73e29f9be45b660285e62edaaa12ed7a569dac00913a2a5630292823f15f246e8316e13ec50785eb24cb4a554599b11486478e9f7ab0760bebbcab022fc6ceb4e325597f18ce5038ec78513e555f37a197f7ba60f3c4294112c5c2b5d54231159489ac31860a78a7b967da4ffb71cd4ca64ca0b80db7f93ab2d3df64bf476d5d4dadb55c3904d40f5395d2e39dfa8792073626bd23e6fd32550307f9f56ba852929904ccf867ddc07aa3f709b638bcd0d44f2460d6b932423efb551aab7b8c0dee2c5a72cff4337320725eeeb8d687ed623ae679c903f6d8e82b757ec1d6c466f1162e9c6e0f16c98fa7ee8f5723470b5a76ada3e66790c7ceb12987c1c08273c1c72bf2b38bed5ebe32e3c68ee51f4d5f132ee23067a6d6b64ac8305306fc11494fb04bfdbec36b1b764b9b411776ab828b11207360c4ef0d50ba509705ea855fb74e5134d9c5f182738ceb57cf79d8e27973785339dcd50cb7fd23fab70a2576bc4eefbb95c57e3604dc629b099434397764519569c16dacada32d59d59d139fe82998a990ba7cfde1c8bfccc7fdd23f4b6a4694d1ab96aa8952659689e710fffc0d57f4ef3e12cf08ae6aab3c298e23cefde2411cb6922165c2b5f27eb253fc455fb2839b0e93a7c2425ce94e552c7449092d306c62ff73f243cbc930cb76bcee335e48f28e806b810d5445c7fe9344e46fff72ba145e8fc7343289938475c3a8d6bec8c5431afa84e95e45ea69106daa8334629d942cc79e8dc5fc1f31b8c504453bcb68567ed12d86ad4b131ca6210b8fa086248e8dcad1566d4a39ab30ffa9b35f08c9185ae6c6e9baff2363774e3f4fdc3b16118176a12d2d37b6d086a13107d00edf782a9183e8c24540cc386ce474dfd02212ad86b3c5828f0f00552bcc5b0ac33a8f0cb368af883e5cabb5be3c216b3190eab5668d6114cd56a274bafe7ef92416151e74da3d6d099797642ff271446278d426a6b95daf53a19d5aabb673574523e86d3d92e8d5423aeb0b36edef3e5af470ead0b3a696df05df8317aaf0df943d8c7cd0d2d5e2787b467681cd1c1702744353aeac3c21ffa2daf1047ce052f741a64c9eca817510ca0b3752d6ec1c651335b428b7b7a8b16602b75863e24644948003c0363b875489e27eb9ac49ae5cf411895ff4bfc785785093de23c7cb1f4489193383a7cfa99335002e1a8f814083082fa6e393e8d038c2a0f70017dd5456d21a3bd73cbb20c9b4e328220ec065142e2d71118323dac0d80fc2f18da41b7391f70ab7c7b1df38d1ccbc1d8cf07677fc0c74fe766d12b119f4cbc6978cf51ca2e41726e6ac1f2a45ee583cc0b1eef4ccc2713e6516b4e38c20a321d722099f01ccb77f8e1eb566892e2abdd8443fa961a6817cde97634b3f14d416a4ca2715514ded8d86275fae5e5ccc70f4e770585cfbca4882b3abdec4f629138a3fe74a584e83c0ea1d572746733d97a80041a4c709f95187e16ef2afa44125c5e778b2801bdac8a863daf508f044fa7e160c068fc6e9f2360fa8ce19ccc11ccaf6285f58180849068da8e13035c138729d0c0d020c59cb6ead149a2019ca08bf5fafc2110030b71ad7af158cff8c937490b82855628a701f0fa2f265726b7ffe97bce702b62e1050ee6193b0eaee30eadb47b620583321d017b8a98c453bc096296c0725a60277e9046be87305c5b72b98ad1fdf6a28ce1b3abb24bfe35334ca1587a297b58e687639c5127076de5704148e99737f8eb72bc65fd32cee53069007c0ba1cc39abd604d6244863035d137affb749fea92544488a80682d22e8d1256536398b7334f64abe56458b7f9cff88404098d09ec393cb7aa347857e00c31d7b4a39726cdb30fbe28c5cef3a2ff6ee65aef466aa439fa32715e473955e0fd813592c67ec635469683759218748b38a5a2ae3d84f204fe1299de2bd61a783ee1ca9f77ca35d6c1e1131594962a5fe52d2df4c5001f2fd9848160e5eaa7b9ee55624921001ae98b317cbd8721c65e7a722e2e9693bb8eb0fa28213c5f5033bd296ac6dee2569a8490d5e361c49cf8f1a4b4ae4c63aca0bda911e4772236498c717ad7ec721c75df69cda4df052de9b4c1ec7ee801c6bece58c737e417c9f7f2bf478b2dee9bd9a4bf29f7ff1c60c426ea7db14df501b2c47ee0be5513a808baa749aba6602c272fb12dad84352edc1a8f0552c4cf8b6616b194339ee5f49e630ebf8403c54043af51980275c4d6685a6bfe0679d8284e4b6242c518796dd09f4efaed12b5d10fa8ce37b29eec1e6b214e9c1afb43bc4b83f07d4021fedb98d42a1fbb43aa1c5672e64a330df3479da1b566e6c0fce9d8ef03842b9a27b0d59e8fbc502fcedaa4b7ae2acc96c3afeb7f399c8c9efb1975553d29a26777d31f4b26eca50e5b51a3f9aaf69c50854d5a52c5a88153305e538d34a145f39a0a4a5db7e1d23f8cdd6409333da2163f7cf027a338a7fff9fcaa06c7b605a9d775020c5d143cc41ede5a9129daf4078cc5400056b2a1a52fa20442b64fe27c870296c2856b85c3611c9d8c02863907609e66504421a0aeb8785c5596550560a5df1c9dc619bc06d71663c819cf473cdc47e08132db0565b5aef76a0c5ebece694767f07d0f917c63cfb26b59778d9133467f6e8d6a447b37265a3e5dacfa44f72fdac2452f11b60fff7b6b307098369f1fc6e98487bb858a54dfa34b66cdc316b50b4d36ae036b58d19014d9ee7e1e63556e36f629a4790c9217ff46167d50044d5780f849faf40e892f86ef52927fe2c87d8e26fee235965c31cae9d454734b1ad27ea83e2ad0c9b01a3558d9e656c4fe469d4dc61df8734e6413c8b328bab3ecff1d88265821e5b88f677655bfe027f2eb05f07296e7edcb89fc8d4316373dc36968812d7c2e1325c6bab26ad922e9697fd909fc1d8960a9cd3a6e4f8bfe2ce1be1e794247df7cab039b5100ec63f501ad220945e84ce54ba796e02706a929c8793071305d4472e7bbcf99e966adaf5ae45e989529fe8a061a6eb01654d467a91a467bc9ea0fab81a4ee4afca74f652060fc38712d541eb691345bd64ba0e61fe93b7b8d3a1517f020e3630f675dd2dc05e1add7056682dd8eeebe262334bc45d79db9760dd03e2feb6e399c0f2eb6c407e63538093199c4bbb418d987377276902e46d6c8e0331769a2dcfb1bdd77f3655796a11414bc5bf23653ed95587250f6652a6afd85112eb067d17a4bcadac6e875f5f8ee53c4e53034a59a9dc782c74eee6b1767270d1ad4f914bdb916c34cf2f584413997a3aa7903805d6db9525a2e26dff147cf1615be67742c732a0af59080fc3e0f6c8c34200fce647de716bdb9a0c97becab09047615336b1f3063f99131e4fcdac2aad9145381f8ea0f368b32a254473a8d6c13783a0105c0984f2dee1aaf04e56d6c9455e271b3ab7205d2e2a8cf63656dc2fb6b6342d84e952328d0bfa54cdaa5596cc9628caac154958663e0c2404ce36a4f55eea3f13c9e1afb9e99a029d8877df1718c50db82808d38eaadd1f084ae12741e9003d46fed7be5ec9492cd8d915e8c0cae9f7b37c11994c2256df6d92fb42932d0a5666003cfb8e591e31caa2a87a4f329db046c6ae68458935604d0ee27c1b189bc036c2220dfdce59ae41b189edf4df2521455cf2c0c40f5f15bd1144564b86c3d613dd25435e7d859bba1080ffb5f15047b8ccacf1b787e68b3c6f09111067b084e705d902b01272617c6094302242ea71640378808be169bcd3873842d5f8f5e7d3852a8dd1d830462ab1169e31eee0e4ea478216527d5e42d91b352cbe7d4a7dae005a915a0106317fb77cea4cb9cd64a1df1c558608cad91b36b530fc4488a1f6e0e6a6a2ae75334e277c7bcd991604a1b8d5bc2dc3ccc19f57aee4c513f50a68cc632ec6794978444b2274eeabb24a01737f0ba029cb94d69601dc25de29eb236cd6e116b98779637ab4a4b6e1b4f4379623649d597926298c47b12dfab7caa2d8772d584e8705f654aacc1c547ae0169821158eeccea6ff5f0e7e3c9eaa7f9759a4f3b026caedb2d5d11d0c6e8f1b1c6913242a70d6f7cd653b33ed7433d998271b9a6128153323e124009613d39ff818bc48427e0578c43fe6293362eae75a2bcf9bc35789628db7f48452897f1a792f2286bb101ac1bad2800886576072d8feef4bff314abed4b1b5d2330db044aad4ac4c5ecde52ae7daaf8eea8bb87dfb1be3e954f107d89a9413bbec70358169b9edafd1f19f75f85848f89ecf0850cd0a63eeb55973a78d6b42355539d9905ccfa7b18400ab99d7156dd7721dda7b55767642e69caae3c891663ebb74a606854ee807a803bb173dee860788d9d68426f836c406e3e6cf3236f6a208b4099ec0711977f93ea421812a21f59df21efdd42c2b423ca7bc7308aad77c27fc94672cd7c5d361d8a3e9f1e14563c47ac3449c6aea00c357ea1b3451ecedd55c28e49984b91d3f142d4fef870b89243567b5b47160e8bc33702b711f77e4aeb1d19b4ac204aae996c56d00f0b43ed4d8914abba8ed9bd2a0c83ec53d0c1fae75fbec3f6a046d73c17cc760fe09a0815877718a1ee72c2305589692126dd8be72286f043f3489d88865395cc0cfeb37be1a735c2cf81d26ede50112dd89edbef94f5ff2048839059af54ffc053c4752601ecc3bc119ac10668c64564ab64a47af7ad17f74f14d7f4bd838c229a6f0ffca2d3c19b5800cdb12732e90dc6b461561f5b15c4bd93e4638fb37a5e6b4c439f528ec3338a7eef9a45b2faf63666ff28f7f18573ccaee615367847af74a27d92f7918f0952482bcb5e8557976b59156dfc3ed66eb8d478427b992b9608754368479139e8a1ef7b8a02256b20bac0b158c0c4967443f370a95655a23c9cd3881d4ec2351a276962fc3730d816d9f8f33a69eaec273143d7337872a6254bba507dec741e6ede203d6bf425646f65f1f9832f7a8b12cd245baac2228f215382383755f925cd801ede29a7efaf265f25e2d8e16fcc04630cd72dccb5b62a96e2d0248fd66e2e0c3a11afb03b87f2ab23e44b6117c6d8c37eb55e5f1b55ac1a80e613f750c41f45600493e4a7d0fa90a7c10e237b759ec51d4928fb3538b8890e1c4ebc406df133e7c3620bbc687f3b4112bcb2225820bfc469f552fd43dbb2007800633813d0a0f23cd261b471ef3b8e2ba22c210f8110b5e8abcb70e49306c9c79741fbf7bc9be877b1191ce23d0602a850c173e56b99d2ad2c005adbe81caafa200444f361f9f5fc2dd5375c2e733ece0737b053f33ff08f3e40df13422d17d6be2bdbf233cfa29455304098ec3efc5a8f6903dbc16b2c3aba0ee6657098a4a55089fdf633f976d683346ceb7f88836f68c37fd1b2895db085a04573c45f88c877c612c5e397ae3760e4cbb59354b4a644c8bbf9418c8e446e61c90830a9aa3e44deb3cab08afe8b56b4f2c09c0b9f922a8dc0ff81698b0f386f03adb03e4697f2a001eb2cfb682a000b72d99af9ac24b63b483573977d3e4a1def46ad0e19e34f2f4b30ead52128c8c8232d95ce58c271904c27a59598c73772630684aa248c8a85049cb346835b31ba4438771d77085b5ee5e60664d65704a955f840c9fe3471eb0c8f2a42121989275f4b23e00e7f440b771fab09c48b6dafebf058c5b10163834584850093dc7d4605f466f2c1bfeb1031540e0d5ec5b0a85eb54f1b2dec7ebe77174a7b2d5005c55bd2fc93c5d0497adcde1efdfc303e6bad8f63ef4506f2e68b9261f2a163394374a5eee86ab6d96cfa89dfe951cb89011fb6b38547cb9e950f7bd4688d6558d777987979377d4f9b32e0fee9727ab32a9530dac913e3b93c0cdace24be50e087a11a0eb28d3e80d1118b2693f78b0fd8ae259dc8d3f8629e2d384c3398bfd1d208cc2362e5e1096a185b1787252ab1b1bab92e353887b2523888efe1893783273c49613e949c9fda18d3dfe42c9af89179e96de549bc2f44d079184490be116babdf065f2672be24e6509ccf7455cf06b10abe53cad623348fee3cb1cbec6777e274a2c04637f364522d39f3f74facd28ddc551d249fca356b8bea4424ec0898932ad2c137d9c9477d6da0fcb0fdb26dd6515a85ebddf1306da0995dfe17490e728942a7dbebf38e72df951b17e93f81ed7eb4f0eef1335fb7e0a71eff2e628ddc3c24194dc0328f18bc4585aaecfd6ca455556bd7e79bd2dba466eee048ddbf57a90ca4078617b36e2833f490612fc1dd18852159b52bed82b125f8570221bfd8e79339a038baf91b30c52442d52ec7ffb1efe6f3796fe861bc88ad06918e9fdfef09aa0d5c1e724d132a5744184bf836525aaf43e7024e8129433e82b17ce361fa4b3081c91ac3e401aad2f8b70a8488095218e132b07ed3ba8a2367a18b0485671ae8ce0e40a6f69dcb5e783593ccb43daeddd1339a2d1b2c2f967d9741ef28b14e516622676999dfddc87f8b68e7e92a7561898db5e221f54918ee6b9b834234f8cfda6d486d033494616cd5f92046d88b2a790aaf793eace6ec8562411606dbb52cfc70b9a8f9f1627e482978752a2a9e6231883fa12bc3d9970b495cf1e1f199127f7ff5248e7d33f9e7f6441a5dfcd6581117d85e2e983867a35b9bcf48b18491355ba47e2eb5d8ae147eddf4fb6d53fd872f83878c158be843a1d7624c46299fd7ac2767028265ffca2d891f3fc9b768e41ed2495359dc703ab2e5ce0764e0945b9d16279aa5285186fe38690bb1ef52b60db0a26d1f4594bf1a932cdaa702aade11b515ed1e9a8d9d19f6d4547eee9bbe41fee70ae867b19bcb8009715ac4831d4ca220a8c0f79faf5ff61851f737c36f7def3e55149c64db4a88e4a25773a27cf1f049ccd2a7fa1fa0ed1e7aca1471fe9e32257873321294969db3192787f0a0ba58269dd9b913a3937a86607799f001e11ee9f6f95261b8ebab5fcd8f2a39984d3861dfdb06e2daeb9e224e8f11ada45e3e30539d70bdfce2bf434e30c5ad9104ac3767c28364030a0a79b877f4ba64bdc570038e35d4cb95edb1dfe5925f26a314faf6aa511baf2e9e458318444f641ec275b949171a4bbdc56d620c86c9313c078f6d48f98c933400f5122019434ab908dff83f77466ffdd8300fe188463acac5f144aa07c06528322c2fc72b97b2c23da1fd0252d320cebc260192b197693c0eac6005f4c4c64f3140b53e5bec2e809a83f23e048070b4417090c4235f15fa43632c58cf29407150bb1a319107d7ab74a177f199d1dbbd0ea37c9c29a7d71eb64a0a04c084506f8fdebf01e2546989ebeb789b718ba16fa248e78d855b37f10aac11b6b4ef8788811912b47560fb4e89a35467ba3c269ccd5a1e841dcae055a7661d390b86b4ce6b947ea4985e3ac319e2517d79b068d018cb3627e72a6e413f6a2a0b21eb1ea725aa347d4ea33b3371126700507d864b29ff764a6c11c06a9a3e06d0e4c3d665ed68b810e52bdd216175506d307850b2264e91cd55fde5bffe7a4dfb88370f56d31345045011b652a29df6558f3eeff05687ae7a2ace965fa04eba4b69748bc1959fff73891a97120ef224b360ad97fca16848abd6473919edf9c2f10d2b1bc9c838f9face2b9bdf1a30904660674f677327d4bda0907cb58753887bf8112720edbd01c955cfc7d232e62cc7b856c1f77bec3ca6e5b680894d4414e08f604dfda8404ab0fbb23d6d24f7d5d11a2222b729542e23582a8f16cf4ae84312550ed2ae87d7548e28f9aaa01d43d8b9bd242749f02e62843e63b83af7db50ecb0889bfd8ec4e77ca7227d73614f8872d8fe9126c48a1c09ab02a4d6714b3b373746de0e0355182c48ef0de6a48f65936b7da6b2c80d774e0e26b9cdf195ce6733189f36c9d724af4a1170841a398f5d3129d2a16a8e2e826631e84044ec1a6af103ebf17659355c8fb89b52ca57ec58297b0b3a0a48db4798bc2e454d6f9e7e423fc020d4266a8d1e23db560f5538041621fe867d0555dbebab6cb4819e032ffb3c6a30631f48e39fbd08421acea93e0052cbf88b1b825a8dd8937d6916cf9976c9a20bd6d76c88e2d340e86dba2d06b132d00571331b8b0c49f46e249f962510370a3e928cb44958d1e82e61e270ca394a244460e52db8ddf9a64be28a9b65a99ac81832e43bd198112e0fa1a531a26b06c293fc226832d835595b6a64e29a27186ace0582e7f5dc405e4ead1194255e508b5edd717695b97efa8995ed21c6f33e486c8e685bdc814cbb975848728dec309ca711f6fe33a1050f9bd06d23b079761250032c8c998be3acb54b36e3fc36859d9f984c03f394b3a8ea9e506ea7ad7a4738cff453221e8771c2809971871ec309b8e735b36cf9c148b4141459f381e8b1561152b596062bb14ac757bd9bcbcd95bb6610fa691de32bde5c87d92570da39dc1bdcb56230669386442581f729da2f46f22251a1b0fac2d53259ae86f7949d4df3a6fa412f333b00ef7a362f666346135cb7a03046665a206af6bbcb1643e390d58490eeb840ca3812ddd120762f49fde7043a77d7d531e168b8b633d3c2950baf188a2626d24d1fcf71515e91639635ceea33cc3fb486bca66374e1b7811e5aa3b85dbff62f377798e717bab0a84530bc7ab40cbf1f532d32f67e074f69ce2088202db14ca8419eb5ab4da4136083fb5253f1247a72c3992dc6c20cacdb67376af4605f6d1fe87bc170c2f5bde76d3ea4caf085f95aa4304df07154e3bf3bf8061342e74330ccc62d05091af6eea6e7bce855f1a8844be05b1677bc8e9e2bcc4fb87ae8dcdcb036bc79c747c8da0aa81632c12356d0bc85b66d11374ace2225ec6fa90eebceeaab0598759b833a40b2224290adb91c0ec9cf1d741d37b0d8f8b7d76f9e462868cff5a803763a4c11dcc63506b9e89e4bfaabee82fa7bfced753f32a76b66e3b1304a8b23764491c12ad9e52694dfca5ea864470696bf1eb71d47c9a4e9eac38871191a2ec0bb72d107a9f1ce93fecbb999a87cf0f9b0a245a54520b87914d7ffa048e3de0d00f6e1d77699d24c8a8dc944386f8e047fb08200e4f64cb74a843682152e602c3581aa71f4682d2f2b5370e46b7abddb62996cc2821b5b44962d141d8d516124c1af32ffcdb18c466ce10acba8b2803e1174c98db20d0e842760a7a1165b3145d036c2271dd6be423f36c2a4ef738f4349c9e0a9bc70cec4ce940a59e60418b1d1d938fdf462d1f176d12bf05140b78c4d500eef184e17968280f01889fc66d6f66ec00bd8fae7044b8d7a1273ee2a5860bb3f27f727c169dcf439715e79fd75b7f30a1740101650d906c212102819224c94afaebadbac2b9c5037951b2338754fb18e841a70c5b4a0960e36743824bade286f997854f82d228077d4454887be3e71722e469c738d05d9536c012b95c9678791e27d0d27a3f58d4651b96425e1c5c11b5f18f6cfc0cfc673bcae3964c83c8b0932c564e8e240c33d8526f8a7f56d4a9101dab88499ac5de0a3838b704837f98c9685bd65e15058870e90ac648a803023a747c4341faad899ac1e43814f35ac0e29a372429417b5b732cfcce4ff8e8b0bdf123778f4be788b5231dde93196d1a1ef3999b77bac415e7e0e0be1caeae9f8805dd9e1ac40dd88af01dfeb00c035d87d16937529c9c6afea4c977f862be5b4f7425757256dec6d4be9d42f299ca0edc03c71356a4c5eecfcde85ce9eacc6584d4e4b46a8e44b23dcd5a0ef4e1842521e632624aab6f36265eb85b4a00b3cc2b60119808e2a6989b7c718accea32e668b75899147af5434beefe7d1e9ad1b3aa04f97c69c26128c3756f5878ab3eb778d10288dca80d1372c38f40b8839397fc9218b5395a59de0562a10a2d5420c582107e95a63976da3ce6c91bc95194aa7d3d5f1e453d7a3305b81f208604ddd1362e17ee9a407a0591c013bc044fcd987bfba1b1852d046b377adb321ff5422a96659aab42b05a4fe87c447dc17574f993f95961da1080b77f194279c0fdde1c436dc4cbdc46c5937065380377fdfff856c20e80f3ddb3fde742f5d4ef7f241dfe60e4b81b33ab5b35ba305df291055d75f8518771d0af80ffa0b6d91fe9e7446e4453f1de911c2e66a7a8791e3a9007374e70cc38d39925fcccb97f95173b4ea390cfc015ab7308e13f5ca4ac32e41da70d9b1c6276fd1ec06a4b4837e0751fd1ea2dee72296786b98048149c465365dabe4f57d419985fe1edfef912372cbb47079f1e9d83d88d0e9e96c89a0d945b44c0694a8be06c4d9e25ad951e7d102c8d0b7def233dd05e2a5bddaebe85b8fb1312eae03feecaf17aa08c365f3c7707202b8b2e53dcc1fb119eb6588e47b71fc90c1372c09e2d2bb526c6feb60f77513c88a6e2c5405ed8e10b30fe0b5131e09761830744564058d0c22103a048bb6baf36f6a9694a75d3bc9a8a5eca1bd3ebe5600ad34a353e772e25794f1f9b08633955f88b0c172bbd1fa3378461a58a97a8538b566b072e8d9674c47353017676130833601464dba946d0e61da5887246d05f6ea8c02eae05feed6be2be87691ffaebb41d2c0c218fc4dc919eaf0313c705363063823c6394b2ee1191ed5059515511978727280d0a50a48e250b60226dc091a20b08978a58e5ae88ed00f0203b337641b19f1d8d60c40427746c1e9b500e2adcb8c3353685b2f7f4aee8d2d2bc4459623d5ca416d27efc4581a229862397a2002461d0e3184a8ca9da22e4ce811fb5e51321cd5014ffead60a10d085acd736cdc0733478132b3037db9b6a87cbe9d9f107eaed19f13ce2fa94fe8f2a50df0fd1d5351fea5b05cf74de1478f7ed17eef2ad6e319f0dc7d54c3389b56606330358f349416404ab68906e8f7d0d4a466194a6b92b1c97a56773d700bf8f73f21e7271d83d75c85e42e9b0d2e42c3443e9b4aa1f3455207b3c7798461de8421e4dc09a59e6f1e369670bdc5553d0715db295ef7f6a2aebcaac7d40d0cd32c1d01514bc3746cf42e6e6cea82471f044d6a28fee19b3b43a0b0d11b8c0de897a1c4779144a743648fc053c96481c8ff1c5a34ae0071a58425588e530c33ee746372f27a8a3fcd61f362ecd9e3ea1e6f8ec69a8e59fa1b582a707fce5046fc0da6625bdadd41c43705351173d4678a527160dfbe848bfa03efaf7893752befec51d3bec0ddb14b0034752d115bf07aa2e87c3790f11546b0eb431f144b1fe58f8b321d58d52063ad90ebc5f4ab3cddcb677c0e88af862477a5a27233e1f8c12dbceceb8d026e77a11373ec10b054aa3c3300833265925e05e5333e9cfc65ab8a3e54918d1798d1d7cc6fcc49e97c5900b675e8c45e39e2caf07ea86770978ddc210dec0a815e229dc04e2403300b686c3e574490dee043bbacc6a81396b796bfb483cd5c3bf8fed77242c5fe23a7d7dcf321d143199a369ec13489c95dd2bb3aca58786740555de1620d148b6a5bac8017697eff871217f02df76bf4b08b31d4ea5a2557d8361cedf9d18c0114a67036e29e6d367ab46252e2fdc127211ef5b5e397832a57ea7a02ae8d3995c267b1789123766111b23a8c4b58d4551da144e2654e300d11999b845b1b3d716c2a7e5a75eba3811010e51aec8c3d5627832378a70b42828fef094b17574e27d3b9ee931195b27229494d5267a76fbcc379d807390dc15e51552e4afb43366e0a71999ea318960db2cd810da0232bfe1de66ef780132a949f0ddacbafe25230a8f11de29f52f61aeb7632efcc4e09ea9ad236f78e2deeca4061c337b3bd14ea77329af0f78aad1543003632a431f66dd33a418639abf0cc1560006ca20e8258cbf437d33bc348bd3ebc090a0199b0adb144e686b74e0bc0e8e08ea503c6b477c2b89b66e8058b78f1054793a45c4c69aa6e42e3270b8a30f924cbf855ad59d1243a74c77429ec872f633354f495b1b34c7cb341b919aeeb0eecfe596751331ba658f4cef0791708e4e6344dd8121bf9a1175dc963f32caa1dd5cd3533619625b00f4e9f468468c4342652991af3985518b3a5f666c9ac8ac39ec499cbb3ed190ccb3e4071ce74d95dfebd9eea088e3bf134b87ffd6a4b5c2c3ad5ec0e36350905f77e4b3aa080276f7c47cf6547336a6e5a0549f9221ab923aad3372c9781cadd2352a9502008955573e5f847441434378ebf6939fc9cf05f57f6fd37a97429b475fd5ccaf13a21daaf312625948f15b0225c402c084cc24fda5aa55311ca642b634961632e78c68045eb0ecf92589b3e7e1897cfb615e8f4397f6f2911cffc77f604950b12da2c71227318c8fbf898b3cc73d8e00bd8709546ca01bfe9e6d1a6dd7db795ecaa2cd3f351e6e59049af5536af532f81eaea4d06037ee095039339980b7599f6e6204967ae7a85effb224672539142b2935ece20d62aa660317c72f98683da76c1097201d8e6f29e13033ac7aab7c6818f18ccd652acd62a0e65e907cccec63d227f9d14c28be654679fc13245ff70459144f2586ccc27084a1f44fc3eb5dddc23731bde81ea3967234f987c75374ea9bc575b417dd39f3b2c252cc24054122893c4d173d00436644687b670d25aad67284bdd89c07099800d98dbe7bce47a7f75a101f78d4d1dd526412058b73318a763a6b298d5322cfcda8edb59a027df177fb966743a29d340e8b0a90dc3100ccced594ae44190fb2896b4cda15be2f7f89c401af57dfec29e094c2cb9598964d0ba69483eec7fae8dd6365ba0646b71b06ac1e386a0d6688cd81fe6502152aabeb467010da1a191144304a3e3b34603c9bdbfe16d118e1dc2f977fd7d8521b8c337befa9991f625691b5bc1cc46d22eed1a2bb444e3940876e3d4626635a4676593ca3923d0299ed6b1a74ab09e02773d3da1928b4c3f599663328b2ba38138d18ec76b8cd7688cc0e9b2d3fe3f2f23ca9380f80925a62906179ebca2887023e2df87306400cc42f5bd20e14b33efde50c0cad0716d17ad52f41de7e85d5b0b71b40012710e4fa8c7ba3ed8ba8805c9561bd74be6c6168ca8c1e643f9e3947ebf2da4f220a25984148322cbf2cd5dc3772dd5c863f6ed8f2b99e2345b9b1cba50de8bfc04c7d782154d183a3b9918164c361f7d8968cdff346d871e514ff3205151735bfb67e2722a626124cd04a8b795a85cc3f0a97c516780fd35af877f99c082824bc7d3f7818f9e4202b52cfccb7eaa743c11eb9a0ff8e9957da6150283f061e8469f3fc4754df56282dbb2bf2c9396502cac856f170b1491695d8a1a358a54455ca8437a268feb7924ec3aa40cc77b6ebcdc704a907c4135e724be5b3b3fb2e5f76c5bbbbbed1a4e0bdaefbe074d9b430e36cc743e010435c0754cf0be7849cca91c68106e33b003a0b4fcf43236ee6e47cb59b11317c36594a7134fd0d7d88cb2c92ef2ce8aeebae0561478fcd69ebb0199ff4c1fda3cc6e37fb6ff821c96f660e8d461d90eaa7e743deb52efb389f3c88a46bf3f74fcd4e24c1d6f0d16a98e8a0b66114d36ce82de78ba05ac49c3819042deda3788e836133df2d35e92b0daa838561c16a3d6b26a42bf32a85b11e54adb198e09abb8adda092523dddf3a7b6535f74fc3745690ad33bb5a87956ef20c1b6c33b8b8775e92d869143d584d169fdf01044d5b7261b18172a91e338ae766ecf271d4930568c86ab436a697e01131f81083ae4d65af2ae62ed27513464ddd81e8ad73a995c9ad53c3149577601e08c8250314f6fb144e123dfc561339aaab39320975652f99eaed93692da12f914dae232e9f9b165d21a06ebf3fcc2e053b33d5f3359d2f8a523ae964ee8e208fb3f5533a04902cf161789830c5dca437f68a7b4c18d9c4c333a5ccfffa21fd062c46e19281edc7975a8c14febcaf4c10665cf2107c4f72073aed05616883017c94a75c10f4bf048342943e9cbd4f8f45bffdad9e0e569027200ca65144e65fa020513ac1098a15afff3bf781c73406384c6341839108cbcda90e80c145e3b9bf8d705142d575bf9066c92675292200855c3ceadc7ae6a1919f3eab5b5993a9f968ab5fa74366dc1351d032e7f11aff68a426034b1133e75ec7de022b7c423b4cf25b84f3368ddbe926fa8a617f2b3bf23548916ee5f5442c13fea44e9690716e6672c32147203b28b13f1d0202471d037ebbb9faca93b401809475be471abe7ab97285bd6b4413375335e20700c0c524ef7b4655b31a054f5f319a1fd94f8c690c3e150b104f8d0f0333526f2ff3852944ed24c70b855d92040001a9c2f4b65b673624c0530967ca9a32e99e3694649f84aae73c816bb24e11cdd7177bde0eb582d2474c7ab8717e5d38a82326ac9d12350545974bd65052d9030fb480edd806a9e674b1ca4c116fc9fa967dbdc11d205d80988b6221574d9950e264cee74dcafbbe89ec613ec28a11483afcccd308584d9e5d143e313bb879b14ad52ed01aeba4a6c5edb94d9039d85823ab46c634f04e8bb7710488420a984585a81f92de21bf9e4d21e7939d7b05d9604ed0a2ac46a78aecb947250940901698be365eaf1effd6a887ce0ca057d2f0086fe8952e34998ee7f9b47c00b2406eca883bdf968367f1445ed60b61996301b477f0e1ae2e4e0fb5e5563ae93ac207246e66840eaf9139ef57d3d93ee08d1900e1ad19d13bd10708194efd5daece8ff83f7879895655bf5e653411aafd9c600e6e14d473b385fbb7e720ac479f0f7657be9b89ebf0d8b3a90daf2ba8a338d602796bb321d2c3093cbe048867760f06992c43160df1547fcd56ee1a5463c0cf874f200aa01b85603412c3aed0d8c61bd9f5737f3bd013c5293e06b45afbb88c489fd79f648d4ad2e3e21b4d6a5e008d776132d7b7e878f201c1ad2b7df6241578f28f1919a1a6eeadc9c75d71980c8e9457ff7d34201805fe7481df129ae2554b3ce74f1e1f15c09e9a7b73f46780ed99359a525b6228ebacb0d02e822469187a75d56e05c10a3de3948fb657a4f7ffb0b2787349ebda40fb43b733cc5b7f04c3486e1215a4668d4f232cf1d111167a49d4a62c0f626134bfc085c3006cc55367cf7bb4c88baf80d72c1e3fefa7404497f2009131c3ac093053ba3bc5152c40ab5d04a74df4c6f416f5b4b80f28c98811cdf969281b2f0e6b2a0c47d42cb37f575f0d8807f5fb77ca8c4b3ae60386daf1ae5e9de0102accef704bf47a6609513a442d930cf3a72455d25f3cfa931057b8a3cbe43a4054f420d5172f1a78e7b5a47cbe75a623bf8f3d02598a299e2315a8bbc1b68b64dfc3c7a1afe8a9854079ea85c552d6defea6acd75f88212cd4044860918d1b5329a49066590530e0a188e506dc73ccc1a386f03ceaa2e5b19ea4e4b88a4ca8c5a1608a302a714e145cf973fcb2bc70a1d1a1fc8bdc744e8482930b1f2660e0f4ae96ab522a95b3484fcbf5f5fd56dd4c1714e65e622b889fa5150fa3165aea029bf24abe1ac4a6ec741cd061d934e01fb89567f0e0b091bd3a1688a07836de35d463a3eb5c1c80b68009ece9fdf77d65a5575e0ca532bdbca30ed2628e2b48020ab25752bfca2ff232e938e052846b8a51ff4d902ea734560a808ad9bce1a9e5f0c3e9713090b41f36e923bbb104931fc715da7b3b157324e959145dba35f610b777416a9aeaa5e1a15425a2ec7094acad805091f47dc7f93ebd1883213317e373242d246947ac3b7df5340a6ab4425226071b86dd76d58666090103dac969331cbbdb097d340621da134f62e76c233daa7750c36ab21551cc047daa09dd3d6cc504614f8c9a0453648ef7cf34d9f1e754bf22b65ef052f9fc4000627fae577541ab36cb34aa8d8f7d69b1fbfbf71a2e44266eb050112bd2c58e4041ca556f6e5d90b55e404b9a0f41fb85ede306432ef8ef3059ff5296c66174bab36ee6b3394d77a1eedd8bb955088c82f9a38ac1fcdcd10cad500d5890ec1c2cadeb2443d60d2b9cebd0c38c4c4bdc38bcd7c81e26680dc68e21a28d3e962519ee812eb48ac81bae6ad9dd47b5f5813c27a6249a01747ecae746b4260c51024858ef438edf80e4c5cdd67f34e68b78a79d8d1fa7d3c299a55862246686f370455390fbce6997492ffc669832d9b8909720e7d76cc0d32a89796cc465030997df40997b6565eb07212b042f328a1fa5ad8bf2be448e739bcdaa162d8d68caf3a8015a01b61fa53b3436e331b9a5899b8ee17de83c38b3b26b7566a22041f9447989bde61c32f33ce28714636287cd4263a879c698502e04817b2c7faf2497daeb83778b628c44900d92a9745988050f58560c5b4e33cbbbd0f83831a46db39ade089203494fc9ee79d6e0d9e09df28e20cef6eb89085f0d40d36f6aba3fc923f6604d310577447b4d7f9ad1ce47a4366feb5b0a2797df525a3b1ecd7fde671119da83fcc05578ee968797b40acfbfc47f1f3d523ce70ccfccdc3304d9531f64f2ab03ae16deb7e5c47345a015d400198c1d3e9d8db4bc1de99e41ea7e66eb7271ec901198bd66852c9a182d7e2dcdcb5897818416b1319627f55f1c7ae09699499481e54947725084fbb6fd6b16f637a809b1f5859728479fbf5e18ad25d10c7193254b242e0d6d69ab759a16ad00f1c498814fbc0d85bf90bbe0cf4a1af0d9a4f56c5f930f388edcc8ea3a4ec18fd1fbfc47f25357c889dc83446c5a59981de1167edff9cde060c10696d5f2f2c8e61d7f6d7113d8bb1b89217dd937bbc5e9ebe72dc12abba2aa5fc4e10b7a20cf7eff04d89a7c66f9fe412c3c90112cee28c9428cf3ee92713fe3b5fe4e6e25ac89fb2d9759406e3b22d27a0f026cd23809474ff60f8245b992dc1c5ce8ece8434d6d7f6811c6e29e96af6354fca085032074f760a01692bcefc090be6626bc2a2637a1e470a80141776e8d225158943083226ccdc34f713c546657374fe20879b03daa67e035e5e25c484f901714dbf68a6ab91966c74fb27f8a4d7ed7f84ff63cea7c06a4efe67c8a9da1f2dea72a2ecc256b9ff48818a7084d5f70ddd87735e8aa33237f0ed2ebf26d9efa42b9fe512073415275950ff130b290b445016b7c026e9d7228fce52153df91d2684684b43d38f5502a627ce2c02296c9698b0691b82bca8425d7a885dfbe9a9c0f1cf09b779a0c1a785624b368e9fcfd439e58c5165aa722b5f389686ef6daf408612544bb85bfd6985095ae06b7e31496f92edd907aac12485554bf9c1c3859200cacfec327ec6dc31f40f1b71f43995d6cf5a6328a9716a839718625e4ab98b267f7eacd741b51d10963bec81bc621875ee4617e8f3934558cfa7b87a824ac7894fc00c4a395351a45c4b870863207f8477f4bc714539bd1729d1920e8f59a5304196803ae862b31813c88c6a0614fb43763ee87a580ba8cc14cf815c4d84c3901495453d632c56a5c11b67d8ec8ce1c4e650ccd533755de85c307c8847c6bbc25aee39d1e3b4098ed1fcad0d890c2ff42013e4f33d1ac0a7ad743fa11a7b50be165b64bcbf1e22f3ad7f270ea40657cdc17a13edfa90e99959bd1a379e565aff8af3ae1eabeff2ec202c27393f43f887afd3015f34a595e7c52ae156ecc8ebd0165e7a1ca94505ef464c8fe88253a3b8fd8a1fd91a84a7d09315a2cc8c88eb8e8bcde8a7e749c45b6211ae0e203aae51f50d97dde6da946cbf02862ff7ffdcc6d65b3eb5c02d907dff8055a9c5ee759448a5e1d82599b53843222e3b1c0b4aea3e84941802a3a95bc508927161c23c1ab7a5bb12caaba6cacb09b362203d7e6726310bc4ad5e11c8f104b26954285c9178109d10c9bbcd0ec41f7adb779f5fe178c0608dbab216c0b4b2722c090d3f9dd57a3b657f0ba35a5ab2e8b7ab1b7979f0850829dec80f5dab895f524a6227d6752367ac7937270674417bd9ac2a6236f154b05b84ff88d17037591b2bb53c7b2ebec24184109057a668722e710feb0bf7b65d99dfb158bd4dd04863cec6009cfa4ac948c422c0558b2c7805a9ca3e71ad829267f89fbd095f21856fb0cba8a65332189683d9a873021fabc8be1a922df303f74d519688bd39ace340c1634a32d008f7c750e6ab565e76073394afd56c3dc546eda7840c17b6a4fcdee1a4d3f75f5061c80b6a0e10d27afbac62d1f4b4db934140d5347ae6c8a6a5b05d3c68e9e9dd54bda56e85f7372cdbcb861107e59b17500b179f7d2fde20eab1bb2d9bb9e9147a6d83d7ad7961ebcd13a5acdfd969388364e37b2f67416f08bda90824f7f371d4c54038e9fa8657df498dafd570839192c2eb4b3ec904be0e553e5ac35ded047af2ab5df4a8f5931bdc735bac186fbfd65e16a7fc800c8a8e1798b3b2a6dd9c9b9bc131fe867a6cd86da1e888d3d7c2f0ebaacb652e95a6b89a71d32b671b5d4536fde59a5fd241b0a8e09f1082bba825d4f368bfb6497d6da16dd80ca44b07dd52c30b374563de382bf1a54e5a4781f91fcd33f3cfddeb684682a3574d44aa461e0ebf90826197767f1a8d561d1f5ec6861166955353cae70208e101c22dae380fc38f0005aa6fad5c84c8e862cf16ed9e016e09b010d1dc92751c841c575aa94f50f56608f691d1e90815be30602cdb0204c99124a7d856249a56fa96536cfe3c5d6088698202b39b6d5f36e023efe6b8d6ed1e36bd1f34eb65dce7f40c44113680dcaa6fb349e954cab2055972f535ed56c4268b68035e98137a610a835add6600a7f7474fe9d5689883fbd911cd89a6c7052cfef1fe9d424b22848823d288632c7fb0172d5120b47f86cd4909c49c409f26e27cf1bb147fb771f5fc8a94c0fe7bcaa2395f539eb83fafab0877f950527de057a8243136fc45689bf083250698cd0a3853630e3dee10415f73e0422fa85c9ceea2a23cfed25c8ad65e495f84cb24f0470df6ec2e92693b380d15e553aa635564e5a2eb53a260ae9bf900b8572eefc99bace057f1ebc4741ad308f179652e5fc779bba4822b3c7b25557fee6005a2c33c177d66ae4319c93679d8748c41341dd93eac9b6cb1b51a14c171de42a13f02542ff410b2fb821196330a36473b9cb483d38a5eb6894646b38c0b8fb2fb954e0f0c2ebdd41a6515f8b566cd134d7a6cfa6e946d128b4862a81d8722918f2ad932ecd4b03bef6ed51234eb38cb1432704155695001af9ea5f11fa3bdf8ae6f24ccb6411b902c6f1d59babf437f49a796c214b6d6f6b43ec7deb52a2d012c42a0cea7e55ce5cd811a2bbd50fba59b95654b7897ce5df5515f8bc237069f93ce9ac22449d7b1c12cd764c874df1e645c8708045ec415857b91b83419a09a604dbb1c496ac486cfe0c182452b62f5724fd8783a7dbdb0f561c33dc977e6016546245ae8528eaae4b56aa0919b97b34022e8f0b4caa8785c32bb5ada372819669430183da09413d4a8955aa9421cfafce91f089c5120b13f0c6835ef5a18b5805614f2e8edfe59871324f8acd5ecebc3a62eb0a16150ae497977a02b43cffeced88758550e1e98adc6aaea1a519b2293ef03fff37b1b654527ad07a18f619e66a509e8d02d2b731c748ab1e501b5d0995c899929671cdbf23770dbdf1d2d421c90071623cb23eb415e02b99d583d8ca3ee8698294c5a86aee4997281f485d4fdb930afac842d02bccd2c0dd8f50b0c6fc00236d032c758dfa7a161117a9234675c0690b24575d5997717358ae32eadfd7cd09deaf2dd06c7507366a65e6d8adf116a02c03f8f9e5426c0712b7b5901399770beed2275c19ed997fcf249cf547ac2d5ad6cc55d3414212922400b60c7752094c2e9de612b1d75256f98770a4e486dd905a1a8b01b02b2389863527c732d5f45eb5b60f61ed0b21520e54c229ae658e75043a2fdfb0372fab0dd62acd7b38a75b94b45c546046aa7d2664a722ffb293eb87b0b8d0c7c6876395b676706c582d40ada5a56602a6c338c4253b3569da98209fb2afa58cd8bf8b467e0b1ee153e853e08abbaf99327ac1fc183d0cebce1d1635abb0fd1395bcac18273be52b0c527ccad512881250aba58853ffb79583032ec4ba3e5bfdc65521c784ff6886aad1b5daf13de6184d2d9ffaffa4ce0a73280ca03066cee05986bc9eb6a035a719a02259ba370121d9879ce9dbb0e54d1b155fba162f6169e02cd5bab5abb4c487bc6116017e0195883cf8f0b7e33e5d7bb86112a4dfaf24125cd982ce38399de8f01fde09fcca0c529f2008fa6ac2e699342867cb0e57f3493bcb7b2082ded073e614740dc5294c133f32d91d5fa037feba09f5ad862111037749cb1eca32f7234aded6b89eeb420cde466c2e9233363779b9fe1ff71fdf6241051c1092e85f38016bc0ab5bb07160fa7ab34eaeffce5595e0a22e7814f5ace2c7ed9120085bb36de8dffa057be598e6eb4133a4e4be6e3a4b0797dcdb82cbd969bd12585b3addc26315afd94d73207230bea769e10d1ab5fa324dd4beb5cd8c5ddb56dd0dfbc069910b460f5bfcc7210e6f070800dff69fae08f6ded4a86589af39f48dbbabac756ce82f76944a43f86c97c4fa44732420b7b36f0ee36268559ef4d9466730b428a07150dfa384abcecb34e5ddbb5ec93122814499608752cf2bdbc3fa13fb2f88eded8057964ffb3cb719846cfbf78e04968f383d8babbf1e4ac7c718faac3701ff400d32c4739312054c4dcfbafb20686af8c371b5b3f2348627cb4293592d1d5c3b37e3bf41b85ed604023b5237b148d3148f6a383851dd51b60c54f65133466fdcb3aa55031209a2aef9de40ce743b2e4afbe9b3b4e65d4ffd9ab73df4b09bcf50280ee67b2cfac9c39c231258241bc55898e3852fe0a740edd70ef2f7d0d640cbb523a5daf8d37c7ee8383ca57eb49780aeeea31a10187c85fc27b315023f1bf36aac8888483ae6e0eff4d172c282feefb888406a6dc70840954499b578ae0d87cafeeeea56abefba560222b6367c38d8e327e640906bc4b9ffaf0885aaa66bff4fc1488b3de813f9d75e9d72a3ee3534941df21b38c734d8f1a5f171d374796fe6744f137ded06b6c0299f3dd66b32c6df48991ec8aae680f088b68e726668a16fcf92347b7b343f73c2efac20c6ef101099e0f4c4847cd0d96832cfcb0df56c3ee666e1fe2e577ae4361deed71ca419266033cb5882cea15bd999646496d9949d287d34b429aae01e0203aed3f8ec8f53758102d13fbe1f7a1830bb8829860b482ab203ab4a7792be1a1b49f17859917e270f9c2109a00e0e850ac2c762d15bef3dda61d8f603dc765172f6948e5d9ec1ff60d35faa5b7442655c3274d49216b7df964293711ee38cff59401f4ac07eea53edff45d0a64815fd0401269c5992b9ef7e0a19c5cd1a580531a77c37d7c3fea87da3b0f8fae0dcf459023707b1fe82bc2315093be688c918debb98ca204fb2e5697d24f328debe0f680d4847a5c892ddbc21e86484dfe163d558e4db0fc4f154179646f1ab7b36322499bb367a92dddd84269db4b0ea3473c3e1d6341a14597865795f271e4395b846e3382dc68c4db61042f5a4add86d74379b662c5c64bdaaf241e0fc70507e1dcb2d29e462d33d182c612072b271cac4e63c91536364a49262aad8ccca8568f04d853d6be68cff8704b73262146a9bcf63dada31f2cdc864101580362c48201cc3cfe82c96ef5847ad54a39d2098c63c621fc411cf2d99f6d65db0fd3ea0447d3e98e20206fbff0f4604d09728fd466fe2a559ddef45b8a0d854677bf0e4c9c8c829a45ad5a9219e4e0b7707833fa853dedda60a95517cf170f4469eb96775c27a10ee693c9be00af19e3f6f83698c7e51973c982534702fbabf157bb6f465c3580544735bb8cff64ca42e36aa7f2a9bb6c0b119de23d3ab503a3e6def5a8990a89e6c1a7e2578840dce31508fc6ca2954551dd4a4b116e53d154dc7759b2381bbdbf1c8c4e3d622d4479776495f4b8ef2c79eea942762793580761336d04cf566c8a322e6e3db2742e796f053379d6ed56fd49a121810e98d090fe4a1556f1ca501158afaeeeb3aab49c5eb34274761129f65897a9f2e299f9a11349344cc0b18da1eb583b65f8fba5ca0646681e2eb9e127a1ebe4a010671699b570bd4f624693a5951229104ef948f089f43419bd1af5d40f27ecd7d55e32b6f7cafcc56ccbe1ea4b8abe599944151c02efd7fb5b143cb69497bb2cff9b97373ba24d2a56fc2bfa5b5910a54d66390f18fd5d305f6f2f1a8a15df9d4b336de32b4ade876dcf00a4058ce6b5ea08fa7a64f28c474115fdf384e41259fc52aad59c7ab47aae62e0b22dac51d52bfc28051c4fd8a810edb5156247e97f764aa723acde53bf95edc473a93d09fc2faccc145562df3bc6d27f0c2378d32d6fdcbb34c6bb6967059dfed3afe5e75b2ffb082395850404c545bd043a71829eb441dcaa8f383e9edc361cb1acde9681b1b4866cb6f07b46a0a55e82079888d191ab8d68ed52397a54c3bc4960d20c06ac4b2ecfb8d5472e4c94b33a7879eaef63a214dde9d0b979f98416aab5d0565fef75f7eac770b3d9bdc5c71a97df38d4dc55a77a08771a037caf8d11e30f20bad61b5fd26de9abc050b2e67fce266c59c89d4577d9cbe559c575e91f520a8aa3f57a2a49707b0d2ab5aff6cf838d400972d24794365f4f25131c813fb59ec28f2a2a7928d0777c1ac1cc6edfb269fc9a31eaf8e642ae8f3b038121340075a0ff41d1eb77d8779c8394c2b30e5b6e8c15b54b04477c475216206941397db5e1dec1ac6b2401f5e36173d87649ca17f950fe34341b6fc0b77e800993517a5931b72a0e9e2913d0ad5209a347c8aabddd6405e62237f7d345c16c3548c9bf1e2f393739b0790a0e803c2b3d4bf1a11c4b6260bf63fd452f9bf92d28d6f42c32f154ef2dcdd415e56ca7e1e5f733e0349052ae373f8dafa49be59a678eaef546e6c19beb4950e7d78aa6aa11618ec3368336c66a2c95d06c2993bfd7c7fcaee60c95c368043411cfc02b074bd059c72681c3e3e789c4b4973fe5a84dff5bdca6a59f4c2094b8b2c835c0dd0a225cc58a836a6af3c76359b95b4afcd89cbf7082bffb2ee2d4a80b8bf1553c963937c06a46be0dd5360d1273e2485966a326db4d1b1da51193818bd7f7fe78279c8b3ee453fdadc60437a4f804dfc5d283654d760bba54c694530922ba043aaeec0f8b3d41eb793a77cae179a22874b20c4a89f4ba1bf91b98807e378847546d638d91410a84d6fd8278f2b0e846e65ebd4a99325bfc98068aac13654399ed65dbb67e84e79a7e4af72636ccd784d17e4e359d6531fd8509c91e808434975c094bdb82ab0a383c313d31412d29e93086636ff7bf2e9f40ad38be671c0ddc20e43686419b128d45898a3b15bce3a0443c231a6ee5fd69a5487878b5f5673e37f69050960ba044161977debcfd0bf38a921249e6220683b0ab4c18d5d75654ae4443fc8547628a4cb42a3990d00221abdcf71fa5a0b64bba940b503ee4c75fcccee7fc544026b8d4b5def342cd0c969b6a70d113ef5d6f28ca032ea7126d5138af5c9aad04cefbdbb820b5cac53873ae99d80fef9463b2ef42e45a1a104303e798553245933afab502a2151f88977747c9f8920b935c7afe011f36c95899b3ca4fd06657b30df029bccd47a74cd675270428c9aa68cb12abfadf3f45d656065af09cda19dada5215ad71de21fc3e5e5f4ff99d4c23a4747e2f32fc42ccf0b21189b2d918b49319a1e5979b6c46dd49010f3def8c7160023cd153fce0ccb4a6ec35b66f322a691b3bc8e08c8b846118a9655a0761e8b5dcf4d6719e52a3ea65edc738e07108fd6e88ebc670f2e7e6e5f6d35c880e11b42cf6779ea41ae13559602bdc0139a8be16788fd6c2513c50970251c2f8ff3f89c9b0eb842fe20f4ffd1c8bc8ee17f2d069217f289c78342d16edfa271dc220eb921b014e89c6851805b8300303ce4ae0d81d8783e59c996dae3822c25e85d433b418f872c804ec95a9766b4fa764ab1727d17dbe560eb0d0aa94440acf4f7dedf50ee7100729820f37af58fb3e5a22fb28623172b05d0636a707d3ec08afd3b5396092bee1eb1583e0dc0712c6267f55ece70329c99ecbb7f3dd06903f2984616d0e141cb93e0a105ee71447ac8c72c3d3c8bbd270b86aed2e5a07e93bd86703baed63d33c6142ca3cf3007c47be6d50bead8f3ca74818dea96da52a23f9a0fed5611e8ce103a1880f64a134ede2439caa82c215a8cc442f04f06388f0c52581adfbe6702ac230c81bc35f9eddc7fa6377628371831266a3d63b119d9827c665cd71abbdb3a042bfd0edc8e8460fc13040e70bcc5e7ee882dfb2392863fec0521be4957023f7674d1ef3c0975dc236da857ab7fea53e984a190676f4ea73c2d6d8903f9f7a314cca0f4593b51c690a525f358b860bf02bdc7759d62e05fa65c151f0ef03131a32b57c675541c178be0f9b2707f939a7b65e54ba5d32ad5e3521edf876f711ac7c194a4a2b796cab43f731dd6c975f5a709108a131360b525ca141902ad48724fffbbdea0c6fe63c62a28ec9a4ef206405500ff7764ca75172f8f906bd8e9b20dd5fb5b0dba93655b22c590110378a940876948f49a33caf383b8ffca3634353ea4d514e0e0f0dbce6f90aaaf1652da5b308b5a2242c231a9e1b790c9e972bb80a34c973ae108d02744fc92c098ad3cf91e05fc4a8db3670e4904660d2e68e860ba228586d421d7151860d1e7318a95c69a4f49ec8646f126b63815641a0fc66e154514e458b483fd5e7aa7739b564fd8519c32903a829577a7c824bd29f53fe7f4d08de8f178f014dfab867fbea4f959994efaf2d23ddf6a8b0992f4192ca4ec77e7cfac5240399040f694e6a434c4cee3abb879e6f69ec5c5614e6de99b294f66ddca2b6b6cffdda5a72d06a69007e28edfd8f86b8f642b3f12be7bfdeb66e53a3205f15201e9f35e455f507998bcc76aef9aca110209b3b0fb39a0bb51d774f01f19176b4e1eab09b86dae18a4002d9dc89235f178288b1df58d911f525d2a538334965121cc2cb1f34db39c5a97b8c11e2623d8f7a0ae2cc57a1718ebbca2d85297e27f4da56ebe562a9e7cf4651b6a711326a3f814a9549434b5b4a81b8340d818dbae145dbfcbc2e22cdc63f6b05f2ee9d061ed6c6b0bbae5236ee186a17f67a1075aa52d63ad02883ed1c8f7af48918a5c3f9952306c1b289d13b46fbdb75baf27fc7553404169e345cbaabaea9293af9e6996b67005ee8ff5266929c83db9d410dfbc909bd7b4ee8b86abb61fc6f96350702c4be8c0951f87b1886cbdf0f135dd98a5a8e4462b564235cb05da9f4d86a4d6cf07aa1781cfebdb4cd40b63ef0c6a902ee692fc92ff65c8e0e5e19a5e4b2ed140a744045b0d88d218628d98b4272623ca95119d258071829c383dc4b4ba176a4428f764a638e5c042f90f5b9f673046f888bc04d9a2df3d4603c0ff3b2fa885604ff5bfda26dcedcee26d678f42c6bc0e93ecbb63e47d116cf22c1e50d91a81d822bc9b66a08f42b14936f5ff42023e4f0f143032fa8e3ce1d3e5663c3cc565656d76dd8fe236c6c9bde55a6e84ea233c5aeb431a4abd84c0172c7733af8deac93f71df061cb8d817b4aed03e3acf7dd555ad08fb6ddd6fa054f198bedfb1bd6f6aa03125f312368f04d293ae29f99f09e463b29eb48003cbc537f3a54addd0b00633363d009b0c916c87ec482ca88a2d71441925a157f415c802365dc58fa14cee370c145002b04a0976d8c2af61c2c26a1c5af569646feb5fd7a312f7d3b7ce41527a9be233b322e1bc5b17348f7329bfcd67b579fde62eb0cc9a98db45d558e27dcb452cfe72ec5c8bba3cd26cfb1e65db9f0eee9f8dec852cff8342698e58f9e3cfd9978d5bd1c5491dd1b1473012d505b4161b92682dcda329044f50482d71bf7381feb672becd5161ab8f0c03688df77a0c075624dd19033b2bf991d4a877519ee93312f1ab093718533e2654c48e017ab8eceb146a208ff7978c9025a201e64aae7ae4f3436f9ff70023ac154cdce73929bae40bab3be18bb42810d58645e80dfa0cdb9144ddab3befb9e0149f02beb8b39144eea1e957dd07411d77f53d0778349695dc7892f0bd6f0c0005f98bd38a184a3534e54d1748691d43f3ef2a6a09d7b700994bb3fdc2733f5ea5828ab07c6eef88bcd5ab99ec4059125e220ef5a6048580b8c5c777c0ef06d61c5d1322c8fb3f0c9e49eecfee6065f70c10309dc34b09ebdc413cc09dcb2c87e45abf562e772ba9e0ff25d2486e0361deb59ccdaf0fbe52713de02d31de0fe5dbaa2c0b89425c59480402201342cc36f055a1dbc07116a1217768d5fbc81f1560f37bc6a410aae89b255a716426ab9f3eae518b4237f500bbf9ece16567639d807cd0267610a68711c4a7162b545f18e5d004a464153419bfc9697dfdeaf362ab11d63dfc27112ecb8889e9a1a1fed9fb31d59496001f1734ab3e497aac7ff8693438fd610163cd8c0a40cd42e36d904b4423290399f8887bdcaf66872fc63c0a1329b330ad455316ff34bf581be860512b124d1e9272083ab2660f96d520494c77dd96a564177ccb5b6e77933680404d9e08e276ff36b599b7ecbd3a42f5af6f8a0e657c876b7593b31c993ebe989a541530a33ec5617f715534e6d681440b3add780cc14888501a331173fe9a6847a3befa193a0704421b0a113e91991ddbd09b5c6a7a9ee844ded7425034bd7ad2a48799a20a55a3ad02ebbb95de4527b48036011740f0bac2b2e14a586c42301170ba8380047fb70237804f401d531bbecb112e420db16055a309e7ff76793c09af4df50203732c9c89094c0c052a46825901b29fe288f25560bc7a0910f925272d0a6db9ec3ce34903e416cceddf0b0aa26add6309f3ba6d9f718b1535d7c9c485162eb8d94a317bba15766e2e595f4d6eed0450093eb6e8193d43c38df1d6ffac4ebfec0d615f3954fd18a716d24cf1875900cfa3aa1ad812e1f16087214fe04c0a6c82393e28f2b9c10b2745716e56328aaf2166ffcb048ffd862c06290dcf309ac456b1cc3d12ce6545655c5c79f74679729ba446e4b1a8721824ce1e3b5c39d2197e91e1c46f29531df75726b1170f7daeeef094352fa9af62660524d955926a03d8fbf6d5842bc6cf78045570c1342d4b895da6bf3bd273432372363b2e25e0ddb3e402fc79e7c4fc055c29a31465dc380120d9393801793b1c560587b281c147851f88ba00b8312742c9cc4ee57dede590648c8e29969bf1b409c93a11b702a2a89d4304fe831938f1928304aa608cc7a364325d8b4856af93b83fa430d70797371164c204fe94fd1ff28a6fbac47bd772127578f2d3d3e4d1e11b67551e9390ed650e51a06873a4dc94d7cedda2ba13b1eba4657fa66ba1bc7d63fc18f9a83e1cc20d026fa96c437f8fff722914bdfc47c549e020fb191d25eb782eb062c911b001f2990cb5c75182fe38402fbed51724a21557a60967314106c99c815593c344dd8bcecc9d392aebda7c327c26366c11e7a96da2363cdd1b0b643b0c4804ce492faacf45cf408e42c38bd666879d0aa87d97771d6d59c1952519d31df00af1360938bcaa4067221dbbc8cd2d00678148b8c202180c78a5f5d0067d59c611afd08a5f2af6a7a5ed33239c4d663a29662809dc4bdd11acf10869f42b0e047bcf7cad21a1e4f798dd0c6c57a0bade82d36edeeec5256f3f35d48e33205d15c5dda675e71ebfb25a40334560a3f87200e46035209709ac16a3d3423a74b0b933a8343ced92f5db934b004bc048707b413b9eb6961bc348a990cbd6a091814c67c20a020d608992d197f3741414a2f9a37c6d6d2e614184bbf72819b1b96ff04003a1ff58e3479e399e9bc63da13da2c6d2186c0aa98a7252f5f15b3e7da696cf07a7e22be6d4797c420e05231d706423685aef4eedee8cce4e46391047941cc7abb86bd02a85a34947f17249ca821cddf1d8d24e550a26b4e76f37c4d1f0f92f93225ea6e3083f9ad412d5087cc98b9c21b4683fc0976b6c4a83aeacac133993a7b6f3b63be651ddf69141a51ee1ef01322106164213e034259091ddbb9ab3e392b046ffcec324701e977ba72d3ed3c16017d78aa02c7686cb61fd49b560cdca42aa63532d8393d49e9cdd623b0bf1fc6d0ccb5cd609b878b1cfae8fa6c327cc6ba0f5a05b2b56c62f4714b66d5746c41ec1acda9159a408f29dc2cf6e3b07e90407be8453a38ca76dce2bbafe94aa03bb1db765b6352bb6cced60be40406509c8ed4a9a803c17bc59f5b141ba67893e931e391ad9d5749e213e36e52581e875b5349da9420d8652d792eebf84c397a376d01a0bc7115c14f7fbdbee776a3d1b91d0b0da701ba9482eaa746ed05a9efd395d5585fcb3c6bbf8a292b366fc8824601f1963d1560b518b138e4d295db3f03628b0a02d9cc557f36defa976f1efb7ef4c23b6a263e2fd7db443eaac857dc4c9fa54fbc0b1a6ce59975f50ff346c454dc7301c13353ddd7a4804ab8b795ab5d4400dde4fa66262c129a31eaca96ae54cc33bbaca777be159b92bc28c7682673c553a2f298810bf6460a190fd3febb904d1b77e5ecc57e8d707edcb885183a069deda8ad124494ca19738e6c55c1d225180401dddd10e7212c4608becc4acdcca3a2f8e7d3ba4217acdeb45ac8fb2d55015c83e46006d811016ba303b0f48af4ffdcee5987fb91916b28f0aad9961aae64c38501053cd2c3956e41089e1b41ded7941f5406b4fc23d48592699c353db3931b7e5a2c3529fd9350bbd029ec54e1e626bffc9e1a153df15f0af9384762734fe017dd893e628a077cff661e6f4f85e71638f38a2ea6b8de465e09378eb00081690858568c58221382a14797a4ef3af298081c8899507fe113603e3a61055a60570f9eb33a59d56c64004b066252e16a0b07469a6ca118c70ae1ba518db294599107e44f0bd251a0cab4c7f82f1211b59729c3c1938f4b4b7634d4abf7d6ae20068f83d2a3f68ae86ee04f33eee734d35f6e318c945c09a8493671cab96c2bb042bbc765bfc6c35cf2f0c2fc55429bc6d461308e51ce782ea954db5915c54d349060203155ae637b35c73e8644b437da07f2fcb362df95893c05886a4866b72a60b1deef9c51022dd505ce752e4062aed6e9f53451a9129705d3a51b7e80ba286c5fbb28943bac40cb75c8bb7c5855f2127b646473a61a8f99656b0b6fd5fb360a2ddc1bad771d2dfa8c9440d55353ef55e2580e51056aa007c0b8c6eda98b739aac90065dd4d652a4f2f19cf7f83ec57aa818fcd6fe3aa0e7dd9a6a042c6073ab03f467c8ccf78df049e23777b6112cce072361427dd465605ed116bda8abc5172d1e01a3e296b89be6410cd16ae5cd44263ea676fafc033111bb5fead5536ed58cd105c37948352b846a1847642fd6f3080581378667e3de4fccea589f799b9c8e889060572679dac382c2c57252aa782a7ddcfc2b78ae440796c1adcff4b7938e4f506b2aca52d15381a6b2e129a27da6c0c68372b12a5c3b3f12c1d4a78e87c776fba62282be038dd6a90c5120df174ba2c2a1995e9c6430d96baf2898eb1fc7009091f20cef307df2d712763c2115db1b82a484d04ae5b9ae5f38d71c171bed64850d5f6c6e108de0897fb38ff720255dd5678f3ffba7a3740f3fa4288cd8204a31de05c30c6d771b5bea8508cb5913eff56481b96bb222c59a35c3a1e955c3607b998a98b58faa282a6d853381e9a0c344162ee2ceb3221b8f54e4402f978ab567fc0ef3b740ab1d02c37465392d483c71b10ae478362083969e9e077ce0991e30ccba20a31794395be862632a41d5eb72fee0011dc4c758be01be905e48331f737539aa28b6ce9db1564dbafaff494352ba524d91cb1d2b2c85f29f665a37850db44e0149e791b3e485354a706128d2a14d66e907cc7d0858ff769da52e8b89d7085fd54683dc87225f0807a2de6e8ae0aa2034d12ce63306a368a03007dc3037b2568ba2816d3bbe55b618d2714f092db774bc9352facb46b8ac90aaeb8cf9bc1644d82447fd2742842fccbd3386ad762117de46fe32b62eb4236482db063f15913314dc0d1c989420f782059918335beca831efbf0be643b4d7f5e92866b878219c39e486b90fcdc31eefda252d2954fbda51ed42ae734af6113aefa6ff1504d30db5eca7d1663e381d0d7edc3db978f48902f5a911d23f25a8ddf609874351a15c2599b09be68c226e2a8636e880db0f2dcbe7ff605d4adb1ea5c8160b193e1c1cebf32c48c48f696d66dc8f3ce7e0ecbb3de8ac793be8056eed6cea2c9bb4e74e5a371ed1155e98aa2a6365e7da476b8ebc4651e69ad44645fbe76f21f71aa8d957f7b55797422fd6bd8ebfa70ee345dec4a7f7cfb41a56488cc7506e56e95956aaf1e156ba0c391cf0e2512bdfff87aa0703bd1536d0621797bbcc24ade92e983f1ccba056558ffe2540b0e392d8800fbd7f3ad91f9ba39128a63918ce362e3adeb23ee7665f452f53bb0e2ae03402bec4488c9b3018bfdce7cf0fd80102d8006a505cc74d7a1298f591ac96abf686627237f358a758a3b9719aac434ee7ad2ae0b417d200320399ec5619f3dd557d2c501b6e620973f09a0ebfcc7237a02c9296ccac9a8c7f1c1089fdc7f1b6a629da411a273637f19b787c2ef9d00a54014d0fe83aa5915bcc41e865b6160d99f9859a9202a359a366d6631a8a79fd3e3343057961ba472f414802076d26c327cb2bbc2d7deba277b2a820effe4865d06fd48f558b10f79dfa21796e721bdb61a41487b26181eb9f71fb2f524f6d70f390e4cb8cab1f556df96bd15fb85d55c88462195a576a3b89fa242c656fdc5aa5d1efd639ab45b4c1218eb4e08155e50be18fcf8377d88df4f2d5823a2528d63510dd21ebf0b423041d8abf6ffa0916e74171b2eab1abfbf242d65f3b93bef626715aff7a07640eb1dbfa24708c8b2cbc35958e3e796473b219243e2da198844b3a0795cd1e159fbd14e887d29cc922d997d5a6bb2c59b0a255ee8debfbff29a238dc3a2533586f2dc9b025a0a04568ef702483cfba0583933ac749ba6e18f686ce97f8275738ff642f217a3bcf6899d0addb06fbfac15a608ea9c1064d1f4a7c174099637aa85400b82c6a19974513c0923f14379c62386023efcf888f878c3616bc2e045793b9d0ead7ca12e3fe303e80a039a2042bfdf5453ef5396c2151df0693c44802ef3f015d046743912d8bcdb97917bbb761b0f9106ffe241e9d08a9f71260913bba2ae48a188f0b41fcde5aba6208e7e1c90437cacb8aeca5435d22c57365d1f03fb60eb7d016df550b031d5414aa37d1e1b0136ed0e815d02f332a926e2cef349ad246e41ac48d5dea3077c67642becc15b9fae6d036b89a29ada144605d1d4bc3f2dcb7fc463f112c7012fab5dbded4426c96dc1663fc4e18d70c9607c3e555e203f353ddca3f5435a6f795d4293f656d1ffa85ef113ff8ca98cb4655ad23ed48b34167d7671f95ea4d24523a70abc0c6914aa8ed5c44a04e33a622298ab0ada4b795d420c3aec8fb86110272926b53105b085942f6f8cad0e7bd74407d0e95947911edbbaef11dbba87e94d4f0b2b199bcf218466f9415949c33ccee0a75d6e5c917f911b868fb98a62481a5b85d950c5418c83a58956cb0a7c58407e7d60ce02a2f71bee77070bcb46ad2603811fd72528a170120807dabb82d90fac021e66c383dd66503f46ee16381462a492bfb6397c01254ca8d6d4c6a671258bca3b1ea805469137e0fe52d3743778326d302aaf8a59d2075a93257d2cc07726cdb113658d3237bf5f975675e8a10f136d7da89171f7305a566a5dd94c0c08b9e732c222950f63ad4505be45b691c71a10b77de9a4b17a56970c2019b4cfcb1f5925e37e2a83c03a1fba1ea2b6aa1f1677f3d0be6b1d6c325ce39e6604114d1a462b03906f8beac7fe9f887cf178b3a3eade39ca6115c60ba954964d508e0bcc373b10761aedeea9f825c36227c373a852dc477ff78f09c1c417ff83a2b7e86798486248f2f160a1a55c6fa64aadc751beb59c87f0c5670cb899e5a27679280e387c0000577795e0a222a15701abbf6cdedc7c37e3a82035d57119c83dd7d0accdfc2453ba4ce2be1261c1a32642c4d48b3bf7febda9c9382ee6a6bdda5572216e47415520e2fa571858cbd2489d6adf3d94065d3ccf364319804c302836a3f217180515a346c45f5efb368d2aee58f693724e64741c00a9b3b05eb2717fb7b628e22960f1763a64915e224d42a06403cfab55734e47dd5d5a85cbc4f8d97fa4e6d8044bcbfb4090e2318cdd3b5b54ee576f605c04bf510d316c30e4713919daf987211887d878c3864eb19273d5ef2e36e74bdba70263919f0834a48457179dedfb66a28fc9757c3753df767e76476198680de299475380d01796374dfe9336b17a897ae0d2eca3b2504effa1c95ddd5134b7f613709aef3265a75fdf22ed141accca7e8c1eb6c03786d3fc58c09c8e57c72498a0519757face1ba2bccf3b8c61124bd2382bbc7d0c2e7f64d0c4c0aa44bbb060a04bb3b89d5a68d05edfcde5a1326cbf9f4a32b4fa59773c6d9c2373d5369668c72df2bf57591f60b2862c6e5acd093f0d70ac5dcedfa171461a91de6deedf3b9b6e28aea79cfccf98af79d8f1c68794e44ef2ead955ef9aa4a10fe593b40692f9b15132d1ba935964232e9d2d60f9ac1ac9a77d711f59b78778462a6f9aa1d7909fc13c5b63d27b608582f1bcdee2b5b9ad246c6e89e4c321ea6bee51a0c9846409290e90c3c87e481d0d94acc557ae5fc71548aef63c9f5839e5802af6122d4d62a96a00fb69edcc715ef2f29d778bbc3ac159911cb710486302eb12cc3d30758470e6fe9f362611b3a61ae64e8be7163508237d4fcff0bfb17f2cc0b200f5dec796b62154039031f5ad83c5064519c7ea7f065bdf7461c01db72e158c8ea1da68394ec3684db4b63aa9f42eb3e4febee53ba01884b4bc0182ab86910bba57913e0b558593cf7c234fb6bda60943bffaa323b725da93606c9d5739dcc6d8147ec1d531c9b248166b710801142005411386edeef8ee2fc7a1e5eaf83fd6c777f261a47836a2a68678ce0f0445957a1eb10d198c89dd706a7329debc2805b4d9902e9409381f07d9a5cb255f6949e86a126c82acd67ae4ca67030a2aaa928758cfca81927ebd40de4421886994a641b4886069505f129291381c29fbfa811fac7cb50f737aebe5e41ac5ac6e3e79924af9660571aa32d92c553d707df37aaae135dfbeb7e406f477dea43811c4589bfc5e36012bc089ad7861332640933ad362ee9013fe3881a027bd3023bfe49f6227448f77b4aed1c1fab38065497c93980070a200993314f69bd9b5d04145f5bb2c4e5a4b6f06cd73fbd4df717d74fef413d500133a5a0634b5c5882d44ed630823b016f5e95b538b4c03964b69e5bab7280e8517b9737f214a0b001420c903b1d6b6981f0ac86d1edfb4956a4c5e7f31115d1a355109edc628a63e876328321e043b18aeed6a832c620c71cb1010ba96b3db34b5cfec763f5a88ce8c45405a2d07328f0548e195c203671cb8db5161395cf397f4bdaf83944e150bafb1a84d671b2168b322d38dba16d380f6041c54fce1e56c3685294dd581a65b72a8dc2c79aea17ccbf1699767cf92de468d904aa27444a953e8f484dc5f083e4772bddfb134df6397ddd18ec1819cff5ba06d56da7e78c580f4f10876a8fa20c3890e1e86651e1171fcba7ce1cb1a3ab7499af32ba03dc3a3b7c2a7d659b30e2a31f8075dc433c2e2f8db22e1dfec421247a1a824652384f49f4ff597335927935e874e27e0083e66496c986fb025d040ce3137690e0c70e165422faa388aeb31092aec82c219635db0aaf6ac1b890bb42f9c5c8c69b06b72c68b31ee32d7568ee9376d6d9dfa051e3458151f5016d5097a5622cd781c1fa421dc7a691f44cab56d64c8c6e4cf1f2b203957d18f78d551a67cbe1e692e8c75452d414696cac6aef94fc726a7d24cf5e99786f1259360f25f4a041c201bf5a14b399611b6d677b98bf8fec627d09abc04bfc17bdbfd27572ea464c87dea124175009ce9d5c70d278342588371aca76dc98377c77780bdd3a18e7e4048f79bc75c43c98d4673e8d197752833d9db0f9498b67307e29de56328b43fbc99711a22ac0b3e83b6a2918007bfd9dc2be72cbc45b3fb0e4904ba335ae3948493db12e4c12328bf619287bd9dae238693ed23deacd7faed4e1e9c19e18eaabe0a8f7ac5559937891172d7ed81b7d3a2131e44ac0b033f369683f76b994abd2ede367ee83941238815e787b0a6b32564dc41b5de591bc5fb3684d4dc2d527a54a65198a1cb323d2acb89d3d04c5dac6a9a2f76d6648915e291a23a453b03f0827bb4f7bb834717dcfc6733554bd5c99c5656ab31e4bab0c8af14638afc610ed69f0536745b9f37bf5e7f3975b51fb82370373d2b16a08be8dc46bb069a91b727f5415ea3681dddd2b2473b8006a521d1b8e9c6dfb32e9c747654b7207b5d6066493843d428e8d3c0aa27ccfe32b1496fc293d4d136df750ff29374f963b340efad0c263d70ec59c071fc0799ddf4c4bdf9f571477a30f6a7fe092c5dbc2614ce40b6acd7d6581341e597ad8b7502c21c42e0085bbc22018f3ef0745f186c6a0409812811cb1e0f50e73c09b633d3e12863b98fd9e3b94dd88237574dfede873ac98d2ccd2677dbe8e124af8ac244a0c9bffb0748b97f4258a0012edff0fbd57bf64d74b4405caaa486fb056ddc944c747cb24caa7201e2d0ee235ad4f7e5e3e052091a0af90e86791262a14553e891a5700031e58a51c71f43aac4623ce2f648065d1d812e8e957be16e6c57c3c78fd3fd9beed55e82327fbd533124d2dc2a8571b839cc5c2571d2ea27a49faa9e825a2c4de4787330252c1512616b70e2229762de0ce2e3de8654d10aa24abd3dfc253d73a4b999f58e03238c042bb3a549e5c2678d2f9c078f7a918876071757ac18357f53c65df589e84c9c7ed875fba186523ed132e9b4109d798553445806b974c57656005153a7051cd276f4fd60270886222438713cf6ad05d9c69dd546a7c65a7351e4c6b94b3bb8ef3b23f670be4d26286e33feba31e06bd2c40abf76d3140a2276838ed51b4d3b6af6fead6eb570e437eaefd08d71ef23b1828f8bda79558a4622680bd678a0ceeeaa0daec1d4bdad8a2c10ddd8e4f976282181051b4f6d6633ceffd1f30c9e4ed90c857271ff342e327998f903101e32e226a40ac5a8381a544be494e46708bfbf20c60aae632c9a926afd0e38b14eeafbcaaf72c73561dba6529ec167abd933e6aeba6e4a3bf989e1e7f0b0e7b40edc840f0096f74602653b63bba4bc477af2c7857e963a1cc06cab6b675f423a075601ac809d8ca75d3bb9f0504e2ac86277938cfec318d9ced9519a8fdfc1a7a9c9eac71a1ca2052383d8e146b978af7abcaca577f9a4f121d28428b6baa0cf4f112d852f71b9ea0bb6e85e03e97464c6942761ff4a3f93f7d5ce1150f65c248c37aa5ae91a3644ce3448f051ca702e3632517f80946e29928c091fa5853afd9b0fb63e6e6ee17ba7f4f0ec9fa5c041a8f22743d5205d7e8c9ee5bcd4cac0ea2d3b99a13c236620024de838e8d93ddc6f61dc94e9f9596b614d67222fba3a1beecd10383dbed41df837cf53e868c571f3f257f40eed5519094564c71cd836cf8a9bcb9553302186b32e55b77896b8e83360b23becc966d1cd1e0a2b0e5708de2261f09777eba3e9ef28c185227e7de64eb0bc31b305475dba0e58ea2d10474a5067969fa148983f1e2d6b884c0bfb2b4eee78ec85b7d6f7433fa126cfee4ee640ee4b9c13589f5eed4c437e99312002d6419c0336d695cd11710e3cd2abbc088fb18fe6c81c3d0539e049ba519c5eb684a11393c7ba2a0d048f18c87963f450cc1557ea16c35a72a14e1212f24c2927159e7c0d622edf87dc0811006aa59e7bf01d8ba1c899c18d4dbe4a1c3aca7a6096d5a8de98b522e22af19c57cc1b24471e1b87de3e74a3a0998c2cbf53924f484a761fde355e06545e0d3177d1cb366870259e665fa3149eb48b0c0fee7b285bfdd89c91ef0fc1f91e419b820b3f478ec0d092dc5ce28dfb6d9ece7c65938c78e9a28d3fb5432cb4500079af5a9a0d789a3c1e7ba16b6919b6716b5453ca82bf750f94d17a10469961bdc02b66c40518cd926c9f66e9683b93c1738b1e81187b65fa0887aeafb1f4c59b3754db42ecc84b449f23c31c2e118dcd86df678be01e48037577c2f634d8c308f93aed62479251d84b893ae5c5ced05eda446bd16e533cab17e573c6373cf03ef77dce96dc7cf8edf8f316ca17d58f08b8a4b019db12589e635fba1b4ab0ff22746d1faf37f7b624a2f3174675ea5a19ffeba5d745d547727fb588419e9bcc5a87828abc264a75fc3c1f40dd13037c0aa069fc144dcdf603c5b8c99d8a0db5f99c2509f33d1d2d62287fcb7389999018207de3ac9734524644cf42b7071eb29ff3c48add530b458ac0c798745397e61619957a194da879c43f56465d35deca72203bf88d03471aeed1638f1452cd5d7db97d14059d95ee51acd45f4dd4d8c4ec8985cd3a2c4f6abe1b4fcc674afccf2641ff25aa36b69463b5fd634ecc223411ebcac3e872b38ee0d2c09f0c626d413487ccf9cdcf9c95543b838e7ba5fb4c42d6b14554bbe8bc9820a6a5e7a71a5c0f74c183cba151796a4eb897650ffe18952c07a84bb22dbc57a0ec82cb8867f7948771edb4e7fe05c4b603ecc25ce0f137a6b46010477fc3bee59c11ee5b69ae065106b60b65adb87e6d226fa301c563384f07b6222c9d71d0f1c405bcd7bfc680f76d7ff0de10e946233a757f191bd9b70746a0105a1e1172be3255f398fde957b3d9e2f49d64ea36b9bbdb04e20eb2680198d77d8a959ad4b9feebeb9fa073f0538ce6eaed57bb61c6156c383d8aa9ff453b48a0bdc5261941e6779dcf9a5557b9f8ee26a10a17a61537f25c32c5a91f2f0b67d9b5e6c2882d765440716ad522593045aa8d19f96e25e1b0146a667228ee5620ea79f9d8034305e25eb1c2a3e7bea18c950abbe6d6a8ca1d5db333b23a06085b7cd52c9ee1c27ed1cb1ec410dd0527c21376eb8e3b04920f58805276b6ce184a62d7513954d9fc2e392ec55e2160649ea7b2a993afd57fc75066181173f35a06f95460938a2d33351bb8aa756d94767566a2438ee06919810ea16bdaf1f38d2a91610b043557a9331978d078ed7bf1ed34b1b2a0f7428371518db36bd7a3c56b9dda654cc664c5118e8e810162832e298b4a0826b398b3604a2c593909aa2068acde9691c653a63881ff0000a42eeb369f60ea5378d57446f8a7d7abe703cf25e0509e49d29aceed0f332285239037ee15e71fdf631738959ac55c1a3f7f68fcaa58294c6ba29e68767571f6ffd4b48583582df570b1b71cd275f948b44193e216e0767996469f2cc776ec6ee6b723851631011da6aa69ae9d4a624ca340fa59c1184ca95c589c72e6cb4a4ef90b43eac073b0d0d73cc2ed1afa3ff5a1c8937303f3f8d3a91d8faa8e6ad638a236fe9a142243e2f85646f6d6df0f2519f937a672a86ca7b1cc5860fa31d40956b0eb7ed1c990fd201b6ec1857694aeb247877104315afaf4124d0d4f5f7c7a00891bdad4f32cd9696157762ae2d5a3abf82007a4cf6ccf3f908ca00b19837f03e035c3b2b363e88333480d1669b23375a92974f6848cda7b5c2fc88ce6309f746de9cec31d46a06b5001f1a4f6fe7870ca21055f9732baff7c3f4530340bc288f8a94adacac6a1f8a9e0874f9637ddfb1d0cde1214ce56176e83d4bfd10c40eddfbea02358e65a18e6d3ce74fbbff07a3be4003049381adea3187693726e2c2394a1bff3e8eff065e21a7041fcb3da7b0f653c594b39f127ffdd1d4a2fff21d01dac9e599fc585792bdaff381348c29f581fbd4d6f1c49225bae160f050d125e93173a65253d2ab9837d4832ddb9d3752be03a50d1547b8a036ba22153ddd731a34aa0f8298c0fae61ead00a9ed0c20a7067fcf6d03159d339d6a0bb5db592361078ead6dac4c9e0718206f1baedec88606e2b2b90a3c957b03d4783cd72087a2f8d3a656f62b50710f6b4e73dad7ce70dfe32b3760fabc411f5326b2db4921c6970183ba1e5d1e2d1e6b51054163151da1522a1b0346b3879a060436d9158e5bc8fc8302c81a378fa590260d25baa9dffa14c63ab200733f94ed24463e4d6613bbf751352d6ed8707fb8f93ac2c5ef206c2a4029b3365551565ee904621f9958967488857c159ea262ea4af7d949562515f864ff6343b1b4657e1f433bf44e86204c5f006365854baf286ca036c16309213ef5af977f22e78348c40a930d92313f30b542a2cd456d995bfa5a0595a20505f5c9f22103cb38c00ca270c6c4620fd90d469169c85c7ce34456eda6175346930caa9b41547e2d7de15f425047cdd16aa3de51e8bf7bc970f4d00db070ef48ab88be836facaaea20cdc43a0d052a86737cd061c28931b67aba036f1fd0866b993f882f4ec57a7a8abffb38c26732a8f6d394eecf7414926c5a5200f66cac3cc3e28c27c4f9f389b5c5f97dfa0991e3e1bd5ddf01179fdec1382754ba22eeeefdd35a0233aa68f9ea962cff0787cc0bb57b9c28c6cbf4b89db043e5eb59cad2253c6887e1014c9cc40da6ffb6ed3f55f8106deab3901f7014f407a2733d4d0b9a4ca32e9250fcfb0febc17a142449c188cd69c385342d408c3fc7a28c15850dab3b8c003725943cbc88bcf65bb355ee79fba05a6b781838ab06fc207279db60243b40130559f17123866cd2f41cbee6207dee36916e00d2687e0820fef4f2f814c62c3166d2a026d131ab6cd048b93336e28017b6c5b08ad4f253dac966bcaf9a860a120796ad1399ab7d16db87070966ff971d8b0b1b2a81e2194f34eecb1e3059ca4fe4e06bdc79e743125d86e21df7457ee02913c2ad056ae25174d4d4b0a2cfe5123df6637db70e8f36c82a0caf2e3ab157d3dd83094bb9e1cf5aa2f800d962e7d4649ee6e2d98eaab9ee0adeec99d6643544627389653bb3a89d7ccb3e1565f51e2004da2eb5e9e6d7b1f2509f2cc224c9287c5592e1bee4c7797163d4d506087b501a7a7362c752ca8f7c9200e042714ca253ca5e686ff568395a6976db972ad44147b8aa0ae31e42cc61d4454ac5a71909ac8b4fcd591471983dfdf30f596ba56227ef0b4d6357ef534a2280bc40a34fed048cae33444b0c6c8e198dab84ee4759bdc096ea1a583517398f313ed17833052decdc2af27d1bcc6352d83820dd370e9932dbfb2287475fe3e895dcb67f1c8811a68f3f4ef4152a54d15955fd1dedcf4d5e60bc05408faf9cf9d6eb3b3d36aeac51985e789a682b5401151039ef28eb9a824aaca0abe5a18c229e1a80e67419a94a3bb0102286a61626d4b2dd41222eb33716a2a51cc77130ed1e2acbc49798f59488b87e4ddccac96dbea5f9f4778a96e920b68740315b57a43b35c35a1c3348b74208981ef8d458605e9e6f3d931451fbac2e7782d5b6f319a2ae06b09d4ed2a1e59b5bef73924149ed92dd76636145f77b763273a57851d3d3128b612e5b361c047fc60217ebcb6629d66ec49325614f086363ee177c3d7e8457deda435c2632eb301f884acbd962fec7f7f4513291485c275916c10c82004f85da21782526d99955fd58c3c639503d26b881b8d8ddee76edc230bdadb2b9270ad7d309469ea0d9eb8b98646559c932fb66000977b26d582858265715a3cb5e5312982b3b31b5caf9a21160919d9d6366b256deb06981b6c76ed2ad6aa57e3266359d72b71cad860315ced6711c6c7f04dffdc8a938e528cf916f6edc70da51589d26ecf72dda27ac8d50e660f2370b75ecb6d361aada8b6c88337fd589af4262a83ce7326e1227d1e40dbb0723f38bbb1dd31f77c8a7eb407d6ca335ae019e5c90ddd71b3d4491b87825c13da375c8095d83d97ebe315c20e8d7b1e55022e6df75bc22527ceacbbdd9b900264095c5e09d2222658077a1005040534299947f1ab14c971b147fbf747e0f2137f9c350084419276fb67ea444bb9084c64cbae36f90f1c3af127761ec83c257f4a293ae609705e9fe98e7cf5b5280d232bc75e170822c4e950d7fdb39f6a66a686975ae39364e77db6d95e3198c2d6d7b160880067d7bbea72f405ec813c758815bcd89fc586492d2ab921f89dc9536e44d7bf74a8fc4730ae793d33966f9c4c0b966515f47150940562b1be7621e350df02fe88510b05af9a6502a94aaae5bd4eedd6167cb245d8d62136ed4f58f5c9576ca36518d642e1ad38c89ef692ddc8c9f4c8c5686730e4ddc9e5601f75e68914123ac9720e5942e7d9701a4d28e6ff29d2b21a6a0200e800e2eceabd54762356dbc887747d98d48cb437589fc5cf19312d2f5f1ba069d7d095facdd19f74910ffc2902fe095e9cf7203745ef7c546f95287cb599b2f4e89d8da64389a30cae4ecbfbb63ae69523706ac005d2b01c81726680906af8d0169c25dcf924979cbb34ce71b06f7eaef51a04b76f2218276f49faf437b2f994a017b5b28f52182bdb7ef981111a039425f5bedf8c8395c981916aae45ba85f189b9d79e1957e85c969e6ba88e5da8cec8e0129d1cb38c03a0d823612bc8eabb19dceab4a10934445a6221c2f3c2f44675c1cce110ac9f85da73ffa7ab43e26d5aef30c9a29df081f52d674d9b46b984cca8b33b1199fda5e2bfbf855e2d6b5f9ced4c968f3d17f91cc308b5fab68c76ec12f7b7f896ad738df1a31430e50c8cf1df07b74a75c9c2e89981291463e4f0fa32780b54477f3e56767599df403caab172922cad6ca82954c9aff10319241da14f40d9e7328b34dae4b0b97ff2dcb8f899b45e05f2e831c07fcb0a74743f028d2fb62929fc6c295d7dac26f83e19ec23469dff9154ee9a710ba5652a7dc629ef956425c461a136443adea986040a1057284401070f76560e736b0d7a957725fcce63ee1611b2a79970a3203911e90cfa868fd610429e28259e7d0aceb8b0626e4cb803b24819908b002ca073c9c7353822b08ec7fe5083a40d08e2220a5a4a015238f2df4a077cea156bac6c1e04707f2865f53f61deb6602471298ab22325f7b1ca3617a073e8af782f27b1d4f50e033b1ac7fa7acf4091ffd5c2aa0f319d9599101e8a292492726dc55ea11a8e743b821134720d3c5954df19fe3c328bffa46dcc1d99039436f225c46def6ee32462d20b6293142dde281f4a088eacf490f0b6979c5b38e41491c1e8ef23d7e3fff7df20583f21d66371be58c901b1f862cc1eba5c1b424058d5dd20ea1966a7c13d84b9c4c983af7ff9786f8e8b804f178d7b33c7d75d6098e9b626a680121dbfbc11de597a46bb5b6a6f5da53629f2e80aed4a24c3ad13c819d8502c5bdd0b12beb84a1010f87e3ed55fe15b9f94772323093567c42bfc3c5a5c77f88448a20cd3fbfb7d3a467cfb2e6ed2ab78e694ea3718a392f66be6f6bfbe1081241d0eda4d3425911d68322fcd7c693b062bdbd4ac614de4272f67f0053be8293d82d6a6c526c36cd258f2c0ee142d3fa3550c666a88cae559c5a80d08be0f462063f28894a73cd8e5e10f6432df41393d8fa1e660f74539444092314c66efdef725d5a45b4fc3afd87539b91cac2188d5d45c6ffe5de778d175b21c759494103cb2ebb97b3f66079eda0040950cfb3402e6ff40ba3cfdc7a0d298fccf8ba2ed40c743569ef6083f20a4de7f697a3b786b8382a98b813cc583fc85fdc7a28607a5a2576e2c35b019bb7e3aafc81006f950fd61c97134a76501459ec36f03d2e9e4f637fbd7f1d606c04942a6c5d498e20212e484eb33ecdfd4eb076cfefbda11c9318e8e105ebd30f3019a1ada8de288671c67ce7f0152386b319f994edf072ef21231818ffae38e0d7c2ef8632ecb7b77921940369e7e2d69392a1c5d7565ba5392457ecfbb24303befb42a1eccf096c1d3b2ac1c77ae3e24bd36f4ccfb6a62b41626b28f3e1c2c9c4fa152b128dbd1e74707e8c4612c44268e0bd163b60dea31fc89d1520cd52fc6789daf24cae7521dcd9189c3a81c9bb0e857a98b0f86c2b96aa782e0efe6654128cb0163a3708dae764cd43128ed3532968abad24868d9917c496fc3597c8e0ac85a42bd4cef01779b72402b7afcfad4dffa0d6bbbf2a2ceb4146ccbd4ef77120941db68cc1e8d6a34065d53334abbf1504d9eaba8b3dadec573ae8a8af3acbd4d1575faee5d110d8b32e04e2f42b39649be5a8d9ae8f700a8e6ffd9d7741964e46289f12fd4abf837e6c645edf4f708613c912f5439ddc7ffad3a51f1ad9bd311f7a62d321b955b713e5565fc4e6b3b987cbb5352b0dd9bd40c21d39717f3cbebf5bc71c86e4375a99da25f7821d25b5c7246dfdb12cb37979acd01dd2c438103c0d0e58da5621e7d871de8950d6f4759d5ae5da2fa632c785f90c2f7326c1de0f239970e74bc3ef29b1c3a3fa515111c9f387cf252225d0eb509c22877d03d490edff57fbc3953064c2392c052017f85aa9a8d741b1d814d25b9efaeb4b86f6188f45409edbbf08c21fab084d9db8d0cdf32067d926aa5ccc103fdcb946eba905a6f6d9caeab385eb41b5ae18147e9d091ba03ed2832a75cadeae3d87a50163f51b3da5d66c067d5a640d9f341f1767d06fec0a2e167bceed1475c6a0e96ef5ea8049d9d4bb24a9f4306798d75a7cafa39cd207afefceacafc6b67853539a6c7beeb9a5f143fc1d896c40e30f731aff1603729dd1c2c8f16db66c8e3707b1060ea0819747a27447d1caa6a1aacb0a51dc42586e42369d8aa90168548cccbf518b7cd45201b925b255e1ce864d5155f6fe4950408a6674b3be35969e4d46592b71a30af56738e25151a2ee42db8e942ff1006155f989c3ecc336584ca47de177067b3cb7ffa48d29acdb440d66ee5e9cfd8225071819a5d79c047a8746af62a50071271d29ddf29bdbc28e14df51aa3600267ee9681a9288ce823ee573f7b63794a6a5e3c1eac033ad1033070c250f30ff32a4fe9d60672e26c7a2f8908aace2bd20fb1da1248ced4eac49a7080ef0f4eb09373c3be49e3a4e62d8c71ca8f6ca0620a6e0292840af57a161057b660629fe4ec82d2d685774dee90e792c01db2b325dafed8c20dcf9485d8888c74050b6f923c45b65fe5fefdace6675cf6380860a850ad7cd978467f61f77a7756fc5cb15e4aaa3103a09a26d23af80e76df4b081d2d2bbd3f092cdfd10963286bc65b569de743ebff64437c275bc9b1bf994b92acb10c64ec8f521d53aa4616d77a448c8900c7bddc89463f3d0acc167ad34e51e096480d2b661f0fb56026eb40a104d253e290980591c2d1e83739ef8f628bc06e99bb8f56ab0c755cba8c605bfbc0d9ef2c1cf0f3e52a9e8f290bf02a78b00df55da6f5479b27c9c5e0072c1e7e4c3bcc1a8dbec4fec64d780181063f735d73678d2c057c09915d2397a8fb7f7bb5f6dd2329145e6d314f9c019f9c9790a76342dcd65831cb00cb30098282c385b8c6a984da3786ccdcacb33baaed98409682c625100801213eaf953e3095a9e5484335ddae578edf8a33751db06a27ffa670c9e3a9dfb3fea7bb71574f561a950cd409ef58c972add2e8446dfe2e67e64a65e512572a5666af13ecefff721790de72b37cadda92daa1d1cad8a88445d9649911023109cce95afbdf29666f09f64d3e657029326206b5082c2c0479fe0ecf81eda3335c68ccc2d500e52d03bae51cdcadba68bfb57c2225e0a52f8110fdd6d948b7bd3a482c95730f9f01868d36c1ab65cdad3b8e1cd1f05ca0862e5019b97db28314fc741195b1fa4e136ccd31e7c840325e3a82f0fc6e12f82ef30db22f1518f6e0cf0e3f1667054f99d21537ecddae7f85c92997b43271298ef67f26df8dc0a0ade6012983a44b134b1c38c763c6f2d951a22d445b05f51fd0f9e11de6652b816a2a087fd94141fbe69d24c994019c4193e9dbd42add6d14634cfdf754c69e75ec86f95329bd0306ac5aadfcf7f4cb2b4280642dac2301a67bd00a96ae6d6efb6cc2ac061b699992e2d03377bdfef043f7e31ed6414c9d8e900f21c76e6a452605e6eab766eb779e51d693b64758e1c1719f7b9a9cbad9f0db5ea0177ea5fcbf37363747590bec25c684dd4eacc87f72134b83bd46f97c42dc134f7fc13cc82767bb6d8655d03c639336b0773827546c611d053219402344d7c8176e8badc5d65c5a7a9251f0a3aa0b9348c6e7adc16c870bd726c54abf4b51b19b0323cf3467c47caa05558e758a09ab9331ee1bc505d279c9214e7fef6498fe0ad2489de59aad495b06ff4811a86a25d91533a86417347c30d68c64616f3954f568a9d2a4240c6e060b0199768b8e7bce4dd0c33b8bc4a624dea968bd70884c04096c53b1e73975ef8aa07f962cdb45259f94accceb3c6edd7fa7f7d4c41b0dd24da86784f771b1f3b83785bc9e3a98e6a881cf813c7918c7b2d8e10cb44feb4d4feac576e153b43ed4b207a9a7986e2d3a4ffb886aff3c36aa64123e3dc22061b8b46d0cb16457e59014d9366967291278e862f2c973d5da45c695868dd78fc594f951f1ae8b54a998f12da6851a1ad981ea6fbd551fde54ea4ff0c4b535984024a3d3f011956e672af4dbe64fac1fb6522865bdd471868c6ea2c4b7aa48fbb3d50e0c72b8b229f26c5be87baa1a53b77829d17f987bf1affc36cafb81bfd0b6a88ae74288f6135b207666d230e4c3a8d8a8d09e467586f8ffd813622b0c8ba898dc0fbc41d99decf62ba625289044031f1221290037d1997a26a6589be70b7060bda6876ab882560575b24a7e274738ae0b030a46c0f27b69145e5f78cbc858da5ad0947351c1ce67e01ffdcdb6c9ecd2e9ec9a2cb2a1a32d6b27c3e278ffbf7bd20d02654b2429700f2b87f9cf52f5c6370dc9cbd2ba694d458319db6ffa83f0bb67082aac5a276f3727df6d05fe2494349d785c8d61e441935e3621796567a69e68310e61a08371a13c9489749841385203101f6eb0236f9738962221b2da2c76e7611cb9f677172b7724f436cdae83b7c554faa9f923d86a9976b4358cdda935a283d7eaecc303c82496ebee61a20cbf4c04bd9497979977d816e3495eea4840a8d7338bf8723dfc836eaf4283f3387875d20f284deab8fe32db1433e61d75ee943bcf185ee54560c25f395921d7d024f4b7708436205f92324c1d90d90ce40c9aeb2572a9dd92244b4865615c1bc5bb86ba036a4972299b632ca13e9af9f25c5a8be85cb6949288655d830b01b4d366f798a0c165cad3d55b1e5ae0fa5b9c11947fa7ff48165e62239b100d15aa7d05ef044811f68023cf10955d5e13ed57549be2be673668c49b9fd3fa9f73c051aa9754160a20fea26e7e08886225a98bc3a49dcd031d3e67b28cf766ff795fe71c0fd6dcfd842071679da892db9079d0ecdf700929ad3b3837137269edc3990555a92ec661928b21c8a908277ed87831d761f4708f8107c45100b2812ff85b5a040370cd50623f6d6fb97c7940b602b979dfdb33d387b05e39443af30ea519d60ce4cc5a072c1c3d91a22390dd3f0213423807a9f5f82773bdc0d347d82c893803f2170c6944336a3532d761551b30c890d029d1d8e1d1edbe67ba5f5cc2409bb432768a7dae076ab7b27c6569c3329a39bc12b4c43b6ef8b2a9df75717546cf9d5eef51ec3b20ff45c26fff22dcd2d406b9dfdde794dc0322349c676f4fe5327203c67a843d8df0657901784b97b5fc32f9a4e0cf487c6a2d006b60b37f593d9876c1e7b7c343bd6de145cbf2284f20efbd5306f1482bcc04b187c32098255eab97d3b71c6f703a0abba9afeb4bff33e1c1e94d267525a36c428ace99d922f6f862c47cedd0230eba940aa64dd6894ea1733366298446773f075af988909e200454cd83885bfa0152314bc3f25d5d4f3b00ab971a7c7083ebf711895a8116bb03984e70efe2a13b783e9e28fbec5974b02d3e76fdba1897f150b0240165669a6110088447ad5e83295b01cd4bc609a9750505c33c8f3d7c6781ab7b717578243d2580335c13f2e2356ea24f073a7ee5f24e331d216046307769c637230acb95e96782101cb18411be4c29081c8a42c705dace005d6690ccdee8bd8494445d126c05e0a718267653bdb01f6fb9de16ab7c99f41884f4a9116dbda971c7ccd4410009dcaa066634af7d7ff145d04a65f29e8db61a8f4f7e268cb12a51bf9dee9e946eed9f41997c9a1e603f9b9ae0a2cba37edc345817f442b21ac00606e28cd24a05c161478da78d13a27de510d60ec9378711a0213fc0abeea7dee02ae0f36afa21a5e63ab5e33ca47319f372b981cdfb6e48020e966f9b6180a2383af70700ae21c92560ba7e916d689e5cc4b53e5b04d56027a6dfe872c2b136914dab9e5d24bcfa72a49ca0dfdd4f967a4ce7f81c5a2a5708567ce9d1d8ee4b5afbc1349d4f26cb5412631a3fb85b1d9e16c07e957a7c8939eea13e06049e38cae26e9b30d60446ac5e5c2a6096966470707cbd3ed50fdb9f2124bd96b4379c7d22906ebf942ea4005eef57af62aa9cea6ea70e054fcac0bb09cd398df1f49fabe1fa6561163fa4e48bfbc81ddbf6024d67af01f8c16d80f0a2450489d2cd2ec7c9d7d32f20f972b685c9b26d16f80ad9768a133e77a22ced4b8ea69106c8be56850fe26028794d6e8c8bc874fafcddd7fa6a06d5b2d127c44807eb174f9585a547c7601fda3747cb076ec960c22daba31684cb6c26d0374d42096b91018232b245ed371a92dcddf0013890ac7fe281ff32de30b77e587425c50d28a187b3876837d5cd56e5df4473a77b461e9449b006783bc5dad4559affc067f1cddfb66cc4dbe4f42918fe92e3cb569dd9385ee061570ca45339fe8dfe098c985d195f54f67250325ba7793b2864c32d4bf72e6d3d04741b7133bd1583d7e9350adbf30f0240e76f5ec02d7d510c7862743174aab1f45c5ecb39ee172d465300ede0af699a0ff06a40c570b12481588de99a7618eb649db4803967e5cfd6f57828748f202dad15725acec6a44c24760aa6b845be65d12bdbc513d35ac0ece2ed91fa6e1f2c69634422256463f87da092b4693e24fc806386591cdce232502895d8667909a29a512373c87f23b5b5851f8c5ae1a85ff75838b79c26a356ae78c26ec4e6750918e2697dea75377b1a6303ec90f929cd823edd79501b5e7a1564f9c7af0a752fc1cc3a2272ba31399e4516f4e3123c76afd8df3ae881d3ce7b20de54dab238a3f5bb17e17a9ba4a359da1a307e406a24d202a85e8aeba94936e246048bd5f9d51b9973a5d1d327b303cfbc4f613338118243b5ab4ded2ac384c4eff61b9075971ae991b27aed8d057bc2ca4edfa0e0ca1a71c73dbf2a5ca80442938177759b0ce03845a42b74734a42c58de320f909433966c76260303f823b3d698fa22c4797df995efbcff791fe006a6023a06debd142c66133251e422211994ef5315f1f678b2ee0f404df80fcfe65e76fae479c0dddd18545f6212b7b5161174ccdf7f394ddc6447b9e09ced4abc18de9ee8e87d2744b3aab30c2080d2c83cd76b5b035bbd38c6ed922439872189926d6d8ff3dbfb76b7a8c23f8f051b615de0f39f260f7eec00e774c85154d7b6cd6a4375220b318e60c0f941a99c86832779640017621b286984e890aaf49b000a67aaacf9b9450fd86eb3d9810119d5d985bc37dd8cf29c110fd7d309d7db3cf607d263af3c0d8a7ed024982280f157f97dc4f31306f61ff4c447f2a2d312709a1647202ce0c71c4a684d8e6512d746a1c6f872d254fe978739818841ae2d2222b33a9ce072a9d5eacb881fbfca9c4f7d4e0a03a1106581aced98873a6851ef9fa5cbc507a9c2303344ecb17c350efab63f3e49500f7c6a142ac41fc56f69a7fa408ee6cbb248f243c23b27fe51f46f14a6b30009c1651cb01f66ae1874c7801ea8c56b009adaf8fb1d529feea21fddfd6be751cd6449a8d3fef5d6837dc0cf2a9c56017e15117f01e13924f6632ac9647503a1e76b2456606d529ebb8b5534d865d4c24f2149e641888475c2f7ccf22be8418c8d9d27db677c1103b37d26988e7b3ae457526df23d5841543993e47f9adf73dba884b6d2f0b640ad3308812123d99ec8c1476b930a9e8bc336fced8fed480ea10732cc84e9f5441da577f32639db5c3b4e569d01e51aa7019c2d6a920fcc0f552f57c41eb6c8493f67f1e559f2710c3772e2f09e7415f5b15d194c77c027048b0fbd214d12fe014ef835928c5c8d7ce36ed1512cc6e7819c99629a11af5b46090de3a7c6e168f891e4dd8fd6e46409def5fcdd74e37873a71c5228d333567ce70d22f3a904a5aedb566ab4c23b1003b8303250e336fc1c3cf3b0c420bef35ba1305c6152e25b1f0cec6252c35a0c2884cc52aad93b227ade63c325c5863595bfec17054475f6e734eb7fede34d99bb706611c6e0778256582f4bbdc3cc59f8047380046cfd1fa0d97089243f0447ed00325a752d5923e4a9412eda5ceae5d0ab6bb8bbaa996aa890bf8ef600354642fb3965e1b391762335d87ac00306e086f2f9bfe87b79c3966617471e1f834e20378b127d0b7d2c6e667aa651938ad063976e6e606f3001d55c3a9b094fa7daed0cfb9933d84cac9a25356f87fab35928538bdf6929cbef6bfc3ff18ea112ca7edc8deca70f37123966ec3b3a804d45b856e01620f7788d93d1ff3bc79f819d207c923b43d018bec67202b476bcc9a00fe0887e36114dd321e4f75c37c7850da9ca34c7dbbe4e257ae247e2a84820846cbef1abef031a821e4ebbb89515dd2958100c682c5b45cba7895b7401316d2805850fb7e2a2503b8c7dfa989faf3c5315d12c4dc7b1cfc76c4c5965c1dee5380273c9ba00158e62630693c309d1b1ebd2b9d1184be1fe2a809ecc9df0a82ebd64f97cb6d69d5dd1a3feda51204e624ecbc4c4916b7ef6df1ad9cd3e32582bb5df856a3aedce512a1469a005c1ec88919335b73be50cff3a48f14353b1f909c60e00fc1b9d6bb3cc44cdac2e07c5ad5bb878741acb907bcac7e53a7208d873cf5d9d4bf7d58304ab18c955f00d5a964c0045c63d1e5ec3bd9bf066d996556ae9e4865f9f7698fd69bf344bc6082174e7fcac4ac510af2ba0b8b4307678565ec84a8abef569074bbcaa5d4669a02cad1454df6083025ced5928e11d7e32c910f36058cf7b75cb7512d7856b51f78ad37c7d3a3734774b091516ac5754d7fb13c1d8202ff08dc67339a7784390428043d1ef985728cfa84f1cc322bcece05b38a99d391881827db9060601332cdfd86736b2f31c9e8fe483f08ef8040132c3f63e5c3013647d6c76a2527d5e31e06163487373c61c08c6f7b2c1e415b4bc2f41fcdb06d7c9f8572431e71ab04eef5f806b0aab6a053f255a45330bdf2527c30e6890aa35b5155ea189b1a4aa2d79fafa39873efa8c044c8c7740988a117726503f4d7cf32b16d55ba79afc92e657e52eaba518b375ada95105a82b0fb75a1054ca632810ce2744dca8cf5099d4d3283785f59eb2368cd3542bee8cbfe8268fa3e9ac8f1fb44194c003960e4b637fbecbb5b72e3b22a7d7b542b7ad0889e0badef3a59a865a08c277b8b9c2e171d4155a422d8c03a270d5b21f8eebfdfba349ff8ea3d664db3029f36d4088d71300d3c57669aa0d159e5bd56cfec2194fc6d93f87fdc65039234f7f55f361512158d0c87331c1600e530671e7e924e4835a6455c4435e9cabae0a635456dc0be4894dd4f219621b23d5ce82d65ee8826c3b5b376a65818544a4713f65727ae64dc17c2bbea9276e09e9ca530cb87e90f0ed1f529daec2f2319ae5c714093363bfbb1a950897212acc569eabd7bd518ce3217a7a258aaff3ed4dd3a0e6114cd464b20d149b2e722e693fd0d4d92c4d9d16b06dee757dfb8c054a3e921b0c0b265e5c5c25e6a7b3c3e3cd6a7d90c3664486e02b5410aee0c1f934f2553eae178507e28e7393e09e1900839d429c67e8f9141d462b8b3cc9b4e943837b08a7853385d72a500903f5610a0ad88e8c3e5cc600e28e32d6a82b211983712cac5b53bede7f2138f5423cc41f07cf8055928f212020d2fd4a2af5911a1287f665d80e2b5df9938498d8bd3088135091079f8d302aa7905028604b499286d599b82f47243e57dd9a3c5a3243b092dd774bd79b0e619c343a860c6ac13716adb6424de246b7d5addc6dd6bb5768ef949e109c36980c727cee9704ff695730afb08f9429f8f9b0c3ceb7537203438f5b82d7164902a45460898b98c5827d639d6d948d72ae4a9167f56602d5a4f43c09f8af8dd3d0ceca3dd957e804f8cb1672f290186b4f3221ed2159acab98030be62df1a20bffd15275a638ac0f9b92e0bfa7edeee9f15136ea3da6c5e4d805d95238d7a190f68be9dd25363f656482d1ef7648de369ba4375d49a868b81cdb5b0a507042221a940379d30255fd67d359c615979eb1aba65447ad605ca17550dbf42f6df8cfbc671075a4aca4c35cef107abbaa1c0890e4b122ea5b3e66c6f0435efcef71da27b8287cb3a4da5ade64ce347be34d06da323b9edece67e0ff109c94e1d7c8dd0e3ed123fd9326174a11a3b8cc4e0c04126bc368c64d687a6472df5d8e7cf08855a38688523ba0f887ae31b989bc4e5f5fff02cb24371c827ddf77a0de578af5a4b692e7b1d2650cf5f03135483a7c972e2e2626ec2305d5e3b72dab8723816b405fa6b21e9535a87f6cdbb7d04aaaceb93fd4056cab635c1a9cedd48813dc8b5da6a0d4699157702b0d329c4ecf00001e28c56367b7894993ff00d31e573be1010cf62bc809884b647b47cf401e4ecb2e66ed65265259179a42a395fc051ec9c71679f250074729b842455363e7acb23096e15389bf69902f923f6bff527b5c38bd25d4914dcc9d957ee5958a7b5aa2a64f00e1bfd92eafba1b719d3d8e671bad87016fe26d920c9b2bbb02c13e1cdb57cf5dec95fc019b9ef153e508b7cee8c6a5aaca1e454a63eae6d89f56a5a2afd71ce492c71ab8dd0db2144dbba6eecf7dbaae32118429c2bdd9a94e13fb4ec84119834547c2a0122573bcfc8b015a01392f7e294cf23d98be4908698c2c1f1ef7841291e6f01062de239c5f2e5ea32ce987bb7614bbe45a4b2de99d76fb04da22fd5d661c3d77eb76958d29c8cb13f36a4e3d0f879ca5e91effd091e04abc533e2cd8eb22d4323c4a138e468c2db579500ac821c021bb625812b986988a155b2297bc6af3304fed858f1af5b5c73805a7bb185b91dc072183ce00a038abaabb408de99e50b54f0957d5029f17ef9b1d48eed754fc75dcd831ca4aa5acfa1a93c08f0453d69ed2c9bd0160f41742458bd5a5553e9fb1593b39f27c4f41b61e7691efbacfd134cbbbf7cf85f33721fd592337a03a6131fa64319a82966d5af2efcc883d45b7279f7970f9bdcc442f4fcc2d2d9ccd0725c7d8e1da5ae609baefea5054a679b629e104c8c9e8e5b001397a571c3db891516a06b5bd6accf24d4092f121134bf087d8abe92fc3f2b5880e2465956bffeb7392d3e7f228204f468b6661e31acc971e7bd02d5adfea9040f0d02b165ee329d665653f9100ef3b34f9fe65971668d2e4254235593a014722dbeeb95fc3f583663e190c1d1387e500013a5074c9b0d4824ce3825163a03bf3c8c0cc5d46fb72c82a773727f68d41b8c94578189aa6e2f3000d0a3cda80d8b1a856b62519c2890ea155e55b10bdc92395aa5541295768b5392a06ec10a804583a9e8a676cd1b2cadae363acc11b092c6cf29e4f3839d05b67d938589411fee366ce77ee66731450f865634c2722c11a8114736d63cae78c1931958a476fd795638c96321b9624179c8c7961aa48aaaae74c8db8cb0f622d8f7d1b6d7945e4e44e937fa2972bafc0532d4c92c270e964ffd5418007008be7fa080e356c9ac836e4f403f31259dccc594e3d371f9c0d371e7b1b66633099e02db5439b5097de2cdaf3c4e20cca1736655aaafb12240a50722d532ac53933acac67f27628ba3cc90438f09ac42fa8717eea7c99a2a98a3eccfc723ea4d26c300d61a78884a58845db52c301ec86b0e316cf53e039e310479c5580f0f4527a159525e1db0856b83d3a8f6a92681d74b58f263b7ac8fcddefa109f11288071093bd527ff6e3f8ad4772fe614d7f5520f9d35ebf4e4129286ce6c4c0246e776ffe4d0d082cac092a0045406dd6192d0f64ffe6772c6c126481fcecf01a28af06b9c0555c7a812f96128933e695526587020acaaab2d38e11e72dc4798781b1f29530dc9cfd5843b701d5ced26d366cf7967afd5e8b0990133fc7045d00d6f9b26c0d42016ae2b67d52694527eb16b5261620e0ca70909ff326f195ef76f01b7065d174640755cac9feb14caf98e301c0887e26ff86730e4d955998eeed96f6cd748ed7ef1bc340321de29d6299c15ff6909d9707519bdbb3453686d3409fbdfc8a13b24f20410e0ce5812fa0f495f6d6b20b46a99c6e00c79972f2d13770b7003823ea827eb15f8b3de86641a12b5113096c993a44ace677ea54e6f9c27a955ff6d06ed16d7a915a6f92b6afa5fa5f77bb6813b5dd341022d1458e65cac03d5ffacf5eaea09f8fcb18420d0dfcc1d44e1bbdb584a87f563734f92c8f10becfd93b643a7396170606fcd74e6e228f27dbc72e768416a2c23957c7712ea7a59fc3e9a7d7a01e845e52edbac72a7465c6641e74c9d61992bb1e48d36999d35e6f8a90802a79886ee373457416a82f078611a9ac45ad640bb363a4232ede3554f890170853f89bfa6306e051f7dac65c56e5ce59a07fbdea980b944810cd532f7b0606f8e5d936cec075f7928704b66a7b1d1f1332aba8146fcb8e16ee5fdb364716bbff9a74fdc039b26b7af475c24e889dcda82d741ace7c3dbcdf8a8b204c0ea7a9075292c8672c0e0bcfe41c866323c75659171677c66bbb04c50ed683f5a0cfd480577bdc786acc984d0a12ecb962a04fdffb554789fbc689c5fa043fc23d5e98083269ec3cb65daecd7482d5a089d1a7f7d1c5826f4d375ec3eca3a2932247011189556879749cf1437223291e0194cbd5085f231ca8c254dbb1f893540193c12c6fd06137fa0ca2f095d1f9c5a4eaa82d2377c6a0a4e88f6db6449652d05f9a7dedfcdbae042057bd9764cb81787a558f130fb58a1c637fd529f6d1579eaf0998ab5249f574d3f072916aea19ab8533dafc10484006ace0b0d197ad3cc8debbe44faaca649e5e246ecb42e7acc1760fba07dc4b99cfc2100a932ae6f6e16af735d8127795c0d0bdf0b1b82d2794225da6f7982e16f30935bf905859bcf3ec477771c9078fc89e00de4a8fd5ad1f7b3ea59d942e77551044e83d8b739011560bf0b92979ad7d5fe14c6713f4ae860c84cb0af584d300249c3035e2c40394b9503e4e3f5516126d37a7626aeb635e9ef64592b06d125082143e8ca78ef51182f1ca2e54e6c518027a2accf1bf717a761234f1684585d2810a4d2b6e72e49a8d7783923a3e622dc4b2b9758159e4b544b291ea40a12e513a5891acf250fc21c6c616f17873f935a8fce55e9a8ca77d694d06890b5444e6401674a5903d1811da0ff61b29abb2889fbe37c11ad58a3575cd8648d5dd52da8b446a085f350f2f85296fe2ffb65a6b6839894c2d2f1bbeeacbe5a02c1ea9c7f3ce990913363a35438b58b3ea9539b571ae59b1a0c200ed8319e281a966db7c8bbe33fbac37fc46db6a91d751dd6d37a5eb87ffe1149d23de8bcc021605a20dcba4809ff9b18aee91be8221a85d6e44dd367a78412ea9d9011d7a73622fc7cd9020dfbb3c61759368faad9396188a4b1d3696b2fd281ed6fe89778454930f403e6ca59e44a4ee47a598dd50c82ffaddeb38d97f7c956a222da9f5f5867c5b52e87be1a85101010186f827eae98fae019c363ec260cae7f9f46ee9f359adf7039f2d5677728d4c5b857108f23d960df781f4b9f02fa16ac2509830c4eb70edb9c1778205b22748df2d77299b0668691b68da7d945142737654b9292fb966b2d07fd3556315a901476fedd7f808448a117c715356c70608e3765b3ac0f19a3a608b5f1d2ce67b2aecf76d6f89ac477005fef1044cc9c0406e8ca09edb1bc4e05d0ef268d1e1a02d1b7434c112bafd35fd224600c82fd44580c4c857b8ee55b10f736e93634a8c496f342e7a8159c10b64e5ccf8529ec981eed24fd8e54288c105f26dcaf2e02693cc51cbff2dfb8fdc06cfb2a1b6924015b7ea417f684384a521d34e290ea6b97a5ec808af5e574e67cb5e2312f4c47a7f0e768a693905128a777d97ea384cb95d837d32b0fe1cdb7aee6d935966d7f97289e745dda46355ee4f02ef166ac55557da3a9636dbe36475b47906e18f28f492d0a9e915794198663654ddc8cc681a9640bbe5b95c51256510ca397f252d84d90c21bf925911fa9813706eb7cdf6f7bfe6a35afdd0eff919dcc5b4e55f816bac5532f3f78df2d651e8cc43cd16d2a48e2ad5e8ff14964df120619c8e4a3f4e0a498309a135a48972097a991d8e46ddb4a104ba651d40ab1c9b192e63aaba276eaa27b79d212bf7ef0f7351c1517e003930fe262d01fab0c9be86c6362021684b63303cf","0x3b5944461642e26b9ea483970adaca91078dc8826c991ad82f55a4db6fc67958e37e5095016886dde8b5e898744a1d31950b3508ec98272558882c04a12d799872c0cd152cde8d36ba9bbf177cfe74de246c6c52246cb34859d5bb381e7d774bad777a4e4578252e30db080c039b75a5ac4b24670c6fa97dd0441d882224511bdd45ff3f2f152b46867627373d655062e602dbfb1208cf40bc8a462b796339b44f00b7768051c8019fed1dee31793022d2dae1fa97024787028f942c066c2332d4acd72f020ac15081ce3aa13a5087273351c9166db935efe985e0c93ecdc06d428e0e9735ae1f84d7d596e98997f3ef116d61705f11d09f9e98ef3daa336f68f140e1f1cc62294c6e9f06cfa056bcd63623c0e7aa435f2db11dffeb6977afe73ee765262cf4c392b8e661ebd8651a94b2caff8c7c78031d96616fbeabfaff090b66246bf098b7804c57420adc34cae658a1a7c97c313f9326ffcee7375834403db132166784769f63ed9be62ce19f2a3e60f0474184954f1bc8c81be661a49f3d28de71144954d45de6d0b19b9c177c41e05651d924c556973db6c3256fccf2780274352e0b57e53a412322d2631c4e7ece2e5a44406a949e98d8a877fb3847dfcbfac01e830c5322cfa5e0cb047f04da7027b4f726114c97627a0cf194d7f868f1ee8203d00c96ded30e515675d73a7c7af4ed5ac70369cf9d7b2cbd1a07648b5d54ff311a153a5c360ed89779cadc4df08c3a4af85b9bf57d0ac1974b0179c524afc7ad05e4e32e4109f7869aed197f1db256989324b89fb08a1653a477641842b553b3f4410a0bf35dce6d6cc38c0398b0c787609301c435ed04558e7d628963cb12311adf0d4ddf8fd96e258aa111558dfa52cea5d341c8fa2b17de199aa1c69e3e495cf166729e0003fb83d666a4cd10bfa47eaab054d6528aa7a11991ed246eefd306bcd29cce157f5c053577fb3262d61ea081d0240a21bb263ffe607eb0d3b2d84d6bc611a4ee382d6c4b981ab324d1d4189e243662cbe44ae320576a20226e18a0edefbb090bfddc96340147cd4845d080113af9d91aa9dc3f651349cda6a585c9b438a85186f82fa610f78db603788eec2c823ca2a0353b9a158ab8dd625ac6e588918b75c795bd7303943cce9ef47e878051b2e959e9d829c286da820d90b522d7c939e7c83970581b1a6722112d86fe43547535d4e2b8c63d6fd2447135e15226d82ef159e70943da286404952d7dba714e80495430f5e7110f4a5f76990d509b4406a7fb579c1e5b8950adaba0b1ea1ebd34a1c1493df20325ef2d6ccfaf2db7d904663bac0f894d0a89f240dd625fda55d47758f251d68545f2a86365723bb95a8ee5ac6fa0d94112320ae17c45341c700961649e88ff2b6688f1ac97b4d74ec2aed7e54e5c77ff23b25f3204014b101c5e7185ff4992820f6beec9bc09127b7793191d88a97fdf8d3578257cb5954509c4f6416894fadbc358f867fdb61de0f90e744fd1bdbc65e229e78c7670310c6710c1e64d7acaf2a28f96328c3694d4c116ec0b2924ec91a5c3520fddb8508c9778faf74da1e878b3564b9179b987ee7344a349c93e55a3cf7888a51504f6dfe51a90b4c2c10eee847670a2efa065ee125748e93dbeaf486384a1167c427eb96e762a6e202998570dbc23051b03a9338eb217c4722c5a765a663724ec52fb6012eeb5b70b3aa0b3bf7a4344452b5d25e6d96b6a6764e05c9a62a8581c885e12964d7bac56eb7041d9037bf4ad18703823f8ddf31815d23974fe6ba9d52e7306123dc8d1d94441f9cc325fd77ffbd47d875cf6821c43e9fc7585775fa800a9b068cf27556be343cd85e49a95d7bd76e2b5f76c5e57c680fbf287b2f79aaeaefbcaf82aa90d7a058ef1792be23177fa8a05e0f9c13bc95a2cba577bed735ddc8e3c0ec1c2cc4e1b8ce757ad18bff851f5dad1b5c8f34bb7a5e48c3ba1ab266e66a24f2079a7ba21510767b8a80a431ecaaba70a13eea1ba5784800ccdc7968e48255a3eca50aa0a0a9d964ce01c0de28745e6330a52f2233d40d46b39de2d31e198b3d8058b21bc7cf2554ee7bd4304720a334fe3fbba537c98eaffa340dfbc8e27440d215db3fea2b41a13fec882a26d1bd8fcc3c248a232a26a6c0ae0968629bad782b4680bbc443fddfd508f9d33b6d04390aaf53f06e487bd36cbc9ad1b50729d216735668d2bb19c2bf3211bfa937686d0fb2d190bc90095ba4e3d765c0215aa5bc75c179ac9a4d72fd2fab7467efed948d8d09f17753e632e2a3db66695727605d51f01c66d4a14e1b4e4de8a533814299bda72048f85e0b7ee2a24504ab05dbebd1520305f6a5c359732343b913236b624da860296c3859016f1edb011d720e98d5100017a86bc2ef54e0f82b593e14ad253a1d1ecf8123ac27a33fe45326234d892448927ecc6d7d66bfbc1341adae7451ee146a53b0bc337bd89b466d82cb395d321b0cb080d50afa434e8acf38e545fad3323c66c361b566379521e2f6a85c0cbe0b101bcace860332f8f404788cf08aadc3905771e786e3ffe221d5b6c106bd15f2e42b4c050f87111f14322fc4eb9a7f4e62f1393c935040ad8925366c755d29187cc1be9d1f04236aaa5f14051ce42e6d5bb9b283ec06901f550c0f0834502f98fedfccd9c1322ac4bbdd54d3d1f75f2173a6eb4d7b8cf3c09e0e0866e9c3f3389fdcd96254490b5bd696942536c78ac8aa6b6d10f47a288ba117a0877f71d6c72af866f90cbc44972a908e12c8b207a8ecd45608c5443a49fd92e3c5dd2fce5a68b790a2ef58e9167163e11ce7231190d424103e424fec6ea76b84749748e73ccafed1d080e1b6dfaefc15a4589d718d09d10a272719717e4382938f4a6b902122e1ee07d19deef889c606cdcd4125b2706e9e73d7b60323468c47e60face0ebea5d590f0cc43dee4cc3198d8f29799df2ebdc2e9c44fa9fe1efd6c2297a0280def55f07fa4da3496abc455cbf563df885525e5bc668680cc19ddbc38e1297760f49cc3d622a2a1edc148bd728d020c0def0748b52ed5b046cb6b09756d1a34bc126153078ecf05e77c460d2a1a5053960169a6acfe48a5c03cad53740bc62d969b50ed77bbd5f9db7b2d28ea8d53bcc564cbff8c1a82c19285777f27e6b143f4513963dfbcbc470613da15da3ca6a23962fc1595589ed744e3acd5ebfc34ecdafcbd885180de6ed2ab367d6fffc1366470f7efac65cab088bc5f80337768b10b6929b85bb5ed1f02ed482e1f4878fbb101cd58ca8f228a04190511be94f2b11374bf4d3189c4c29ed44d9a9d0ec74800a21c6065c86793ed5721fd603c0d1caafaa52be80bd9f2bb2d0358afb23ac963dcbfdafcd03172722fca1a606b87ba5ec42035c5505dfd090ce0a289380efe3ea3b8ddaac3b3bff53ef0265607676298c664d5989b01653837c9d6c28045bba8345344c969dc8618bea140add43e83e358b8782b2d076128b0c1f6d25d0fa622b5b02ea61298c994125f6da793429e9034ef10ba80d25cefb9322724a9d7bd9ffa9c7e48dc3dfbe7562914f785a1c9a2599290820c7475a910aa35b3b993cdda41a75b44a0be85cd1b2a83e7deb52e0e332d7360d7aefe1b38580e40defdfcfcc7f2b19ff6ff4489eeb26815e0258a46944a367975dcfe270c2c430ebda63f948219afc600ea09026eda58b7a429ae1d6aa5daf83daeaf0cfd238a35bbfde4d96fc84890ee1218acf82b4b76ddc602f238f263decb33007aa1503e82cdb30a493972b2526f9faa433db61ed6fef9fd2cc26f5bd81bbf1373880fdde5542624852292e90781e36b573844102ea472ae116b1e4f31d936c00075388977e8d17b907e7e2d824f8bfca268b3453129bf36db3e1751ccab2711b46d5288042f27a95225b77fb01598723944294607bc814b2ba6fc7531996dc4f84969c5302a7e1e6fa3f128f33249d57f38617ff4698fb7c82de854aaca9ecc37a7c74c27954e9e6b1ee23380e61da2f7be6bc092e8a7d7adf6898b6e4a79d1c96facd1da3846c02627e5eed762784ad950febd991dcb1d67a9673de08975abb95d0e2ad65750719ef43b6d492b0fb17e43e38e37a70f8fc1064a8dd8c247ea5623a3d09c93236f06c1810787ed45af9dd72f2ff9e64345c96ff12504868a519823ab639bed75cf2e435ed74a49bb925205a601435191ee17ff1ab34f0805574ee47523a8c588783a7e3e3c0b38d59aface0e475903d04c730db966640100a525a42e7713f858a6a5610a59bf06da7cde2469aaf8e136b3bb3b0375342de49a79be436a487d61679cf36296b31330e0aca380b67ec29a8e2a5cab9897c2edcaf6c6a629fd2c8921a1fff11d6ec2b2e3c4b7de5ba2607ddbe0d4158df770187a0d873686f386860a8383b95ea357a79650ef0f6cbc22c3aac555ed940cb787263447df7849a3beafa4541e8b3aafc07d2f85e623a0ee04a1b4fbcaf4a9a497fcf90d619959ac666e92b79e1d62fecc10b16eba9b0391197bfc3af5749a59725fe9be36a72b8479fbe8e3c24db4131d3d1f76a6557879a0842d2b69e28cd37e643a43cc037577e5df8baeda9b6bc770e4182c69b4f3cc0d1f380e7b9010dd3d5855260d35292030f42bd4884ba78d385abcb9df81e710f640db2c5cd2a12f3238255058669ad9b0ea1d9ef1e3bf5c48e7a1dd0767eaf01df36262bd81c61a41373ac710e429686cc680db9fafab59ac48a69d1578f0e020d3b6f1967b3a42913cbca456a188b68e5eb468d83d73fc2b2d857770a6139e5793b87dd7202c454d612da4eeb587ca79de1fbf21e1b59e2cbc431a5b4beccf0579f23e5dd78860d875fcf801d87853634548473a323ea6100c6bf29fa2794cbcde95d02587595ef37be7532be91e7484e93667958ba6bde4ab1213dc4b9ac04bafc92e121e2f6408292e4239692eaaf1ef2f87bc8b0dcd2819bb45e9cb14bf99ef48c0f50e9f72546a945029005ef54ad4b8869e96daa4c64bf84a7709307828335185e8cbcfa7eddc349c22f706a096e7ee5bed51928cd428ec951bd3db9b2724e1c5fd4e1597b55323bba1bd554bc3cc2dd72629bb27960f910cfe576928730147d991959d471f54df269b630a2180fa05c5cacdd1624a50d496d0aad327fb1affe05a202dac509b9692141fc94c543e83e88db04d5ab9ae8086ee44989dad56e2904976eba58f9537d798cdfa1e2136c3870e88b56d21e3f82158821f2722aea45e109e3fe41ae7535ff5693d7b84d7db84acd2c3f0161cc2dbbd23b510f5afa3b2d8a6bc3387e5d23ce3966474e3e8113dac4b9b2c2679de00f6cc10e1f5fc04f446201e0e7a1b7452991a0a383f855fa8b8780ba38b8ff7e706846e9d0bca248a6fa0547d22c8213bc860aeab589c4a732e1fe5c522e558eb66383748f763542dc83fa2a3215e13d92bf4685d11730b0d57cc5e94bf7abab73238395ceba3e820721bc77aac04c0b6b10b29aa6a2003e4df4d89d0833ddbb9e1b87046457e983073d53d9c9014a1a32ef8853bfe0f824a6736d4be6494ebb46f50602094782b8b941188246e9dc436c1b81f3f4d81debded5ea2a102d6c7cb097df92c7e2b78d4a7f98a03e9e22f214a678ff9cd07db895760ee6227d102870c1e15da04c5b208e4bbfb4bccd8117ad6c9e796310b5d02f01cff1dbe44c0a187f77b481d870b0be506dc531aacc56e73c2c55af64773cd0c332c7bd9665fa78b317f9c91479df9999106b8cc3ba500a6def091fc4bacf7848eb20f6b5ef5ab3cd82099a5efaba01795cb949799abbaacca8be77efbbcccacd79833a2c897a9045ccc4707fc32a7d54e9d855b8be788c4d8d4ff800f77aeecfea3ff8436774c47119fae2affc34e567dcc3d120cbe1be580767a81920f64d6b55b653aadc937a8af40d74d1fb5e099e954cce9ccfc80ccd967befe64b59bd1f51e93a824850d4cd24ba808969f2208d9b9dcf2acbcdcbc245d3128b87798157748167f09eac27b7b46537a302e6cab9bf2863e43146c0242c8bce7967c46ab3c636d80bbdd0bb98d2a59dccf3b38b2016780885c9a3b333969c2495a872fa0066ffce21015e7a1517e6781e9414b2ddffbbf9276b83b1db36ee22b33cf0968dc3495edd10e95f8f2fc8f0c0817c9e98e2fe72743cd9c827e263505a525dba78aab05883ade83c402cf9cfb49fdd3176c460252291f9d170d3eda8c086dea4a1e6c5e547bc5f9c7f3528375de008f275a8771b3c49e32b0f592fffd065ff9ce6d36e30939a088759f7537ac8ca69de73c880d1862793a2f7193c1ba21a3802a8c85d7360d1e9427c19a61072eea7a797d8ad844857b1894df2e87192a35e88c5734994204cd749b7a15b9bb308d412188e7cec46543f0764156bb3acb1bf67ded31bb851df40199cd30056914b5bf4f508da8f16b43e25ebd0c06dfff6eed8e31461292ce3d8c3e22c806d7f0d9ef86c98a9dbfb794f6a01d08675894ae17e54be6e550803293a0c76fccefee0431e26cac7fe39c999fe150e1d3dab068b2622519b7710e83bf4b6a2d33a4cc5ed81cb728bbc21e70fee5e7d43ba349307b18ef6b4fc04a23ecbea0c0957d5a0ef6eaab5c103d7b34531d991313a6abe353f79cf6095d78344822627bec3dfe6667594c9818c89f13432ebfa2c7b8f642b96e6370887ed1bfdb578baadf15336790b325fbe784ae2ddeded8ccc1739ed605c23e5446580241f9005b8e8ea60587561ec1605ef07e488fe10722d25ce925bb636e15bcfdaa00663bf7e20c3b97d636b1335df98e1a525f6c90082f44753d4b93f563049edf3e43a8e6f8d8639d6c91578f49397d40b336035edd6320af851ccf1a813540aa2d02c6955dcc64e224842d6f60eb1de471068980a4d31f5ce9c169e9a3e03c86bbecc5d41349b4c4aeee5c6feaea37ab23037fa849b118144ca1b6b06d690de52d0dcd575ca520b3eb79b8b07a2bb571b05b54ab708ffac25082cf846e27aeab8b7eff370e180fb59dbc73593b1eddc3e415cc03d6e9662670bfe9bab983bcb1d76fcc6e7fa7ade565d760114ac45466d4fac9503f4a20535f138f6e8a84ae431b64b05efddcdfef12220d87240067ce0cbd4ad2fbea9131c63cd3fff778fda340790a4bd53e24963fc4efea24f817a897c2e6f1f4f84bfc45f2cc91bab6e184bdbfb0dcecd2ae2f41539e720e9c4e50cd3c02b64ed7f2c3cfe02d89ff9b498f0deda69eea2b7b2366cfa4d5a99fef5bf777b84f9806caea4aaed2154e470ae1fcf2b981eec22671849a2fd2717c1d0fc38ca38f8c91b5a88b3a89a27bb7e48bc245ca59da9ffa29d8412faa4f4cf1e683dddc6b6221478a99981864b045de11ebda3d5e2a92a9f23135ffa1678fe80f4609f67ead1b2eee457acbd17bab97ec122b6aac5d7cee8575c5e182f52ef4edb03e3586ce6b2c8912085b26f906c20871c0c5c4ee73642f6804d518394f8d96b91813e4f3d983e2675f1de1038f8d46311c5809dcb7a853cf5f5db672c27455256fd320aa11aad41b31c8457ce9f1fb9efc2d44c0bf14e6de11e4b0ef2eca28dcc41563fcb9f310483aedd99b55fc6f1f4e8f217be524f1f7c030e0263af8420b3e65fc2441d198709193e9591ce460fc5053b0696bee13b7e85622e54a8b8a174823dccd3d2a45fa01f604e0f8817cfebdf6e52ee1ddfd52e628d889b4681b605da319e1875e43f1ef7b7c0e6bebead7f306170c2955f34ac2e366535d04c2d08ef16fe96a76901f7e77e910265fc25e770c85c99fa651651150b7bb31238344bd88ba0294158073f010a6020d649097505ecc2e295904fe8605b6e4f67015b8ac307d5a9dd63cb6840c52a36ff8d58f0e2f720b23970da512d8d6144eb0614c5b575e4059cf3e923bc5511b79f386087cb35fcf8028020da3b68b43fb930719467ca7c63e20d6e94bcc49d002478bf04e7a5fb3915e8d49e86ccfb5c060d8ce85afc7f2999ba87d3a1aa5493da441af5ceba17133e6eb5dafca9eaa614f5d2aedb9cf7d61c32ce3cfa31525e121f93e1d6c3b8e738b6d1bb1ac7a179e464f36933ef624d4962dcb66720c1bdae6771787d6a32fe197fa4fee43e77439ed1d5e7309168a3feb4b984c1441139e8873e88dc63787119b8c44f3c1f09a5e4469500dca2ca4925579452e572f49a2c01b24cec1b6c9181bae8b9c41a0adf2f28bb12eacdbbf9dfc7f9ccbc39480a0588f592f64c5b7f9e00520375d8a19a92a0c9e509cea0f220679bc6a3ff6499eb1645f878b6cb64d53c4d2156442a0ea972cfd3288c1cd12b356f0c58c1957e815224e88f39677a797360af68ae274e11d5ea39f97f0e9a9a959d56d238adf731ac64a83d29432720138d0cdc386683c38c7447f2ca94c325e9cd1d9f9181558b04121cace9b6c4fabbb30dd22d0267bbcc237aa7441a6a90588daa06b55a8512bf6aeb913415135f647e81cd86582d55c5e071f7bb3cf9bff5f8b945a195d826e401db764049bcf0f736264cd6dd10fbfded588f11cbbd92eb28bb806ffaedea4eaeba9cbc30be58f6e08e1ea175ace31388f3f5e40f4ed4aeb4550dee785fb66b5cbbce74375c3b279dfb8dc13cf390b97eb2bf604b170cec5ed7cb472fa2df4d5486ff0ab51f4b46b77f2f516dc0684da41392df2719dc8ced62562892a2f317445423808ecbb5a2c29fc7559c23272122a3ff3b832cc82ec1a25d2d71db6722e448959ca1825bc3aa56c071b05079f07f08ca6a92ab09cd590f2de4abab9b694822a3f457e9b0427d2d6c71a263694b4d580003a1d7efc9512473596c84cd3a9e357ea316d56f318fbca891e7223a4c725d5a3863a5cb20c1472a4b208464e68aa8a2d2b491690484e5f4b6fdda7a122168d7fcb8fbdb86e895be2585626053c044c943281f6b583abd90c59f0e5335d7aabeceb078b4dc11d6cce89ddee88856394562a1856ccabc2544cafc3fef241461d48ecfb3b7a62b9d5ab075fd9151aaacb855cbd184437827a6c460dba824ee081cd785340438058f6a0da84245bc223b498f474c235afd5ad35753c73b03644bba25e355ee1a031cf06cc4666dbba50ca1a82d02fcb31b43d729ba6dd3160f56f570f48e559172ccab8ed5e168a3280e38a7ab7ecf5ef7e7b5f99c3710266c1e8962282d9c493a647a20fce426fd03a1564b4bf6b51afafce4e547a95859752f46dd08a7792938dae3c4a70a0862b7d2fe3ec15a0f649d71f5b357dd1837692ba5178aab42beac1f88d7aa169c351c2535a43f6bb742dec3488a8b6a5d139dca9071c4b9c5b40cff57cbcf1bc1e41de72cbbcb41519dae8d8faa0c5ceee4affe7a9e308ad0991f2bdfdf5e96dda72a8bb11c055752e68bae839118a0e35ca11f146955297efac700fae4be079ae4b7644199bb64ce2ff7e9f9175c1c93d09134e06229aff1eb43c90f6a9dc926ba8657983fc459c0c5e14bcc348b30cfe0f5e8d4f1b15265ea3ee2dab408d72b95c77199e794c862d55f659288cedc8b817ece62b370d969cc2091672004be7fba979ccc109c5d2914b6d974446115e4f0e6736fed0c03e03339300ee232ecd15c9c29b0488c5e5b5b90c63a5ffaebfa8180c35b864d3976d67f8c6c5a33265b569bbd7ab3677eaacac810a7e4333cadf0c5e1de7144eb36ba8458128bf6cbc11293934f0389f348847b1642325e398a9f3a7224d28305f4791aca35a7a4d15851da9257616b71a793f27b1fdbae8eeec8c4f6640888fb8e8905ad62d1cf7843c4f0ac9533b95caad5dd486f4996f05d4cbf754b34449d2e06b9f8102afa136493ab8e09c79a5dab7001d6ca962d150ba5673959d0e425153b8634817830f07bc5005b03482b199a19837f20e34d20e380787f377bb3620f263efea77aa116a1876a5bf1470395fb52cdb6d0b34edaf356d7cf0a0c61c9dc02c004cd27c1ccc5783e8d44e3d5c5e60d53da7df0db16d614a84e3cacd12e2dd263092d515ece1eac4c4a5cf4c50bd8841631bfb6b2ca09916e9d30712e10b283aac9f84d23898a27bb1134fde6bf32431bba8c07f6aa4de7b1177a2fb09a024a23a5a12d87b952e5b51f7e99ebdafe0fa0fc17f7eff6527d752fcb6926a743c6e917d738dc37feb0eedc6981635ca2108c57a8f22a5dd03bd939069056bba0302fdf1ce1d98780fafbda115375b01ce57651b0a27e7165174bec3ab27bf0ff99656e15af6ef80df50078acbd59a86c8f881f520cfdf8e6276033abdeeb6b6e0b54a04024c34b77c7603d81729199b3c92e0b3615f04feecf2eb52f7f31e58a3beefe1619655a104989b2f7e4d1ebf70def96b8fa001dfae4b3cb0fef5b0f06166acfb4f0ff0f433a3f3826ec30d360c2ac811e35ea8444b98a6c6285614cc42346e1f1936808f7779c4ebe3efeee06ed7e526a0ece9d00b224de2be9c6eaf5504305a1b4ea7905b6d8665729885b1f9dcfbbeeb9fe4de46e81449cbf18395cf85d04430df7fc894c86bb2506bfa37ec408033bd4f0032f71ec86448a4679ec8bb38a59a3302ec74482bf58a78130dcaa3c42091d91abcaaa9081fdda6bc14997dd29dcfda7841769c9ac9d1b3fc1a11b65486409fc44c4ea512d9cc0e86aaecf84ee5802605b7afe918e64874a00668bea424ff068d3b84df4ea96495874fa52abdc0c2b033956e48c98f4ff88f3b521510720df75e5f245cb5c8fa1b3643b168f0b905d1a03fe7459d6b7cdd7b5ce5a927c11b5eede43bd0ff59882038440ad8364ce12aadb5ea612f76edaa3c1af27c5072b5ae2fd66328acf4004c76ff2df85f015a1c42c5d2926d2c9c456bb39022b24a4c5cee648d01cf9dcee7bb20821513b96251a62163be132f9d7b899e0746754aa5605f4a732eb6184461e7a640d57514d63162d03143f6ea117bba209125529c79b7d2cdb7355bb96accaa5dbbae22400a6e349e45721c101a5b0bd39a116f7817e36e4457d3a5224c5ddc9eff6c6f966e79168efd0fabe141924b58b8ef8e14b069fd1b7f747e9abbee56bd25af03d4570a517400d39ea8c17c75c5a8b98ffbc852c7c209bbb0ddad7f6abbe16abfd83c9ca02fe8ea56d361daa67e7cfe3866f8aa81fed7009e9d61b1dc2e3d66f12e2bee69329876ef8bfe4fa98a1f5d5a6fad054a8400cdbf11f6497974bcbcddd22783fe4db30a6ccf74e40a9f4a0d3a3d46f5b2e35661fa4690b33295935f94f48523f1f1702fb24f3a84c6662358537a1f95e0161b2dbf16db7dd27ef99017aefa587a5126e253aa00817cc5a21c65b79901b549bb59ffef80d739721a68d2e759f2ec2034142abe8b0d3b192e49d0904adb0caa7140eedb391a32c9a4515fe575833addad29bf2952c2d6bcae6546b1b0c80b5330ef93682b3ecd756d8d9e3490e487cffb7a893c961bd34cd90bc80bcbf178a5bdaa2f63a36256dd33ae1cf927aed13eedd3f5e464f8da704467f3e6e4832b9f1b686a8ca947d2f01e1cc9cdb6c2a0812cf541654f1ec92b65bca293c12f8703555259b07fd50fa52793fcb61ac2cee1ca1527d9c3b540f484dedae813aa0867934850ccea96482c47a2cfde00955a84a65b54d69fc8d5174cb809dfd62ee26ff15c842c0b365193e92cc115a2e74cc7c064b9b1f7c8c52a89c894e4adf8a7ddd4547eab9c095aa4d7ebac5187a6acc737c1a033140a4086749eb7c5ecc80c2abc2c5d7db3853f3185e811a5d98683017a769ac457443e17cd61b2414864aed45392e8306dc36a277304a968f67ef23b777c365d1314edffd14948a77915f2ab576973d09802420f1f734b919d495633063f8fc25279a4cd4a268e125128f1a43c468e4c0c2529d9cb3c296c15e67bb37220a4d94e58704586d80826cb6e7514e0f6899a3830121a4aba4f3b0c19e5132cf4831b84f8280976d41b59c0bc055b81f6c93091ce9d19c81e806cd5e81c48d883477462861b97d3a6c735ad6c02561bf9e6ca86cecb539cea5564dd1e1cf6a30adc162b9242668969b81fe4dad127cb66a19f28a13b50ef77bd17d4cbbd4484b99d0fd8900e217151a47e32e91c2952d8a372fadefcf02370baed1bc143544336c29fd859be40d621fa65a47c1b15e99c152c31bfef9adf557ea37f19eb929c1993ab04a7478b12b5df139bfcf0c28a36fddf52a1b5d99df9ce2c0fad905f626e78c5689ccf35087a80e1dbd818fe94dc726f2a883161cd05bdca489b65aca236d39f733cb3ad33f1ab7e9601e34f1ef9a22d1a98988e8e73aad260c8b39304e5a7ef9cee1f4a8d30c492a26a00cca7aca0321c9d12cb4d13a1df8f5ab21025e5180ad5ff3b687960fc1c5e8e1f32e24608462a84ea719e09f967cc3d6a4e8d12b1c6eb1edfb39340e72da7e998f615d0718b5ae04e1ced4d4ff139a0aa3ef93d7e70a4e1da9e38a055d4647e2b045e83b35ab966a274da29cc5ce5750d6b09a9fbcf5b3fd3484ee78457f450c9484d4df4edcb715cf351bfb9051bf40b322791cc97fc15a759f48e9d1899e9735fc7538ecfb27f7927ba816aacc01823e256a046be7da1689070a381b5bed628e9cc69c13116b17b6ceeb76f67a2200865ef77cc8d4d0ae608e447eb467628de66bc9c359a614ffb2f0cfc1f158f10ed6cb32af875dbd6c1791330dbd318f48ea610a94d8b5742c72880c16d07762ed55594c395e7e9711bedd228575462d45715f6834915b19995212ac90de47485f92c7bbe6dc570adc7b5738c78dda965a38828d1387950044439e0abedf2e9653d90d08cdf33a03c19a3e9cdcad014c766506b6dbd47895ca8ba1454fa542ce1b25720104cd1023839b53af3f928c4206ea495e69da52319f1ede38236dd804f2f3b222550a1d59c0fd43215d13d0419b9be9dff27a00bda9585594a21863fccefe5a415cf346bc9bb4a43e28899785fe734452e63bb10e1d7884f926cbde2b64d2a5523cdd8d6029d09ec1b66b91623b562091d1324b594d2dde2980300e73083471f031ad62bf0eaf00a16a8d3839b87f7c22d8a2fcffe1bd51b696180f6a17f73f45dcb0b541edea5f33dc7fc127f09f9efee4b13ab4bf558136b37d1d935cc1a134d876ee07d68b4408d0346328cd7ccfba87ffe6d4f0ba28fd463ce0b5a80888ce69e363de27dffe41843af8e043d1bf9d632ffb600c793cd30383e9c4546e8a6aaf8c66284ecc122834006982c009d29a66b5dddf82d22eedf0fe5416b6fd4e613cb96737ec5a1b388090d57cd3e26607a0b56b7f45263499436051a0819e2ff0e27a8b65355989b75cd81b9b55a80c169815f8757562ebbd71e85989b60dda77b52550564acb0d6acfbe19033f6e4a8be4c6289a8f669d784e8359968a8a74caef27e127cb7de4f21b202829d002c7d784f8f9c0fb16cd64a37f457f4ebe9f8f10665e845e87055df7bd80ccdd2fe13c9f0bdb1dc7110f42d599d528d240d4800b3b499d06dde0d835f08a5a05ba2d8980f7cf901d55bee05bd12bdc0bd2a268bd9946912fdf345bf3d1c8d765c2a4cffc4394893f6e24afdb8c53ce119e73cb786bb9d6129a14fd97f74c7b5b5279dcdf63aca4cd3410c14a185168626685a9e2ad6c4fb4ce88e291651f946c3230504ba38ec0c39528b9f2d8ad60c01dad3caa3ce7a7f01b3c09ec68b8d16c97a525c85dc505447ff39c531f4bf11ab33e1be19b79b71288dfd3acbddbd976f6785bf337fc6c97fd82f02a23efef8ce2d346da76665bb8388f891c0ca6a3c5051afcbd6d573b766c682a4ea59a868949d98c1d5103f27deca004fe48ad30092b053535976dd8bb3829851966b22cd84a54015e661bde6c388daafa38e50e0951862def15df95811b6cf7f446c50d67f6e7bbbece9000ccb6e9a047a752c5cb33b812765e340536fdd19fbef1d59f5bf5af978bbe0bb116cbb4c341f9298fa04faca9b18bf9457e158616385146ee2eee46c039c8c03516a8b5737065683c344cd49def412d2621046e1acc9ca2a194a9d4b89dad5908dcc1a369d2b3ec8c5b8238046189c2a163b08e79c90fda157d7f6ae2c20d13a6865fc6005341bca8f2f7c025e5978ad39c69fcf5043c71d20a9d6d87f9d4a45ebeb89f5a7dace09b80c55b5659929f1355d3375b8b635ec3c3317ac0c059c9402a40ef5cbafd8b458654090cce6c07a99a26cab1652544f36af717dce5557f7fd966ec250274c788c2cafdfbc6f5b5196708adb8385e4ccdc907ffdb775907d105c4cdb26f7091ef76f45df8e445b2882a94a4b6a903ea7c20ac6a02b9ccf44d370efa7d4c5569b3ca4c92e50ea05ae59980d1c0b98143ebfa2fdd6838cf636c2216da3f32829418501011447bd72d737e1d499f90d12736dd607978ca0a2f9be02d60bb0f3c8afffef0ab46723f375df0100049d6691f3adcf444105a3d5b28af769c04ed599085b840d6fdd8e5efd3762e893b11fe27521293b9e257aef564ecc14f2a2472b9a4f0c45518650ec25caf4be27c516d98db1c2c3cb12a242eb185eb5d095cde7eb13abc81c753a85ff44674da2bd2fa5b907a62b3f6a77cd875753f16da681fd9fe50596c77112b71ffae27cc2504b22ad12fbfd9ebe51e2e95e6497a465f4591b8500b66974d4b3ce5f5dc4ef8b8d24af870c681b126ec818e733d2f1f95ad344b9898c7f2dc5067b9078e03a4df475115b5c69185fa99ff48db7add39b0dd9f5ca77a5147f54e946caf3769cc93d80694ac84171120b11875030b16e195645e8067590e300d06135eeab874d061fdd3454becf588a1f969415b38ff2548e2a063cfc9db77be59558032ccdef62ca46023a81d0de9d22613c24413174c28eb50143658f656ef5fa65b4cd6e1e0bc6a20c057d346e13fa2f6ca87096b9ed6c7f0473018c808d354e40ce69a6e98512217380abf92c2a00948c33a558c2f189e8162948ed883da37e5ac4983bb227a1a9deb9083e02119e781137d8c8ad02f156994813b4b21ac6f2512b83f6d39d1a127284177c2b6db6f2898df1eefde40572cab3ebb8897142d4c13e426c9067f5d557f87523b6d6dfa261f2afeb1c7183b5ded465e9a9c63b4b1c5bd0c6b559dc4a1def81ffe65b6f34c585217f7817caf59e188e247a5087d34983896a8f19658b508a0d1e95fa52c3024bacb57d577eafdc7165885878ae15eade3b9bc8a5169283ac2328c229bc324eaba6fd3c47773c4cd36d42ef5ec1ed5435c8820bcabf3eec7adc5e46509abe08f37f278511fd477eb746357954f989e576523b51ad1d4056e141ae3a4ce5518ad17812d272571cc39a590596602bcbac3402bf0946d21b01104488e7eeb76bf2daeb07823caca3bb7f965eedbe0a51632553df7ce0aace51f9bd79637a3a9db506260763d42d363d694085495c3bdc56a2f5a5079b36bff4d589abb8295dea7332cf83cfb4998ff6e2c983015d34f0192a3fc123eaacfc299da5c15ca76297239de25840ea0686f578c1878397f2deefdf2ade781624047b9167ca02953a2dc36b37f26f98045cc63ace3f90157ba84a07ea1192b8178606b5c22f5308acbe942599eee87208c8663fbc4c40952d58338c04b533433fe6e34f9bdce10850b85b3770bc20432d0bb01f99173d0ae7c9207d0952b878b46fdd6b2b08cf125cc53571faf20c6841b12c78e09eae1bf400948bcf894def6977e5577759db973c936e87136a785325d4876ab80c40a3d37acc8d450f6495f1e63727d2545c7bff7d1d918d523b097afce41b3e28452fc9270afcd21c282893e90e3272456a968e6f738b19d1dfa59ff97835e2f37a833ff31d77480562bdb2b52e931515d536ba7b5ed6bfdd2ad3034bad63d50a1615fb057d4160c2b03b87ac51a9b6c3e99c130390b5e8697755c0f4f0d7848fd1901c2ca6becea2aade204aab975071d67a976601d1f40ed08289d91ac726bd7953f3086cf9e27912b4bf9f4dc5b32b877b2896cab665c739c2ecf3eaeb566efdf09f02a221b7f2f9237a523a5d6af229bbd9037d549674b013df08c901c32a6aad860fbd08f1e85b6133584dceaca2244b09c63c7e0333ec879bad584ae78d560692cf34a312e973fbe1ecfc4d66bd2baf4af2806e24bf24c4d1badb0adb182729459bd48d5a3ada4f267a1f4f4939555f217bbb3fd36cae822487a50b2dc149789e72b78334143611316b8511f4d276498b1a287245737a11f882121bd0cefc0bcaf55ce939e129caf9263a10258ddb4d5b13a96a948f3ad0900f918917db752ba4d12043c2b1f754c5aaac91ff5260c063d3e7f0d53f26b781ab49acc30246d80b4a61bd03403e5ffc7ed51a60ee53ea70c0e6afc41826ce6acedd5c1e9da84405cccca4c32719f05100adf740c40a8813bad0b9f8f777b3070e8677270a0256acb1a1eb9633e1dd3937eab62e09f34a3b1a1a00b014b5198e66915ed1693429f7a126a7b2e1c352afb70fabcc5f1a56a13ef6de263181435cc0e8754d4e988a483645934c826b4e4b43416d691382c302e5bb8e806e108df70cc3a668bb577dd2cffb8d8b91fb27ab273a57a360531f2be411b03f851fe45a9527def704a2b8c8c64e17082fb702279301081d7e01eace0ba5c94e64f0489553358f545eaf26635d7cd7dcd4c15cd859a7e34157ab193635b7c098785b51b871672de92cd365b0207a1747b08c84f0e468901a2b9b94b6ce0e63311f0ca7164838b1cd6fec4938a12d45d73dad1e5f461b79318196b7fe6bade00d048d89d274951974ce5b576ead6574310b0ca1c8aa9c4e8726cdd52f253b4c4d6fdf47bce690e66420474af47754da5dc094dacca35c8297249ab0c8ce431f11c3f6c880f59b9c0b805a92ac532e50a9d2d2679d04c0cb6ce4f50360543ab6e5ab970528e16245933a67d09cef9bd51583303de44a42a5c5ef6cf82ec0b5d0bac5c88ce35210efd22e395ca03ce20b5b0ae023facdbbd04802abb26bb982126cb738fe2cb959294a6dbbbfea66fdb97614d1346d2e21cec95c2ec57383f383d906bc913499368a2d2b0d395a8d6f47cded7661d7bf65669d8c83b9c6c74f1090274b8e9f2f8e2a835a783c9a202f45da038eed04e7e724fe187e8ad0b464090286b7d496fa77274f7953ef1514f4e4709b81ef6aa6f3d680c83e2b843bfda24f91a4532507e522d069b8acba4a1e7b8cde8cd3edd2741099707cfec57579739f053092e61ba9bfcb65a2828ae99203b1a334d9f7aae7a92d81705da67fd3f906f91a2b051be429eb5e75b3fe79399cdb904195dfc9f33c3dbefc156c4d1f13036d33498935bd8b275e29373a063506037cf46d9eb1e2a892d91be66e48fa8580663430f7c93a9846b454380c999029282bc91fd764509189de7204c2bc6e7874ce50b53d08c304670d1894c2cbd98df6d5bd382ccb09dce2a1e599ae6186f8049d23c2f4b47a089db97f5ecc4b0fe2f40ac09de6d90937a9a76440fe8626754111ced4eba548e29ebc455d2c825c7a45da9106ecbaeb8f2531c78c15b1d7436bf7a27d08d2bd528176008cbbc656caf667347e056cf597e364687fdeadc91ace3fdce2740e831f84f7b1983bc9d3ede347a7e575901348c6c26a13836cbc9628687e3d1ed33690ee505c65391afc212545966ee06ebc5b2c56f9b5f72175b8a479bb5c83a0d6f4fb74b78abffdef47107761217f55b5d124589ac455840e63609ddd20d3e0e81ad87765d0d8f12f67c9c5c55aa9de226cb333ab170627c2e08589ff005b5e9dd9ab430ab6e292fd2a63404e48aacf2133251e6a74bb41c05af4489f8345a9038ab9117cf0afc7145e8fe6a06fe78b63f274b5851a3d6c4a63781f66c9a8064889893adae47a5f7076dfc7ab265756eeb7bf56073801198d6d948c5603e5c2a0b7262fb82ca88b4808959ab11a47db2e56c91e917397d8a252d9aeeff7520137fb6aadb21659df1c4d022c37c972bcfde690c65464e3afc3cdce89bb7d4263dfbc5b81b527cebe66a47a4cdd086a191942f5cddc18270be2ff6bf02b044920e8699665f0939968840fd318457e474ed78a69a6bd8edd17deeb1f16a00de0699bf1ccd4f9c7a10288b472c33627b762859fb9cf883c658f07f96908ebaf5334564b52dee6e0f354d95df33e65d14ce3f8aade18e88b7a3cdd770293b91201bf3cf0b64f2f31661c04f9516c53a22a1f5a95e3769b5682c5d8139350c4f4e475a75c75c50dc10a1fa019afae1190f7031190d923306fa4859c62ffe9d0277a065c90ae74abca716cfb739f06c5e725120c8edcf7ab1bb67e085d3dc62311e8c18793f6bfcf3cbc7312630c172bc789234ec5272e1491f4ad92dfbdac292a81a639ec30e9fb377ed96c65a4b598a43ffecc323a08ab7e8f4dbe28651ccf8191aacb2a7d72d3957137a99cdbe842c4e2ed2f130b01fc8303cdb45d135931387d22445c3300725ef82fdd4e7c75fcc413ddea6b4e8252b6448ad205bd7113032f5c3999f0cdd7516f7f7266c75df4144fa98781c94d031db995708d13c1c7aed6c92a7ab308e66250f5d4f124f46b7613abac61b70c0536030b54c5bdaa0c94f755949b08a07c36257f18a16463cd8358ac94593db7b0729cc8f2612097b128c7ba391e47602ba949afb94d8ece052655c7397defc526b53f902c7ab66fb770dbf64827dd6790487e69be6f426a881f96e76925655fc2d823da2e0afa1d06842939fc61ac49eed74a9fc7a4aa31ea8164e7586addf3372706e5f873dccebe1454de5477766ebccd30461830fa1898347c8309acf3f878e404cd5cf9cefca8e04959629c5987f4d3adf130787edfc374129f130e64b73ac249b369435b82363d342149045d23a8490ebb62853fe345502c92e3f8df5c0a6a8440e2de4b372197b5fee6680dbaf9c10f61a7883098dc2c457c8b95f2c32cf9340cc2ba64dab2e97752838629bdec8253c91f50f084e43f45d5a453f5a32480c6b2dbabab495e460b2f739f5e83a006a14dd5d519e75d16c8b68d65141b9e8379a448e9404b6f541460faf8141e727cf87ebdb12461d70fb397824463c4aa7b1ef7ba90c7683ccccab8fb594c6c314775488ba2d5539fdf1b74b22a01ccad54a0c4c0215e9f74fcc7433c9188fc0d7c009c8b0a3fc56f0d7f9b70954db313ef5ee0af0027e6d845252e16efcd8f2068d312a76f51f7a274469f14ca2cb7f5a159ed3ff360cdef9cea8779cbad4f7e09ca2ba5fbfb0fe1c59f1bee505c0a15b3291fd0a90fb8070dcd3302fb620993908511f9fcdc742102a354ef71d7552ee0cc2ff53134722a333da0bb5efeff17e55e12b36582bccc04c9e156bc4b3182da3f54337f95257bc2c523e455a3275d222934ad4c3a57c6f7b1e1ad869d8593c4e839b7ea18efd5feb916a344d4dbc217c48dac6a735127d93b70e5647c0b89b9d15678b1aa966e4134042e16cf6232c57e50fc9eadd17dfee8583bb207585344b3166e71b62b9a79648c4ab7f9cf1be349abfa6afa33778e963fd30f8329b5e6ceae68b02f88e5a72ed273975e17005dbaf12a740de5f40683b99edf1ae68f9499a2ad562afc1861198e2b2d3964d6cfdbb0a307dd5eaeb3a41044c8b87e62363643af2f03a4cf508c2028729204ac0ed39b1a90d2e8ac3a50ab3035e827bd29c0b3156a182bd7b35e3d2262ed7bb69f507a63218bfd1f132f52e9d6ca9a88be653cd71d12fda2dbb50afe7f038faf3ed382f2f12778430986cef71e1dd063b952f64cbeba27bcad33cdec4e915abb59bacad5d92eeb52bccb9a8ac44a6707d26a1a08ab676f3d23757bedd732c7489328b6a4d8fac2cf1f8f9ff842932b9237d47caba3f15719cbe0d2c32950ec5441b6911791bc15b2faad36e0593e69de0952ff334b3d241e46c5c716d419343af0f3bf54c80a437668dbee858be00484da6e04eaab530e6acac76ae86d9552bcddbc2484e902c55b5a76c96ca0af86f735772dadae0a3a48a1a29d020dba61527b59b8e481f9d49313b0ecde22e78b4a1644ed6aa53d243da8333145d29cb4c8f1716c73c2bac590ab020443e03c506e6ed5b957c595905fd1da46fb5dad333499d0cc2d045b35218cf7c8a76f65417da1bce6f1a4e237f9a1280f7f530254d983f4051e3e977884f83d6ee4a4a8ce7c075772ecc002b9d0b2dc3f7cdcb5fb18f0733ba991c5e12f8011e7091b86e63c6ed84fdff85b8e6c3234db122752e5add72e580cbe55c58e8ef044a6b469d4769625f80111e40915638822f5e44d54856351c28eaae15da1dda2a58810707011b47e4b4432b99959193dce5cc8982c4672269db5e1cdf100fb8f6b49183bd37cf0f90dd1684ad7633fffe45c0d38bf2918ba82ad117adc79ce65bad777d6ff6be6cd8864231023715253cca6eff2dd211a4cdf338a015d1e0c0f72b21bac35cf296c1ae9b7d4ce61e4736c52300f086874925884c58b4c6fc1f92140b888dcd5bb6bbf7adc8c186b1a80819a941dc279d4cbea31da2c1efacdadaeee44b03ceb9743de492fc0779f2ffa73c766d66a361ca2a55e0856ca9563b3a3607c02751528d5dfd4af81ff2eef9552b4f2051f4be3dc327831d6873a8cc96bf35e2f61dacfc65e9740f8d6f73d57c77209897e59f5e3907a5b6a744aa26ce956adfb61d260aef79c2b770a3d8d0022948059644ecf11abbd30de8e03d5ac29c47195395da50b045d9c2637206ad1d186b0aae32d24d6b97d7ebca4bda9542b9e74f86d81da12cae3233cd3c8f294e848f8151c325aa894a1b98f1589765ff4ecc562e3da2f47eb9434c314fa5500816937d7b67a0dca5fbd74b28dbde7bf6f3039d9a0a14ae51bc2a7f5b836596122f452191fb38cd93ff3ecaf1a0a7c76cd7de5bbf03dc21d6de838242e78d99c7b6ab452c7055734fd1e7d343ba208f85eb7a56a4f93a51fb4a5082fe2ec417d0f62c9b10d6ba729c1ac2a8ecc3533e429f499f9d927f3f2d75e1a8eb7271451acb4829991e2e6fc569619bdf77bf90babd9a2f52e922023101fd7e9e2a866d9c873d42d0c6e32b89a451514803b29c883bfea38f32396395e6f8b43b3ae638820f838f0fdb4dfceff850b6915e5e69f0bf259cf6c9b3008ccc4e5ad06cbc60f29782d6cc310f28f59428dd58b062a3f004e20ebe5c2c1c338ab7b956dd0623f3b89b2f3e260a6684f8752071b183ebdcdbc32e9c9488beebe0144993e489d3389d0cc6ba56febf62626b4200767443329cec665aec7c812fd741cf31cc9f0d33bf206294cdfe2ebcd5e3029552bada2f7c8d760bae9254dcbe24370b33368a1517eecdb759062be4d2bea0d6d28c32bbb12a4721dcf42a7277adef9a51128d518348f8d8432762d13222632cb92209400ba301089633342633975adfb10e5ce5e7604ac1637dd92b367d08f2357351d8745c3dfdd1a0251dae3738583dc68d3ae33e526e5b189051ad4f5679849c5aa1ca7671d7fa12ae3c0a7aa91f55f101b3ad7ce9b98c6214c46424883e635673968566b0ffe681d7fce73ccbfd2dc4d304310e19267267e9f07fd9e02a94307a51c25e48a0f9ec08cd26bf3334513269e265f43cb8fc7313d5d5e72bf16a465bf4d532788fd00ea8f7b0219318328b4ee5a609c97fc70fcc96602623cdb0894b6e1c751c7064ee832dbfda19176e2a5f901bf9b4aacec55840e62af25edf57dcb5816bf845e5ffc207113cccfaa46ba82e59170b3a6b3e646d744c161849a5a586528963dbef6d9b0c6d7459ec3a84928f9a650aa96ae0f5b783ca1b1917f3b270ef73c84bb28295fe5749610febabba1bbeaf5ac5836631b31487ee15904841855bcdde453b8071932fab0537e410f4e11aa1b8b10d82666fed0298916546d199c6be7d9604d8656771f67f66b9f561272e604239880d6df1b6b985cbe39816a420c8ed0e55428052804f4c11e9335888455f52d599b9f057b2c660c57f91f3634c7493d6a86674225cb7122de1c667c80eeb71ce460e50f1af351b67220fc76661553c0d17d2fe690f2601ff5b937bdf2181704d609bea464a9bbe5ba119f2e79822b5a649562dd76ee25e41cd40939bca96699dfda755a08722b24abe9e4ebf9e314b0f82f535457dc87d87f83d26c66dcf93eff06090ae7a7c919dd3c7aa100bb966072ef4bb1db1c3349d7879c222760aa204035abf110cae53d881a65cc20b3fb6706e8085f404039a065ab04802d615560900f44a7f0be701717b07ceae8d6d294f7bc39673df98d2dcf98bb6412305c5280a0ecda1255810df4e4ec406a1f700cbdb703dd77b9877f138b081986ca24cafc6d74e9338bd1c7ef47b3f282796adfc4567f17c631644b2925454c95119676562fdd3e7df43f1fe3b12667e4381dae448539bcd02fc2e5fe2c5e631feb9f4e28b5df881c814e9e6e136b001036265dcaa9b399da39284be1db551758e5b00def1963b99e0ea291f9fd0022c481ead97efde051b9d87cb88bf4c0636e343ec0639a09d9b86687a8529a328793ad0fef4c2aa9d448b78bd361d549c5b20b4360eb84a3a968be6a2594e9a7e9a02601295e026baae39d8463964b2dc8d664bb81ad87b818975a5d9e2d5fd5f7b8046eca9f3f770eaad77d994002bee61f41280f24450282a6ba974d351d889b44fdcad0419e91d9a1beede8a106e1b7a7510f16367ff0c1968cf2f1625e6090cddecc086251723cf90230701e01916d03bd79a32f592518a9b96c3f56e3504a0f17ef3255c5464af6c007f1a9705aa3ec6973b02829f7d9ad0da6df0a8b202cce4af23740df2f535da01559ab6cde7cc5cf8e1ead11f1f4655668029c98ec0deff3596df36dc00e580763da982f541412cf5fc4b7245141a25b1b7eb98aa413894fe9e9394c829627e2dc93cef20439417d008a52218ba40abc4d8070d38ad3eba230ea0ec972302ba0d2c96172cc7d5e208d956df5c403b78d31ce2931b6ccd303fb446b4681e905ce77aef2c59e9787d0a987bf0d5706049d1c95ffe3952d475dddef9ca31300ed2365babe19000c3f56f6c7c02554a84d4709cbcc7c66ead45faf935af4811e69f6179575398a2ff6e5a0e113bafc9c793a161f988dbc2330690a31053a976514b76d8d92cc6b74684ad3efec7746817bd5057a3c15ffdc6134cfd4c95016471f9a2ca23c9ae508a2e8401a4a045ede3c81bc0e711aeb020b51a05c95063330a6847c1aef28cdc5cbc99d210f3e20fc921cc5fa928c0ec270f5644b0c5e1604221f68ae6c940c6d168022fc55aea1832c81cf76d22a4e2d4dbcc7adf8535c667d46613fbf8220385f4d0c828632a7f1a03a2b97a5e462349e44b70ee01996d5981dbc2046b79bb6676b1d2a070c2cd90bc44f9ca2e054eaec76e87bb39cc89547b02ff014e1a3fcbab61ad69869dff5dd0357d32319051cc768519acb5aeb6a0f7058fe24e871219a464cafaf2ea20ece487f065748a9366574adcaa2006b2335d236471b2bd20d8aad00135b350da813beaa7d9aaf477eb61ffb5dffffa9f2dffdaefaa4d5a99a5cb7078f8e87ea865528de8e49203a944ddbffd97f597a9b5dc80795acb56bffce7a36eedd14b702f6bfc6be5a8eb24de776be4dbb1ca66ad1346e87e9bba190177ee754fe392728a7282f71715f34da856606b667b76a01d84b73708ca26acefc7e776d9e07a1ab70d98b29459b163a5a98e4713808f6efe9668ae4090bf6c24f28c6a46796bfab91c3715b801c0405f6a82bc7664ef01f87fa8cc645c20cb1e39fba763aded143e8a91acb78e37e807fb8f476167bf00e75813330a1c085125d61bccaf77658eec865e67d63b65706e2001862081e02d9996aa1eaace9cee6714b396069618bb76f49d53ae64c38bb3492b83d4d2327133ed2da7682856a51751e224b1b77d53f75eb0f9b285f1b95d147b657eadbd475bf5e45a605ec6e8848ec6c803868548ef1e95daac612d0b1d61d396ba2577157fecf2fc390e004130b239172bc3e5cb3369fad17476a7bfd0b8c2d8554eadaf60fb04749cdaae85724ed0ebe8b36f23a4d5591f5f68d14296770c0f8a2d3e295ff1a232ebb0fee7b809ae3e5455a526f9c40c10a4dffe5f0a760e763d463cc63a8c2bab928ec7a901aa81620e2bc6a6b686d94b2360a10b3c6efd7c320a5d037f22ae9ee1c5806122ec4303269585cfd0fd78b4df01107562f918c99dc849c1bfb99ee83f6e8a0b6a9debe5d5e9200593413c7414ba9127f0249451e34b4d0a5f75ef06a41de698cbdeed15ca9265f9a34568e8b83c0cc18d745058868fc8124d4e175ea84c317689e977f6e13c23918a719cadd0695a629b6d5145ec18eb797e064e9458c5773a3d83f5f73a9e58086ba588952cf7963fa4ae57ddda3ecee7b11811ef6eb57dc2bcc7f111a95c4dbe8a8297eab33019a8c815d8e6d826099649f2cc771c525c3e02b05b41b3fae2455a6767e224f8b38610d415a2496a49a660f8bfb850b172d429a2a6e88cc3eea85f5bb05ca62befb8e92603a5b9440cc52eedcb83ec9ad346fbd8be8234900f03c0cfcaf7dd2f8f8286bd94853d25b7ac0c8a45e5f2ddd867464cfcfd1105fac75f18dbf629f5e15f54258e2e9883de4e7297e306b5cfae4ea97ba54124ad4c9673f059bf881be907203462d5d2a79c14cab32d73d4bf5a4e5af60ad8e83f1a55973364db9052d7630ca9a9020fcd0b8385f50de3324fc3031c9ed92b5c688d4427545044a48de2c74e30f35c1662de50520e437e3f8b985e1331ac067b1849b3e81bf913b3e79ea2f03cf8bdaada35b398e01b463c2f7b530b5ce7ab4682476cedcfceb5a9fd93c49368ac61a026b9c6dc7718f27f28465adb6e203a6763792ec016aab91dcf77f2f461d5d571fcb230d2039e26315f350b9bc26251ee5a553ee29e39056c1786ac288768cb1e883dadc69a330094ddec257be1eadc22709d0366200fdada2388288568ad0cab4e9a456912ddb4b9c321d4c38e9ecf9a65a52b9940b76ba3d00a435a1816d093cf6d4d39bf2abbc4f1c588815d138f7fdc4d94b2e5d27dda7737f816429ebec5adab48750394df1fe177bebc6f169d73d7597c94c075d66f235174eebe595721a31004a165fd1bceae4245024265ccdc7e172059f0a0ad6632e441e2312f989b0d06831d0a32cf8b9f205ebf033745e905c868c60cb45ac68d631dc77c06a996a5a11fec249b4dad64163f91cc714afddeea1381b20c8c99d79ddeff1478112b4161554cd13a7e2ee9399bf1e5926dfd26cc692181f7060965b9da590e59816ab5efec57c6ba44d573893d7fc9197765a67ab05adc2c1733fc998e41aff4e2bf5d04fafc95f2f2158b653cfe309d8e141233ae0c69760e902364a35e04106ffc5d3fbde833c32c39f15701761935ffe970413286bed97290ca7011824ed673d4a72ec11bab17722618b92419d5f24b7a0ed093c0a67d44f50d963ab2d15dd73e8777d3da6612f6fdee0f78e2f5f24b1efe8a146d6662aeb3bc1e6a2359117060ac801aae8326e6362fef1ce643be2077c1696808580ce4e283d578dac9387325e553add1bf4ac1beaa3668d1325e119bf3b58432a089b72fa15546b93a1e526c9c7e2540bb5457f017f4f91577f2b65308788eb0adc8f638db3d295e0baac2471d4261117b9fa38947b39abce1dcd73b00f7d8ba3bb4c44ca3c97f42a0b9cb59b8725ef8d0d6e3fb563c7d3306758d8b458c26d3bd998dd8f35c084156736f04c4475d3089eaaed9ea02d9a5d2e623a47ac9567b8d6ddbecb0e3d2f353043ccda099fa410189aab6d35b7e2387d0373296c6afb9909a9ee668750ef6af64e87e6a39e15990e8e2657da40f5f0dd59b9b093674b46b52f2be82e1249ec5225196ec564401a95ba196075bbdb87e804df2984e5db12e6c5baedd4ee0f74065cb8e7b3561053f9b6a6d20bae10bafbaa9cd8ddf525b3ba64cce032247d5559c96e9913cb7cfe59fe3df7adb27d9c1174eee397d8f9f6342b6884a747966b1909be9e9a909305987317d2ccf5c98b918f80c1c186b1021edcef416ec0e31e57fa534eec6753f886d83ae7dfb7f846daad2bf25829336b255e2f55564977fb2f3fcff1a28356d7505618473fd0574f00818121669fc3df2539fd39a07242918ac093019a0d4d78b8f8b0659c29bee7e54669fa24fba84b651d6bfd511fbd98a427af56dfa3470f45304f7e923e1a4d6339cc4252039d99b95b86540507dc95b963cd044ed3d1a66850cf855da35002af1a5210dcb40f83b66aa89aec375773d5293dceb9a5dc90621278ede47c6a4997b02d2818ba39156c6457198e8e40d01b6e0464be0e791af61163b8ed0314246742f9db54f9fed5c886b63b7f3eeaf969ee04e4b97e620c0d4d75a575056b75821ea3f51007a1b265b3868f3b511dcc3ea83047365359c204abe76fef651d1fde156a8467af41225df2b6b50d22a0f14060ceb109634de3b3735f685698d7337afde99c446f3526d2ec84a4e2e00c3f911cced7c17758042ed096e8d5280399ed2e385ec8fa2512f76f2dddc90ef82154a82de782ca88c0e95495630f32d5b1e30610ced5b80446b7ed2c77d706675e82126daa5e8e7b0f35717814e0e7aced25650f5e2b41cd43a2fd0f23177196941e2e13d54c67c7e4d200cc8de50ec2536c31032c8be617cb59d9d38641d01049621242ac59ee366234be72abe6812366b9d4434f69287ce48fdfd695eff71469529b5b7d542fd326ffca1ea9b4ab05f676b921c54e3ec306951159c6198c4c8570e7b594432bbe8479295a5f0e1161c3e2d261ce9f94eabf91958c46cb8cd23cebd95fa62757dda704421fe98dc6e0019b15cacfbc47f68bca35369313c78992c599f5da241b54f703630134aff4201d44fabd0785a993c3b7dfdd116b5fc326ea1185f2fb55ce3b703ae53c8c5a3d5652ffeaaa1ac38bcc4e268f90f14d358f0471822fdad39a54dc5c49fb01c9af14fb24053a68ce05f694bf29cd27dc404748abccfbeadc9585e0c11e3d8fa67e04336f93224fb999aa77054266072c89f201f7c179c2a8e3a7b84631f5646cbb5b676accfc97a732e2f07691d7a6b0d9ebec516473ace37ac7602a20becd2751e151a082f8b46ec1fdb134f763e9b15c5accf68d59272e7ff7a291e69348f9228fe023214b2718bf76ea85080f15c82147b4eb3545579e6b675804f40571987ae9aa09b8b0e0657f0c435431088b68f46cc476c42c093b0d0397efd46315cec665d8ea18524a485359d1ad6429495282bcd3a377bff2824d27a4226ba6a9f8a97aac6403a2b2667bf99a445d6b4d1ed2ceb0c0d79837e4cc354c51b6179c88583326976103c49aeaad3aef6d716eb909c028e13bbee0454d11ae942cfbee84476042aa944a4e74d8245d28e28ebee1842cfb5d5e98df7eeccca0e7a2b8e0042bbf7844d16b1db52352168ceee718ab3b4a3f4f01db69db2e1899abc54b5ab744b4fd138ce8cfd48481835e31900afe78588315a30cf89dd9315b2321fa515f026f2b49f6cd9f7c908cc904c3c8e315e1df1cd542a0f4789d6bd5f8b9914e50ec93361a8a56d90c8ade4189d6bf5f01c87cb5f334d33517918836b635b2bdf5e3c5fb3fa7804a150630941aea73fb25d82d1d9c07c1b0947b80b24d93e2dd4bb2a844763f2c4fe05c248e89879cc6b80861d03f775f7000ef716f9073759364e6365cff92913de6ec32c5cd94ba3347e4386d0f7354bfe132cdc3b9f17d9de603852b20fd22df552d86728034b22e301fc4888231d89faefe0146e30b57468a014c61a3d02b529290ba836036908b277e21f2d554152155043a2d8f9b7fd99d17d5575344be28b136a78ece175db1b50cca76851fb9ddb64aea2920deccde82de2086826fc2d9ff6f9a3152941f3fb4a37ba8d7b8a5f32658452c5f8d35592dbd634a865638a1790fb46c15c6704bd4bf7dfa5181f810ed2cd885f787b70da9a731c53707ffbc528d47ac65d643c91aa83ac93f77d6c6c80ddc72fa091a25942df34d73c5b28b71f96f8f353ec38e5ce466842fabb02e671e075f5a758e519ed87dc29b477473f0bd3ff19a966575c603314175c4f212f0cbe52871aa71ff0edbe9941303ab9e9e31365e4ff99b0c57b3336fa8954d387a07073e7af0affe3c0eff03c588148d7efae4ea32f1bb626424244823c10f19935085a9156e8d801841d35b9ff1b22f4640ef53c4fa067a15c376d7856d3e60bb8fb615c731d53ba2d67375105b6cc6e1aecc6aad94f119e33c7424310a60b6e7c00c5177cab0076fb4b4ed8f736dfeee48fa1536e00bcf21d7781ecf232bfeff6d711df36faa432b817eb41417a1e03e0a8396ced12d1670155868c9488bf360f9a5f069bb9bf1113336a79dfbcef7595a0a5886f1c8e148b01e05cffdad3b5ab872d3eeef8a7c75ee17e2ab6d33b6fa4ec24b94ab81e276dbac3553c20959b2ddaebcfad2b8c53b349f55c21ca28a8fbef7b1d430723e98dce28b0e2591a222346b9cea1461375d6056f6edf78545ba221e4142e7b8aca3f1595579e88351d5a8c9ca7d49ef4a6878a78e371a5066d24a85cc816a343d5e87e304553790f5eba54bb55870aa26ff4988d332c727aa63de22e8571c8f6ee4bf482f24e93523fb3ca174820d19b557d91bde7477edbcaaaf198a2f45fc9626490405add69dfc8c21436ce8d19fdeba1fa39516f5e23d19dee85595c4480e6783f0ef7c3e57499a736134dad31c5c041a57e634888a61d9c86c10be5121fabef723215f39ad2d185af1d58c5781ef292706db6f338f5a43d11bb7fd4bb017971e7668ea3e8636686de3cf9627acadadb68ed06e00c85b66ed28d84f1bf55f327666a4f4b9ff5ae4f1cc73757d066b2a479103585ca875850a7614d02077ee23e95f7618ad37c959e28de7087aa54aceb4f3a95b84afc9963f802744373624d08492ac3567f6cf87cabd80132ef2e5dad70fa32902a98ec3cfaf6c8fc3fa9af67fddffee472afba271e93dbf321a812473a78a7638d028edfcb59c3d55671f259183592b946785d454ebd818d2ca469672f7a37bff282924aae268fbd66d923f95a3d1965ade94b9f1e84c8ebff893ac7984a376519f5f30ce9bad1c4f0a45fd85affc1f687856e1e0beb17560b6f2fac17ad72807c04b0cb660301530651b6ac05beb794c11ee51c0e5fd85a45a81a449bb8f55a41c20651703410039bf86669bc0759a6efa5e02ee19975a6e7a29e551f0ef4734ac3c1ff3ff201de6c82f244ea8073f313d0461125099f58b7638882d43874c36bb7cd04722c74d7147fdac26432966274deccd7ac062393fa0a1725f8d22349a3cf54394d970966229fd5a10bc6cd2581c701d4413436f56367016091f4fd4cc8dc8f729ee75a2b06c985d6a673e8a96c3a91755f722c2d4bcb5ffa874ed85d4ff849e4f7dfe3f717864392e86525adf3003fa28ae98c42a718f4a297604a57db71b9e33678ee019ccd64566d71050d48df1f5695625e7fedcb3293e19bf1497b6dd93ae98d41a50261a2ef4716b403d5016ad5bff450586c9576114811fb93e2015cbd5dacf284a854672a7a003419d266bbb15cf9ad96b1520cc4ff57bfb3d0ac1c85d122668bfa247e6a3ba5c2ed732d22c8ec5a5844b2f44d8594a4b49766db2c2cc187edc68cdcf3118322226e24460ff73e68909b836fb0700951f925d07c9237d99c7aa1142cf4e4453330d8e07fbabd9d3c2d0d405cc6d6243efcd8a3e5709709c9f9c37660a0ed06d3c0958517196af2304277ea55e3532f5f573525cc4c7efa86ba13b869394716222054f0b089f0efa4ebc01eefb1c40ad870073ad5c6402aa584a22f91ed7e3ce6a310860929552b2d3246756fef45a362a76d15bdf1aacfe254ee4cb62232d521880a32490bd04a16458483415237ed529d94513c2d8016088064f56f59835a32728e0ad7950963816333aee612150a3a7b377c352c10b87fbf1745b68aaad73f726418354faea1a28f5c4e224fef026d192ca10d8ab77bc0c32ae97b09f44596ab741a2e85db3e179c3ec05e6d9180c8a865a4691a11bbc830d29de43142f377fe09943728c97a79d244a77c36a860e52a408dbfe624896526e0a28ba79fde171e892547890319c1185b292d679bd749a8597db2cdfeb78e0611e372890c2f732d96b1684e6d72c49b6703d6e6415b40fc7d7cbd0c0838a169b18aedcf289262b02ce95d2ff378cf56c0d72339e1718958be4d0ece0df4091f064c0a28a1e5bcb7751ecbcbfc0a26cbda74c2b2a2e87d1f56f3652cdb4dd7bda87db2f172192608f117e80ffdcb124a6dd19f024a2be35b01fa1cd49fefae6f90eecebaf6e45ff6551a5440cfe33fa88442a6938ea9f5567f97fffcc3151de8d1120d637d7af1f59307eb72576c35ca7716cbf294969474ddf8ee3ff40211dfe4049e2eb2361b18c6ec45f0023128faa920f4b4cbcf37c961c7cd2426cec03558940cddd6ddc016741f5cd45e83bdd1e96b0bc0a3a5034fc01035927e5635dc041ed106dc43acf1438272b12fafd0bf3e3d3d261405e2e3b920d98f440ba9312edd152d6230c4503304a4075f79c4afda5ad8264b335d3943eaf0126bbc032408a4062a1bbf06e6759c1f4f5d483a074a07a99b70b7e4d4eb30a2fe9477af6e574989d3085a4e1b74b896f0a97e526749aeb8113d50c6f30b4743ba0760e31bfd52f60352a77f82d2a5c7aa0dceb5d7e566507c87c6562090966896324460630a3caec373b6a0a4ad8858299393c94f61a17a73670f445297a0e167cfefbd5bd31c12214b66f67503e5a974861dea6a3ca293d538b1fcfe5dc9a88e9d42a329354382e7adc82c3c79d7f12f620cd56318d89a228d785fc163080fadebc83e8fd0d0d4e6c5a95f2f9f57e2a602abc10b2070c1e549808b09ac85d0e4003b6faefcf55bcc3bc8a7d75784c8c37aedc4a1925f56187ad77c2d28d484b2aefd2db7e4c9bc0aa37bca58f0dba3c9fc6aa8bbe6e1c2d083efbc3234691da6c5477ec66d62af522bdbee1dfe8056fb0b10dc4ddf4348d30b8942575f357b607f6a4fa835124d37c6365bbbe2df7b1f8db54e616c7f92ded1f51f9b73f7e0fb2b8757d58b325abe11b3a3ccca33ad1f8badb8aeefc401740ad06d059f9ca3158a512608b13b64acdb8e6c26d7bf10ca2775e024d4e37babfbab77cd09f507598bb9872664cd2b0904448314622a4d5e268496dff3b46341404f5ae150069dc4494d502582b3d5ecca726e0c5270b0f125e8d4e33c8663c7b34d96626e1d7d5e2d305b272317500fa9fcaaa973160f883b9c297ffbee24548c5773e84c0e63a2e6dff57ebe37170e505ecb13000293dfb5aa55bd98ec6015f2cd965b49e8b0996b371a79dac8a402c64b22a4683ae5d325a332b1fb1858eba5adef7bedbec8abc3d2552854a295fc4ddc1ab56fe91925491f8a091a29353e78aa5ffb123d48ef897659f46d229a675648eef1e3d328b82f750c5d16bf3bc875f4a1f1d6be224d4c6fec1a73e0a89583f6ff021d8d2ea50cbabc5eab9ffc8d1511b4a97fb96c48e9978be10914b765fe12c982789b734488d19f0c686eda00c709e6d39907b2599cc795d1182a6eacd0f03faba8fa5eb66caae03b751cb42186cff351f8207211cfc7c79dcc1f5a6ba585133a9257d059bb685adefe7fe99e6f9c053ea040cd8716641f2c6c8f7f390d9739c8803dd774b97687907bd8341fb7dcbc94173bbfda77ea1b3ca374a6191557ee82ff5018fcefd6e947cd36b91f20b33d44cab9d9a014fb98806c940f74e9cd32f377e54f4f2d1177f2d356587f7b44ee9377b03d275ab86e73bd079653140d3a6808e56c6772caa33dffb45904c26fb2fb51dcb342e346d7c514b5252f49c17efae46e3e79201e754b9e9e036c79722877ac687f40b284800d1829b91282efe86a7ccf2b86b9b55b0c891a442520ae4e3c03e5eda1d07f4ce693a34b3b03574d8163054bf9fe4b4be7c10c7de6bd6f35cc391283d2d057ac76c71670c43b241cdec5e0f62be825bd4295400f9a18a0fb345c9b252334c473a4fb33219eda54c9cd92961becec0906287f514251d1a9f870bba2220e00e1a0d2df8d5d48eac8eb8d75883c9c649280233a0b3f8165bddbb5de0dcfd156b8ec744154f7359e2b667a0cfb26614b3eafe2c0b67015cd521a48a4c781b6a1c75ee76bda2b7571ab28b33c225d518d794c256819e2ccee025d4629f93b0a8fe5defc91115fde264939753f9e3dda6ff77cdfaf90eb8e4135f4aba169d8f4f6000e1e7c1b3eb2ae6967d0727b37588e004e244c1ee862602c9a9eca9df9f11256b889fbcd3bab3fc38a11c4b891bfa61cba7c4b0cd06b24ab8025436675c55b82666627064d6d5b79b7d9e2737182c3bb951eef70ba5baf69064d4bb8dd5b5cfb209c79b3ab63b9eb4bd63c53ce8c45ca1c29a74313404287a1351a943db80fd098d07aeb4b865c4fcf88008fa84592b5642849ba786681769c6333965e6391315e581b75adc26339d24d154adf6f989e014cdf097f256631dc3e9ddae0f165438a738ea5b46269e510e7642f294a4b49d2c7810f1684daee80663be22e3827764ff50aa0b9e606a7be35062b29fceea4dbfee46d447658d5bec9642f1269b7b55d5942ec067a5041fdc8812ba66e1e95dbd7bb9ed16c6c631e7fcaf36bdb447e98af3b0b4b31888c59e0963630ab937f509ce1e79ea2fe0170111d61fa0d41eb850fe2ed1a7b1b47cd7010a09fc263d93d97d07f53f70fd23bbd7ca479836e258c96cba79a5ba356fc9646833c96d3045cc7fb6c6c5f00e1cd953b760b5c723fb969759c70e5538cf57699eb168c7cfbe2fb08950a52750e3640db479c1ac08417afd77ccf8f3510d200bb01035e1ebdf5450f23c952516a9e994b573dfb9fb82dcbfe6c66adeb695c489b922fc5bc2700b84fdff2f0aeeb27ff8887745fa2fe7fa256d6124b0c08d74e633880f49281f26d46cd1c88b0bdcc24ee07e8c2ef3f66a17e96647dac44b1269496d0eb5840660ab5ea13f74072437306f425e47d09b5ca6ff88ab1e3b83171e3aed341a9b4c1cdb86c92220eee6ef3a171f63eb81aed0815d1c1516345f7b54d0cd59eb259d3f16f77cc3039e98878e24c431f6b481f68087ec01ab1f056c1ee584670a418ed2767738c021761d97519abcaaf69a12061d3b89419ee339a2d2f4b90951a43733f56eb5ee94541ae52c8f259aad4cc937f65eaf18024223738cc3b57c0dae865bf65b217d09e5d576cb9923b293fcf2ed4d83af41bf8f5f89ac1172e87d3d1929badc9b98433f8585614de9f052f95fbf88f2f3b511bbed5b54603b75585da9a2ec473de597d51d1c23c6604d4880cfd5b15108cf08a29f0eebc5165444be53ee16d5279a6fb306cbc845ca15086013fef6d0c4f706b910659cd25d496001430b29556522c80b437010e0e1c3d96a15db2aec7ed844015bd6d88442e4bbd28968f97a93b9b317f08b3a375e2a35b928e843bb61a34c4f049dd04d2d72dad33522bfb3a099d74430fa8b48dac74cbc505adad24fe880e1ddd3fc2527e93656e504958f1bf5edb3dd34ce487cbb7f20687baeb4f0219246d04d96b1c178bbe56977fdb9d62743079e70cf90eb4fe57f51cd21d0cbff0a94cddb1f752f2d4ba341115b82e33d3a813102df996751c9e49b1eb912934bc4ec6321da197e14396ff1f376f4242fc872177a530a126c291c45bda008ff296f57aaed4cc224a2cfe389f4fb80fdf3f7f9e1cd05d2bd9f39d8831f258dde610fe5814bc26e58a1c81fe141fa35d9e56410d3b7d156d56edea2c3ad390b3c467b276521cb71d8e2d6604b7b678796b4c1e99c2e19b129ce34a03cd4f34dbadadff73f46baf424f71d6edc1a9aa70f9853a25f7842f8bc030921d1250de18acb3c8d0966258c51b2b6d27badd06f464b743d3ee5359fd9925f051b256401a0dce3e21aee69dce5ea2de85592a8d8f4cc12f424afdfe0080ceeeed23b7b1dfc85fe6e7b7e234e7ee104b173e2a60284d72ed39ad5b13cba179f43fb83f9e5408d07299e0ab8d21e032e8383b0b3ceab805faab80d2003ae5a1200794926eedf8e4fe048e9ceb8694494821c089b063d774d4a9f0726f274e3195a049672dc8e9379e29f90d1902c2894eb06f38d15d8e33613075cbfe196a83b339bacef2ae100fcf18fa06f21f59f7c2d53529f5092076bd3c26d6e80323f325bdf14d277296ea48c102b1c6ca05914d5498848cfcb51d4f54f6cb51f285a6da20abaf66504291182f8de8fc115b911477ec7e4d3b7cc6800ce6da09021d28fbc62ac580d2f7f36da5ed743e69fb1a34a8e5a34c4596eb662f7473b1009efeef256479cfb8a35db0bc256f74696eee863585fdbb1f8116094f5681744c16b15a7b1d986c471ee1c836fa22f3d483f76feb23fe2724b9c9a832a72c946303542a587708222baa3e977e587b0635c06d567eea8344adf12af5349c06bc4ef35e846cf101ea710c3841429d307fb1581029aabe223207b3d7c6b585d1a2734982e65c97e8afb85ed05ec4a22035e3217eed5d12ce14a81bef4d3761276d3bcc76f1ba6ad1fb174d5d9c0930f37865088fd0dad3b08e7916d9ae67e702575df25b9590f15d5a120383f9b13a179a014b888207f0013c0ef250a4ce0af505b00d24e478375cbe0b17aca2f8d4966b1790e5be20dace2c86928704a4b3923e509212575a807c33a6d04e295f78bdb4903de37f9f73f3a24cba60f64078252feb45e69e005aea3464c0a424e10807d4bc1055186da1243bd130dbb450fa88fabb43bf9c4c883f50557227b99b70b33bd5711d1a091620870fb225f054ce315a716dce73f820e6715619f701e11a2a6af7c6d55a01d6ffac1812e79b08972200d6bd1f9c1066c4c6d0c68d8e0a2bec8319ee36b37f79f6e2a8cd6992aff237218856c2b52d9101ab233b6c087ec1f4a55a2731a5edce41aebd4a2e55b83fad0eee6ba2ba0cc0eea141eee06f430fa2443f3ce5a0c60da9c78ac9c98afc9b9837d2a34dce6c366fbc31e87df266cf81160492c97c4c639f3724fc27fe23f55d865f993d49ebd9b30eb81d15278afe61081e5e732eef9c2042ed3b47a216756a0f9659ddd0092988d44e93fba1d5e4ee42f2d6800009c1e16caea460178c250b147b78c77580ffdbd387abc4700042295ffda0ac97a872d56dc06599c8eb4626338facc55265e128f8f7f7c7ee3c0d3edf03473cab342da54a791bd6ab9e69313bae91edc12d7f974b060b7b5db81d6fce6fa6d346f32c884e349b69b56139ba3fdffbea7b0b424037310a098148397952bffc3aa4a952bbf17fef9f67c4d36f8c2c240050f4bc6767f5502a7e7d372cf039bc0c7f791acbf812a1854fa1fca7bb59cc5791cafbd2a3f8eefb542d702845471e59646e276a68e3b7e23a21d6d99d8d5ed39d9f766235dd2f86a5efa6fd55631411800e57e78e37658e4ebbcde43047d62b15983df85d6938ec2c41b13a45c4b23c29020949cf5cc06ace198296f71541ffd98e98b1c9b21408da75cd218a5c972fc8280105f3315e4a75e71da955613b4721b8719d5bdadbfed86561d1d41bfc2a3c9d7d270bc76ac919d84c632948e8c1561c9c84335b9c1da27ef52e107ff5a87da71ba65aaffe53b171c5548719aef2228a7881b2c0709a1da44a8589b0cd0ee98bc0835e9ac4c0cb16621dc67b7cc877fee4eb8e1793261116d5dba7c79710f1055e5bd9f64697d3bfa927701dca066a95224981a7b1e8d46ff29003120ca98fd00c03b28a5299c4096bca6c3edb297cd638edc20d2276d33a6506a75efa9292c5f73d698ea838a22026a7a2cbba8237cda5ccc5409b29c5631b6fb8337b5d79d58da14dc0cf3fd8920c709bf88fdcaffe062514dc5fb8bcae0adfb67ab41aa869c01615918d9bf249273eb1f83337e8044507a88af8bfd6be5e97462885d1f17b13d8f2d63a82db48127bd41a8b1e6f5b7b1f8abd60089504f24b65e9539a616633551f3e007b86b7347a1a1f91fdce9921aa155a6891c8de4a1d476ecd2244e9e5fbe1bf705679f31e6bc9309bcef0ab69df07bd8c6744c9aa52588c0a573c2f88ad4d653d354c230bdb79f3590e28e7f06835e026aa635aa48c3d79066debe7ac4f38b896d2ab504953fed1fb02b65f9b59c069f7198227ef00fc77c3ba6af6d5a5ee27017ae64c17086c0399d7539aba3a157b97e8726ecc82b98a9b37fcc3bc7be59937b33b86090bf0671be1e46bac49daf976283e47c3e7e42ecc61393aa079e7220db24d08707a5f25266baf64007bc170badee35b6713457297c7e11fa3ef0662fba7e944537f5a8353b8e5a0823d20444b60121a401bcf51c71d000b39286724f105293a4f2effc3af515670dbcd5a74e59471f1a5948d07c628eaf2dd79aaba822482f2a77d93c3e4e7f0a7cb65fce985620b466df5040a6ff32bc1bfd466b2fd3171e9dab7c0effaf2d0e0a272d5323204053730a0f6b4ac9a69f9e2f0d46ac5e0e37ad48a55b1ba536c9dbfda21e41a32d978a550576ca76cd07b53951949a398f9156a8a54eaa51e6a25c6545b7057ec4c73e8d73cb3562fb010919da8a1ffa8ef319fad25e8a403a65b5d13e62c5f0b4b02ab6e26e1cc777f5689b55e39b0316906483046db4a2645fcae8ddd348c601c886119c61a734006e7583d2149af6c415c16419f025b64d58ebbd1470965f83a4cc673117a96f45b64df8461c1bbe4a0dac157131346a0e519f57f81a730a64c4e2cef4f997256c4245f307d58371ed87256a72995a7f761736194399fccba6a38a967cffa43d8ceaebf553e1ac28081e5a7e050cd65088a80975d580ffd29b220be3244d604932f5f291c922d74d873dfd5517559f2ef962993f2fb9ead76b3c51c38aec9d92e0f1b833edc286ef6dc82e5619a596aeb609e916f68b82ca910d013d13908c888963862fa9cb86124e030602811df2a35c96cd947645b54f5e2a31de666730da173bc548c74f970ad5d31b8b476d417cbedb116e1c6533bc7545cae1ed03b460e621bdc97823886e404faed8697d989963f89c33fb5dad6a4d26c5d8574d411846949ec9d024c252df9837a1672db3e10699042648e7d886b3efc017198654cafccbe66d4714811398109213c71f2422140b356fdedc9fa845b6abdde85fbbb8c17a66cb39fd3f1e66d103569296e019311f1921bbaf1ae5f76677454020b28cc254758a672e1ad8d57fcbd2d55fbbf0b3db6b8b8166384d92fd149ae0d4d0091e6b1c9974c222f5886808e321d2a0a525ff1dd6f62d362c300b30914c98ba886c56410569b8f452b4ee17ea0cd9a584c652f950847948bac28c64464ae6c00932311c90329cfdaea24ac25cae113c0608cd6b94bab317628a11e98d6500edc36f6282ab1fbf445f3198d08025f71c911184a462c97ef229a131e75a6e6221711238d0586400b18a5135e6ea111fbe783d706c330785d74b7254fc82f546453c80517bd099250f3c8970b7e11886603a9afe647870d9b8893dc79c1dd43bac2212da97f03ac35a117187f7d663ec0c6b6a7396f220e89a559523531ba5c62bce9493da2f28b50b3739f5971720420f01a51c78e5daf67077e305167647199169c8b0e13926276ad5211133aefe23c3a37454dc8e8435d974e0132e895dd668b5e17fce685d2d2b14fffcbb33b467c374b7536a0ea95e1aa11f5366b4cee54aaf08e57907d8ecd75ebce3cdf37ab8ce5580d48fd2f5e7046681cf806b46591c72dba4cda60954c1a2d787345b8d43f306968414b4bdc29908be912938b460f991e97e0cd8426382d5e0218820b074d398f347b92475ac98e6e3e5e101ca4eb91a3718134d3d055331fe8ef6f7a6c21597464b8d38370f58eccef19d2481ad3e84beb5dbb69577ad3a6acf93ecb98b51ff72e6e2dc437288ccb49b0e7c92620564eac6ceb94a4308d1f2d4cbaa36020b910d4f0e43797ac5559cc0d18efde3a05f1c40aaf641c5a64277a1cf20ef289043e21a5860e07a161ce2ba0272867c1ce829c50976c246c2996fb517dbb7dfc8b67092cd672395c08eb538e5d233a06f92daa3d9bbf8a58d1f63ce556ce9ff4b4468d3d84d0f6a0c7d9862a4d1cf5078756a9e3e67758ed55f72f56f01e2884bee32c3cab8175ae1fee55160fd6b922dd20b86bc8aab413fcecddd5efb82dc29ca69d1c03cd05558b045d703cfd7effb6e61a5a5d773ef678c3993090ab00bcaac5b8a9064753ea83139919c752d0f7e7b05fb1c4f8a9e66b6907878ccd332edce8af9a2d464d60de795522fa4475603860344356a0fa572094096f96b59e62ed82ddce9c79e419106ffd84256449f96085cd6f69107b1b0aeca38100133af723fafa358424b16cb1b74d39a2d07fd7657854b564fd960687244df49d25797d4372f488b9734fd0a4cc438dd20b88f3447e6fa65d2d36b24b6d87952a63dc66e71ffea844f47e281cf11ee1bb04169e3b45d444c4fa35a7ffa43f8e173632d86cd2649b00617b5514654bd7cb9d1811637b8aac836decf9ef357285db3c944f0c5defd8ba15ab9a4cad89b79555dbc94a23ebe517f7c609fb979e4437776d7c3af1695ac0f69672ee3bd4073d71afbf9b927d7150b5afc72cc2392c639f1dfa631f06dfee4c3dcb7c3760b902c0544b6531fa068b6b7e6f35063f01cffc8744b3e446d71438adc999b4b3a21585bf51c7ec77067c1b73e2e235a3678050dc5eda31c8078ea0460460cd87008ba210a76ad3478de16e3172218bf81aabcd324e5c936ee06c51bb743448c8ed7209d6e43cc6eb7b869a722800f5a95dd28b6299a85857cf5f9de383ba52bd5a466da642c1af3cb8441d3e4224489e7f22450d77308b05ff955fe83fd4d4d5fcd7c29f94022318ad8d51b8246a0be006732c5db8dc892fe96e1524f9e5bdde61f589b8687b50f689d079714c1b8fe29d2a538dc49eeb884ba9853ca09379adb2924874b06c365b78fde4395bd78636b600853d10d41e644e084a08db5ce99c9a0810a0ebb1a7fb07ab08054bb759cdada77c473c1c593ebf6ed7c1b4f2ec10f225982d032a59e4cfb7e9102d45968391ca8bab8dc41688165f14bf089804de6c2f81396b35d77f0d3271f488ad01ee40aea6c5e199ec43ddf9119236f294eb52ac822372a69b2e61abbb6eea2e829107245928819b43b29ef3d1296d05127bb31dcdc454ef86caacda94699caeb845a5444ea6c956a129065de0bb7c1ee0f033e18b002ae3fb5019386a20c0a4342a69187b40fe223c54411b9f8cf370a92abd858d16094b413e8b5c104084b1d084bfa9d3710e36a82b26ed774e733d4117d3558b51afbe9fe48a32087ad6f33919329e03a85b97aabbf362c3c3118ce3dd0a69a98d6fadc618a74b0ac7abe33298cc1448be9c1b55880f765b40e0b34a0c437b6fa5a6b3c1fe7509ad6d0a4143321e2397c18b360d3b82038609f89a214c7e6b51dd3a53b8c99af8fce0d12cda48df591605af0fe1a302ee76ce18bd849683a2e88239bccf0d5c47b3f34edb685192c7b1381607d60d2d90f2562dc6044832ad6864eb0b10895a3c63b24672e7428414a70145cf18ca37ff47e7e85dfdab3971ca8e812b4852edebdb4632bd72398639ad88e8411b8811d178c3376ba391dcb3f3f9d776f8ac3afa50477b14ed86e5be5b1a996b6b3b798890a7fc0d66080b8e1e3c274903279f88c66c930f9ecd94640e5b989a2805f3329d64f132ea2a2fcf0efffea160783963d7a25f9044c7c885a5f1c8e84d5945bed720652122804d20302ff2d2a8cfde589c5bd31b6fb0cc13592b2bcb09d773e8d84856b0f6be675ef7f33df50b59871c72ec5408b8e45ee4ef16e894a76fec80256d2d27dbda2b77b0eaeac6a6a121e7e83975dd0ae20091a78f14ab1349900dd56453b02c150ac8f1901669e08815a32f2710fc79a68bdc2223469d65ca3dde6974a7a69efbcc0195035198bdf6d1fb890bb9a9ca295141e704705c5aaa611634fb2b08912afa34225ce296fb5bfc6abd6dd9e08adfc2d9ae91e2c0b8b6d630057911d14c980c22f51de93a4417506851ead6693b44d5ec2946de6e4a4e58ab5aeb13c8d090452b74d2ccdc5b12f98372a61ad314562509f7f92b70149d2b6e6f79212f399b91dedda66f60a258d577c9e1dda19015c01d43c955d7d064f2692f6fac7f39457975c79694ef33153e1ac810af4741d13b2d95f32e42c1a2280c41616f7f8eb9fe019f490191b5f69fd92c0fcd0d3e5d4272601f85cb1f7ded140e6274cb6e186c990d31f6f32aba35dacabae401f3572d6584072ced9c4d8601b97cb5b5c8da9fc1de8b29e65055d7b2a76883610fe9dcdc4bc672cbedbe4b60180c4d00a5b14d40bc4c51f2cd2e19d65b9f8331bc79b83ce1c266ba54bed940d9b5e539f8b5990813f473d5f6caa5391f15c7520293ed79d87deff385753b54b055fcc88b1f7eb50f43defad9d65e83ffd79a3205235e3952c1260c0f42188928184e5d833104553c27c94ff28d3a1a9a1840bac83ad7cddba0e521f4f1053e0f697e967ce6dd7344c677e1b406ecdbb06ba9fc0923d7704e87a201a45545f3bec0c668112dbc0217ac680f70fe8e8aa388842516925087beef202f3ea441ffa1432303ae1c8c9c5f6acf76331726f22a7461ebf85dce2210ab54dcc03cd756cbdf1af42f4442c75b06bf56736d91534a77e0b2478f0e8b2fd5d3a5b8bad891a5f2292c25640d3215b968a84236f7fd7f0c3476b673387238bb55b5f3290ce301467448de725ccf5ed175a42f1c5b5221e1dffdb8c3a1a6bff8a0e03d9e92d3c1aa0dcc8dac0e4082357ebe00d3aed6232fc69e7b52cf448a4a179c117ad790c52eb4df33773afacfa6a6cbbd74c60e40fc781c2c3e0ddbe4dadeeaeb7cc926ec4340d0c5abc6737375fd6fa23cf4b87feef8d912c4c2df6ce5ebe08388eed721957878a720f710be188a579bbdb55372a9e0bb27a41cb2e8080d1c14cac5d201fcc0ab403fa69d6c1ea9751daa1e9f925e6b1fdca7ec228c1229959594954c649ed06d6649a0da8e9a92368b2ff518f619294b74e0c323272d827abfbb1f99c2dc4c2e342665d8e2a929772e7ec9c91b87ba732cbeabca47703868a80efcc04046bedf81b41cdd99caa175713b59193e57124271c12d1263b3193f66a2ab3822dfda94cc4e26b091adae7834002440109b4951aadbf5f762987065ef1dca088aa17f932e48d41a4c39bb6d271b70d6b27f986340015e1db343f60f67ed54636a1e9a1e476165ff27bc3d9d1199bf972a7081f582c06bda55034de6b31096d80dcd4fce74b38579d6752afd9a1d1e8cbd7e2236159987335f67ec5415d36e5984a9e411f863d1cbd755b9428e727fd9865792bf145701e39a421931206573f047c2e3bb31ef57bbbd2f7235cba2d1203e6f7fa4fc2b025a55bc5e57f6ae3a98528e2824b5ff9b3c5714bb0025f204f09539c5680352942e31f86320a75e4a7ecaa6aa5476946d8388b9927ac0fa51b69871fc4d019d426757bcf96384bd686db7e3ed3f6b02006931969d7d5765301f9c2ad01de7aa56bfafa485971c25abed0c799fe1d7a4b9df85cb9bf48361044f9296f33337ba99322afd2eba3e5bba318cc0e8976648ff9495646680662373bac43a00972d0724895ae91806d723e1bd075a6e231de90661b4edd69ac0397b5ff7d3a613344d92444900c84b15d4431ccd290bcf8aaf6479e50c11d4b9739f22fa72c219f3508a9eaf3578eb74b3fc50d9465b14e61e0afafab1f25088c00f8137623561013dd31baf8529dc01873b06392f484bdeeacddcd7194ba89ca0e563200e2cef8e5950d51c4f3a9ad5188120146a65dad10115fabfabeff13260397d4422abb9dc3cf0725ffa3eac1bbeead184d6c9959dc7dcd7361e6b875f002f10e9d350f355dd5067b58ad54c3ca0f7e01eb4120e43ad7619b05ae285e6694fc1855bdad61723d94defeb52ebc2b51f103853c5623637ad83f0adafd793e497c6311939a0a5ad96acd284dc6683ae770b2242c0f6969945fb6d62385c120397f8e5ec9b70f7df363f48f879aec8c77209bf9cb252bafc889df941e91b62a4c68c22c6ccf6cdadd5decefab8dc2678bff33d38298286d277f3fb42bcaec6b55a4f54fc9ebf181d0f5de8aefeffb45d2d61daab34d602d74e6a5697f581b68fd1858ff4840d619182fdca44275ca55349b3d189ee1f051bc40662a351a23997c967458d624115b9461f3082d6801fab430b7dbce1fc38b3db1be3174d7e8ed25d697dbd7e5929486095381baabb24a25b78755200ab3629c6b10ffa3269f42c1cfb3ecb76a2d27b052cc62775e4c90a6ca580c4b8fc5b46a42fad6b0f21feeb56f4552fdf43a09c17818ff7c30a4cafc61292a0653db2efe0f3b3d60d5413905f8b9b9f379d744165727328ebe2d2bb13494fdb7293bd696f37e80b5b7a9e014967033e458cf74c8c18fd74384319a04d3ce78b6294182d59cdf2e666a759449f7bbb3dd6f3c4d0a82f34f49373a32b2d9f7b11774795e821fed995e28407971e636910dd12813d70fca01a99d16c081ebed611217706aa3098a5956356ea7fe3b5609a89a7420f1628ed52831be48aa5ab467eeab89fae2b5c2c9740a61ad11ab61bb60b3aa613075bdb7b6211f8104ac5d4585924a3446140d8905c331095435e42c736d66d920506c1ae5885c540d5e8fa252ec8c9533630bfa3cbbd3b1a24f34443b526a1ef1bf7d7277e04b60b89df5c614b9476dc99201c50c930d8d8801ebcf5d988ed7b005a9b9fe0d117912d36821fdb6b35e844caa5508078bfa888900fc80063c44df2294eb85959025b42f1ac470e8cce135d9fd58595166483a50cce194443eb649983d894ec50f1e04a9d7e658e169c14a5fca42c404ec3d303455f4abdd56123ab2d21a9decc2d349085b60033e649d6133b2c9bc930891b678369363742052f5bbd8a695ef39e68ffaa23293e5976a3135426a8af5c2705be1dc7edceb7f048ab4403928ca93af564a88fc00fad0efbf7257e37c1b64fb49c40657438717c9ee5e96660dd421b716ad1c85ab971a48e88bf8b730286fc7741d4129e941eed770c7fcc1d2d7ab3e93cee33878c6426431901cade39a5a5c90ec92deebe1c37872981364f74ed6b7fc83a2a2833ed287f4509651d9e00eb7dd67a7c86e87588979d8316b18554446e35053f661e228c9d3618072cffe8ac8c4cd8e1963ba44fc8deb9b782d24ef49f9a7ca89ef10ea94804113a28244b4f53edf9c6ae038dd56e8f17b2fd52bd5cca591b855b1306629d264c4b9423c4295114d03380d080431f0dd6cbbb115931367a2007ac516391c2c1eea539748ec76d8a185629789b6bb8adb5948d548b114056bd4b9632be7d4349be54df4a17a58e0720badbc979e36b9e24c863ebe70fa93cc97f7ed6ddd0cdc72014c7ea62070d1e1e440e3e9ea76878f882d6538aaa446da59c090bada55b17dc836dfad92e629b94c28c24339664a99f273d62fe323113398c273f372928af295b62ad39d5788ad12d519316b832e836597e106143f1bfd154d7aef0e7a7a0185bcd266a453c68d5f9bfc4fd45f1660f95a5456bbf302edcada935086191767b3fbabfa45e0e26b3432a76b4f1fc9c3b4814765c2edee0252819ca3324f10b2ff6a023aa6f5bc089d37e9e96dfe703b6055fb1d670f71e7ca81cd6b254f163509c4bdff39009e0c8da91d4d23c869e76722c411d8f13812d4bcbdfbfc2210df3c08965cbe6194d6066cb3cc045f5f10d0bb065e6f5c96241f38f23dae22a27327f9577f01ec9dec79c94546733abcd142f4448481d0cb21249ffece7b7f04de1a9e39b1716ba2563eec4409cce1673326273a8785ff306fa3c0350b3dbd299871c33ed8fa1a3e6eb1652e3f0b72d54c7bb007dff35025c6f90ceaf1093435606077ad3197295cedbfa855f2201f624b007171169a5dd281c4193e005b8f41b6538c92361c3ea145988d3cf597ae0376b04f2a234975bef2f7238565ae4e77dc4b1b56dc38f0a58ad7d07b2c14f8468846b230cbb41b2fa254863333ce44735387fd5fa34e4d0179c2923bfd9b6a9efed6093ba912845baf1b455f6ae519fd0c1152cc25cb55236403d63be29687002ef14434a2871d6fb0fd86d008acc80dcf74fe2f18ec7cff14f5056d69e963d640f758c8ddba045cc18214a27714222681f1276dee632c87e3b96b181c9d2a3a7386bf0094f58a7daa95dc71fd0e4af55dfeb997962941f4943db7981311978528daf5f0410fb70a496f7021162a384f43dde2e9fbd98be3c8c77fa23cc67d30b40dfc721706c658a3da8726d21036739a4196c843764f3ffff3ba1c8b7842ffef01d9f3af8ba4ed52ad7b6b9f2a65a292c80cd8f6f5c7f980bb4489a05c5f81f974155068fe75622e5bc9bf1dea3906a81c3e2088189d97b5bc5dfda607e1dfb08b3cc0e33f10165dbf95d8e0aaf9e6b0236d41ec1d748aaf122aaa62bdebfbd2a561d591997eaf3e9023e141584032eb839cb832925f5bf9de33f5bb9e32ddc8d6988547687e70d0765f9ba2797f51c4211034532468a5ed1ee32044797f7086c67b483d9804587a6d5610c4aa785055f5d6f7a0940be023850c8576c63196120fa6430413b6146eddbbd012f9c453cdbb3d529623b8f9bbd66057b8bfb88092643b8744216ec701ed63c50a194f5f44965720ea8d54c292bbf903594a1b63f2f65f5a566abb95b094c98f14e9d76cb09c7d62218fb825a907d11515e5736f8585bb9708ac5f0cab5b8b7a7e4170d2f6c8c8e00e18313648434bafddae6e25378efc19267dd2512110eaf972a302f9536399dff2e2fe06ac9ea992e614cdf027db53388c6dbe28b16a4ebc0ef3c962934611e42cd3d6563f8d93ba3cb4e617cf72f552e49e81c2a14fc04af80ad501d2e79a88b19a5f0ab2fb9682678122fafb979c6f5281ae61d74b6f391ba288dec40a5c61b673b8767473a25fd656809e501804e6aab78ccca8fe2ce868ae1b0ebe70c8f04559f07b61858c281da111920fa16f1c0147958f005dedd2c02918f9e99d11acad0d8c6fa1848aee40fe7a1657332254a11df80f741d4d7262e0e1f17e05ae0ff86905d70ff2b965974b75a0231ae685c7ad186e3a280aac8aba281bd98adac3e4e548f2adf79bddfacb50a6b3f5874ea5d00b382d9986d31ec5759ebc4a14d110043416ecfa91c954c66cbe268958eb88661cec3cbd483a8717a432380d9fe440521fd7d39b65d9c03abf0d72f8ae4637314a4b037878501b9764f2e19503a5fff952035d6ec3a1ca125206040e6014cb35c77b5da96655c182b98268458a160272886d775abbd4877fc475b1f04efeaa8b79fb377e0398a3a37a89b224c40f04bd738793d6410a910ec6e3c56bd7e068de7c62f61266471dfad373067f04be0ec737ccb31fad181b5d9d40518c61179c109df5207148d340c0c58cf4a5e0395bdeedfa50293cfdb236a4e30ce0d4d8085ee10842bd88dc99a5a163fd56b42b43f922219b3e8d545e5bc7d971131fa4a613fc311d5da9b50c3a91822e802080f1765575e1352463221f0884da05b2bc5774db4aaaf94f73f5970f752fce862cc54c7c012ba6eaad3393fd85c9ab03b763a91b447e83c68ce87f6c1e79838d03981e7f1d4ac6ab2250ef5004b040091507ae8fc78bcb7b0cb7424a60b195a1995b94bcb9f47b96d005803e33cd5ac4f2a9fdbdaa65d580509808221165f37039be5b8279aba8c130e8741935016db3c28f6c62316dc30c5b9b3ec0429a216ee8c9a8969d79f1a3fef587c648ad235c1a12fbea1f9620f9b2c135c2d8f415527bafb4b1bed71598a0b19d4048fbd2c78eb8a3f89daa5645d465491c6f674f97ba2faeff573b696120ae2b137726de03a1bf1ce4e838539130cf349690aab4fc9654cba1211d2aa1b39269e1a09f98a0bae04538d694b8a5bf159a4c3df4ac046e6cb02c3b804061d4d60ee18726a3343a6d833326977bea1fb71e2dc1a93c775e792c282644658ce9b8a55ba33230d78f0e9846a6047aee467fec7eda05addfac865f9390cbd2747f314a2d2e45eca669bda69923c768e4c1a6d75430ed6a0c3896bb5befc1664b10e5014985fe7f43f0c1e6872d6274218648e51d196b5691fd17f5e7e8098cab5c56b834d610c35313cf3b1da84d11a051916f18b190b144ae1de48f8f1e5b0f54db50891d6ac0ffe58a549f2e20c57f9b4aafd840b03ac252bc8b1f6c711e578d162e74752f9d740b162a0446b3248014a304c5f32ec3e8ff4df959588fcf484ff70438b4634d633364bb755fabde89b756f39ade66c15b9b85716f2916310a2982cfaa5a53c281a677a7d2aa0d6d246a9868e85553f101b0866d8d2ac5e0a81ae439b3d21fe808e3d1d92db9cdf3002d5f8b929401158652733e4d67321c934d92f2d1fd03593549abd44cef7ce56edcf8094fb2ff4ad81522ca40c6eef9205d78dc96132c26c8d7c3442bc88542e48004922cea433eac0feeb76cf32288052f774028d03a7787f8de7278e79caa7e2f36e2073146a8f7ecd290c91aef98e05bb6c1c0f2d50aba5c3cd716fc5103787a86af151c463b751647cac3721704b0278a062b0cd4d3b0e99f8b9a72ea29b571af695c17f70f25e5856fbb96c7cc5a0af0bd4c9de50739b5ba98d1f7ef013b3319c53aec0c1fcae63b2186b176e925c8e3e79885d3f52b12072438cb1c35b50b98d2dc9762246c32c76fec7736633e1a0f0d98c1ace2deac695382e18f24f0269e57c871b586d5cc7d282af3f62035e795606a797dfeafb7295416d772de5ed0c20b54a22e84151dac0a711b89fa7e0b019d415f693a802712538fb27b669643c15a2ce934f1ff703d7d65426a9ef2b189cbe15d436b3600ce5b3a77f442d20c514cd0b3404ca991404a9e4da26c4b3c470b2397fcac3ab1d1059b87320b6ff9793d1384acc6f067ba192ac13ea8abbb01dc77b2846cf981638de8d7a7a7709845299da6bb27f2b07861099a344816f9aa665d7bf5d6640e95778cf0f70efc4af0586a39511c91c2d6c3b0f8b23ff30dc0bbebcf23ae71d33f28a6c0ba8edce4fb754a82119bf75fa663879b304d8f8c15064adfefab98cb56e18740d719fedcc5225f95e7810d9eae876b29be3e9a38d03e39b8eeae561b1f7b38e49fa9f40d8370a14b297702d872042c4c6721a74f8c05892747e4adcd21c5f2dd1a7a7606c3c35d89d365cab33347109bac00b061f5dd9fe6db03a2a0e8e7a33020cddbe9dcfab2d498a19c365c894e5560b291b9503af5191d7dbee1d74f709237eba4e0e7f3133cbcdc73e9cca794ec6faa8855bc68981f1b248e5ab7a12d1f604cf21821e2c88cf29e8f0db755825f64f9bc6f24afabadb487244dbcf9e7da2b2e0b57e13c6cd9c3d9f1e0f9e62d64a367b39631a0e340fa9a9380263e5b04d95fd91584747050278ce2f233ba9d62bb16d955b1f90adb20c5765dc29e7c3753b86a37e203976253246c88fa5e4f3f4431006e3b2b7d119ebc6a1ae272f05f467d714542e77839fc73df10e9f0b3f51ef9db9d15faae6b47cb8ff89f87947badfdc441e0fb007e7a984eaa7ac4af021ef381fa9795a2118f3a0b907b98f42c87c5dc066f4ded6e942d10b6df6e1a9f6006f0d793d573ca37b0872ca41cd41d9a1b583835e15347e37e3f7d416642caea2488417f4acf4a1a35b03982cbaa3173ed7ac3a7201bf50e313ce525f46724447a8c02673d77d3d578b7bf513b208807e9a8efa39f7f1f5a092934e5f73d99272ca53ee9bbc11f86a6d2efdfc4974fa446ecfeaa9c907d380f6de563036eda8d705e9aba5217a4d8c7b8c0bd679f6716b1755ec143b07ce940348e82b016aa9c1ba9f37727761c1b45299b79c280d333cd1467d36e18375179edd1f5a945f544cf58aac6b8f06a1d8a6416b252b5fc301dc0adc15853ba89f2ca6678c681be99a5a57805aa2ac60039afc413196cc56b5f12e4767ac19de605432a89c141d82986aadb0fa6df374e9dd30f946d076b9613ca48a99315e9646793452791736fe655ea0bd6925b6bee429d257b489d386c8448a829b3d84ac11f39623e14f66560d8c785cb0500ccdcad5ff40e8579095a8f13ede1ece49eaef579eeb4d9217d666809226e27c4f3af306c37f1aa9a9bc4665640e54de9c4e6cbd9714e0c3b504ea55dbe67fab16951c99f93829b32b2d6b65eba3389ebc60fb7a4f1d323001f72d1d3750c4964b07ebcd5c3dc18280801e81da313cac24faf336a6534d82268ade5606937528fa4ed616ed5357c961296b5aa4222139871a6f17e3b8ff7c653e24c5a18c8ac782b2e34d074be194b932bf2bf417e936fbcef1f77ed5a2c03eed1d1ff4842bf7731a3c0e4494dc049fbab1e3a9aad6599a49900afa419756731d188ee8052977cf82c859764697b489d416c9c17213e3e3937d2c7ee4d0b71e12466ab0d39e386b65e225bb9853e32b085dc8d6777a9d92a41c92182a1bb18350ec1209d5d4d2d84598aa3ad7f68a5219d7656bae19cbb2121e96512703d4251d23a17668d80199ba6fd6ab4b21e74382df365f34b458764e109fd3428ec4e934660c1e0a5a988e4aab8adfa3257260752d4cd692a34b4ceba3c985534618bababe3320fae61ccd78d2dec23010a75ae009ac3380f430f17fb0eaaba5dd1be87e17be255a089cba8ad87ad164969d16f7fa6222069c93666fddfeb99dbbca00d3e4d2de466c77ec6fafef9d8fd30db8fdd71e8f40d0a1d3d2f9cd43c80755ea5903fd4accc0172a9d432dd7c6416ea0895deb7a172108dab5bb42535b63a17941ccc7aee646f4af2415deb1c6020890dc0dc55b7b28fa0d6b61562a13f2f6226949d284cab8a3ad89db0b6e89039f75a8c0c2c8dc9b747cf633c5181e938c5aa5ed3ca75de838ee7149b5941989da55bd6bbcaf19e5fc4b86e377d1aa5ee3f3cb3d8b060579721108bfcf1e513dbbc19166e626bc954303f759ab66cb915bf5c52870e105f05e603c5941e302204950108194a08281cf65d77e68fb6fed2aa5fe8bc7cedc44663e5ee108760814f3e74bb637bbfb5f056768ae8df0956a159a462a176b1b77f3146da2853fb9bdcfc29f18014613858e3b568ea41f0ce5298a0c19bdddba2207d758d2db0341485c364f3b0d716ad3705cd1656931f2dde29bb315a74bc121b8a1efe434f6da0a6c0f25112ab7494eb78f5199b7b3ec07e3ff67461640be10e2d371148e6be4a98d0002c2c1819808acb48f8b8ddb8bfb8036ae74eb8112c5a503e3afdad98650e06bdf187cedd8322e366fdcf66a49c28138fa857e261da046fca53c3e26bc70829c9c7c31e986c74756c7d7e05b5fb9ffe58b3963420ad161e688b7f45681f07337df6c17f69de698f39109e6c4149f7c920ef6a68db220580c87aca9c487527d3f90efd0ba0bc450f087f29935719276d5f837d19f5ac8cd13fc8bd302f2683ec0ab093ff267d582a525eb136e1c5b05638f807479af976de93e8ec4e92f09614da14523f8749d4e7088eca52d8fc19e5d3ed98d74b5486881adc2dc838e1c21671d7dacf770f37a21a1a4fac8b3043f08974e511f75efc452801d8714633f7700c91135bfee2a28a49351f4af31bad3ddc4f2c209ea5f89fce5508a5587573d63d4225b01f54672252484df714cd550a41ae4f9690ce39b3eac24bbeb28515addeacb340209af0c3614f45c378e7b188bd1f11e53369a2b2834ac9a07a371691d2096aa354b64b19ddff8c81ad106b5b50d5cae65b9d40637933a72e05b339cf99c49e1082f070a276702f48ddb8f81c2c739856333d7e9f828cefd232e369ebb3269be8ce4e9033953dc4f371b1dcd7c1ff2054b4fb8cd47ba0a9f9db81691cd21c072c43ebad720a6447a1c943498d9bb91ae5b0c91883053637954d95cecbd97f1402d948f65c194ca435fbb93ab306de30716b9bc8c372b44599d570bb18b78b48341d0a55c1ccbeebd6aa8b9ab28eaa2a43bf181cbb0788cc815f8add55ff6d2db05fab02c39dbfa971eea0d6654723120cd9f9c22b885fa3c720b711886316c9097422f05b6a84b532425d739ee767a0607f12a1e4151d346f55a1adedf2b271f4018631d4bb05230f8d6483a0b3a16437eb4d504b5f94ad7c0c89d6722d827bcc651ecb4e5de1b23d354c9ca637d4f16e301d18da1d70e57804a27d3ae6e4460f21828e3c42893ab844aad35bd21adf44361a009fe4d8c8f097a0921bf6d968dfda9f07e6bcf803c09d348c610f1d8ddd3fe1d9bc3eb7d4f6b947bd20be4d0382f0d7d3e8046a11563fe25ecad1c372c20463dce51dab8f9e020a77077ae7e5e395871fb10dc686c4994038a24e792a5f5931b1e598126981cb0faec3623ebb0e86a77005adc83712f36ed663d77d98846788a219fab9b13d5c3433fba0a059b74360577c11137f91db71307e741b4ae57616eff31d0f9cf226cc148a0fd649b4e9b5170189a60f54b2d0f3d94fc29c9a45c4267821f252c61446b0fdcd88b97f8bade153978167b239af314cef57539ff0ffd9c3607d3f63e2f9b418a33d5ab5258eb7875c59e7c63bd588233f43ff556c4cd380c55e87748cb58a512f0621991e1fb4844ba958ef3e56969f6cff09008d178971659ca62bccdb8e53ef5676925606fd853093d8504acb3a75903f3b469f1874ed70dec8bea325637d0eab28a8c8a74f9b8749b351ba504e4d29ed4eb690b97c5c71982fe5fa58196109e05f0bbb28c12923feb81c25bab144668b8d3fa84cc0bcd7ab835e4b865f452073e28c7f78fc66228ffa214fae476a41c960b5ce10851fdb5d6a6a5d9ff0a5990a3922c48003de376ca9922f9d44456a4c8d4f6746522ba7ba164d147975afd4f49f6773df3af13f6e0be804c2704d00ff112bb8d6c798dabfd9d14a8ab2c52b87411925cc5153279b105c79a36b6e1d6aacc9e24fffb655e8e4c3bff956ffa686e7991e0b6a8a21101e6fd7c3bc8930e7a88c6c37f6e458f38b7512cd01a67e042d6fe30fbe742f8c0e624e7381f227b2f43d57f5bf1af300543bcf4a1aba6d583dc455b1e5bef5543ebf46f6d3f36b265f87c9d90360c05d07ee8be1e86dd42258e66611c46de36fb5cec6719c48a9b0967c47ff9955f897d66c1426f6a6aa80a5c8579a1b59a8692820e74f58a26b3fb50e7b0bf0a9ccb74815a7ba158042db607f8933bfadfb259508570e8924883c1f978c103da217b55d504131abc8dafd03c6dd0f5a69b271b61df1a8e7e957590f520b11729b5a84cf04fc3aa782015be77ba7b7d1e0255166ba5f122e7cc2b768df65e842810c55cf1f78b67364c6abbf1f2b30583aea444c7ed16c445e183a50c3f299995538dbf5135b6f8559bd527c5aaf625ef3d4fee1cdbdf4e383bd039de5884844ac6bbe5e54dba092ffd02c4d761d25b95a5341ee4cb9cf4dd5a3746713a858b432f58710db5c8d7d2ae5b901df3186751311e312695a3188b09529dd7b317f332273aae3a61dbdda5f49c786a93c3f0b96ee30f0d02d88b06450cb0732b0be09d0b17c1435f2b59a59fb05030a726dd6ae4728adf7baa455b76eb663b13195ad1fda418d0f123ec788b936f6dbcaf403fa89436993980c796023c6fa52aedc3d0bf098be3bf47b748de5d5c91b752e666f1bd3392191ebca8dc3cb36d701203321a1ed8dc73b85fec03671756cd4c4f9d4901cda5ed754f05e217dc6e473666c0263c47a45b42e6caa3c11349412df3c4434ba22cf4ac62767b6e3be80d0b779e4e1fb9c495e2f588bb160cbfe3ffedcb0dbf4b13acc1f6c329e969a9810db4c10bf5d87ec8be811d59c7b9036042eaa5b0f12ec2ba2804abb6049d77c921147659f186b4c0d65bfae45902971de6a305c80d09f70edbcfbf28c79b960e2d0f0d489fd110a6d612a73f5bb0602f65fd21fcf8c289e2d5df63c23452ddd12bd86e2959a66ca3c6f8a308acdafe67adec2e4a1c8dc8ad5591af3cfb3d26da565ed29ea8c4ab671850e28d9d74f3996b0c43f0b0ef5d0ef04cce1f5fc02f202878bd70aa46cfcd08f2053e1bd5abfeaa92a964069dc4fd3d3cfc98894032f84d2002d78b0a94b6ccf5ddbcd22c45ca5172ca6a0f208aae7fa7cdfb708ab4d0a7fad019d17445a2e1e079ec867029c1df243ff092310c9790de94a68fa72582724853c0d87e76277673caf9ca1032c0672fb8381eca756a01303a97fba51c0d6a1b55d1461ff9fc9e7aed528b04d5f2deb32eb28d1aa4130c8308bfe55bdd9c51641f593bc5fd28505960ba2e1bdaacd0eb6428572023dc1f0e4f5ea1ae95b2c27c8293212a2c2b7d49afea5cbbf286ef7aa6815539140c201d2e10ae0a5e234166833d8f0dfb6f2971e921d445e1653a0359038c4d1079e80d0b96e994c6ff573bee18053a7fc3dd0b85eefa344587eaa7bc2643ae58fefd54ce0cb03c2e5f761da1557721d6611ae092b82bda1f33f910e5aad813c9dc6811429a770f18d52b02143242946c861f505780f31e19156b0a487998cd637fce3a65e4ffd13925055f6f60e4d4f7bbda6b1f9c5085b00a0bdf3cd4ad75029970ac2baa7beb886aa01374ba98b90dd78ffd614bcc66cb359987183f398a9824038c06fc2abc9db4ed38b734fada9cfeaa2a762b24eea3ff18fba569d3da114c8147f224b51b86bba68ce36d654ca36b9fe5d34a65578df64845ec43fe21df7b8b9f56ac19794f1e0d6c26b8996b77a353ce50c34412df13382e0a226cdd006fe0c535483260f8ea543c5817a009509c950461fcaa0c877dfbbc697a81ca360961b7ae2a72d5045e32606fa1eeecfd571ff18daaaaaec97fa02b39e2d261ce85966f687a9a01a7e05746dea3e41cdd24edd7f5abf66081bcb229e88853f09c6cd520eace02beb87302e437c33da22793cab46f239710cf279be7a788eec2be408326e2fce6ea10efa284a6d75693748819692a9087f97add394b6652bab9872a88f02df3545c9a8b1f70f8f7b741c408a3234b9170e0303994b4b9b8ad7bd63d4bafc010929c94e394dd348e9a75766b73d131264eb9b4d5f24523abc62bfee53bba8ce20f711db6d4e88100dd1194238835cd4dd785d0b013d58b69855f10db224b0f2c8a0eb8f1f9930f7ae181363f86b12d91addb95d9e3d0355f759c0017279a10f1a94a18f9a01f31c2f59add89b106129d986548a2d96f0b434c444cc5310183824e5a062bf481d37f4a9f6b88bde9e8351ae177d9bad1b4309d711b5a8adac76e9819175ab4c80102a7aef9c122233db59beb8427ca481c4af3b90c8b742eb4728fb9298f1756505d0d440208907b4d657cf928e4b8d2bd05be857543285e61af6ab8c806263b28817a8986592e56d4f039eb9353d3c425200ef8a6bb850a6d4294af0436516ae9f122a0b9268e22dd6f8a74ab19d10510966ee27656d049a4e962965dedf214163b2b4e14e39469a2c1c584da2c259670a921fa85d9c4e6cd414bc91b2af9d788115b33048920c9bc9ca6ce6498b395a3d7c3902b26c1812a75f18a003b2e089bc267a67b0dd865556de013e0b712603db15a55888a683b27cbc9129785c4393698b26cebbee2106337fe83af4276ff9f69fbb2e45872c12fc76aaec9ee56cdcf1e1d98e229ea093f2c6362131cf61eff15109fe14a62180422beb0f65fdec665250f178a3765d0fda32393595b1f88d0450ce6965e843df60e068bba11524369f620f267f71657665e62fbf15962e64b9f1371c6b86004bdaf203828a3abcd37b39138afac27defc2bd812f4ca05c4172580c8e9c1f970228cfa96a3c7b343ca2085740dfb6667254304faef8c0431ce055c29635df5dba397c236938e2408d61e5cf19fa9ced3c30038f147bb6a37a8da23fa8b4b0b52eebf53abde2fd008619e5e8889bc59fbaa3cb1e285a9d81275464b7065c84a0d8945741fe01f94e27c08b3389198e308caa4171f7c21080e8e3046322b4a32bf07cd63f904143a9885f8a478a4d7de42b7962c45cfedaeacc40580f760816207d608c5ddfb4c9784495cd6bb613709344f5620904023658a34113b8f2e714ab12f11144054dbfb5b412dce2a6cdd513313972e8c9e39b011d1292ab0da390f0f653e98c6b64f52c971d85f438dffc2042154c8aed5d273628b3eef0fbd4c4b9e39fb8819535c4e7960fdf0ec0346f0e598a9b115a4c8488157867ea82d5dcb1416154b97a608378a6d6959842b75eda1d4f2dee3f33709a2d87156e0b46a8e2bb1a9a5bc36a922d0d73894ec8db2171974ff27351b8caa7775df3ce51885f634dbf1a16c63bf16c877cbc460214c0333529d25f39253633934a33902244786b7738950b5f9f68bae09b92f2bd5c8e1369af4d344de94776b1f0e5bc10e4893b22e2404896c5422afc0c5fbaa5afdbd4c967f19710154977edc8f01df819428ec70ee88493c393425623430ccb8fad4a550d981fdae086e67b9d54c86a536983e97fd2428c0f128da9c45cf856808c9dd20f7d2ecd2433256e0ec4bdde4cf73685f07f8a30077f5b85e944b4878d60ef84101fd667ded4170e108a998d7e3776248c32d7996b9f805420de9fd2c25dee5665f2df16b148a62a6ac0837da8fd446e7449f9e6ddc3ff870a73a59baf0029689be36a9d64510b0738d6e4174dea249a3d6bf97e68e1f457dd240f74eb1a356defa9c6a060b855016966930734bf50f66f80466cf63937d3e3a92b3c8feffba3d4e9765433febc6e415e76d5e9c35dc5077eb860d99478c4f95b5cd4b75fbd35712788150f9bab9ed773d5df3b3af513f5ff31ba4307e3491197664305279cc02089a0bf4bcfa06f98ecd1d5bce297cb9fb3ea4ecd4c77dab06722591e28c56af16976a9e1034c63c2b3c1d544919d335c92acdff4afadda7189e0dd2ac9835125bc9681efba83a9eac066aa6ed1e245faeb8dcd97821bd080b521505860d55eb33ba5b73610fa2b77ce3c681a9bd6b83941f2177b261fba1f41790b58b04d90c67080b99ec9d03e246057d9e916da644eeedbb6c7a5d413240cb574df745e823c82f30eb0f76e3c82198abbc47224b761a5d81c88dbd48979bfa47027ff53a329d8df491a263afd7364d5d156c69c380fb9120b2e2c817c9b5b1afc6ce298c9528e08d42f1c15ceb167512f8d075ccbe83651e90dceddfd1c17fe3470b01209b3b6e9de642dc642454d11ef1d0fc925f2d1f082dda13c18228d8f85f7c5a98bf96b451dd419a70310d9b8c1fdac803e249d1f613b9b3ad1e53b2eec4c274ee109b45079bd7e4080454fbe56e7cc7bd0ec95e5f926b9d18a3453438bbb6c0bb47732aaad01d7b242edaa92acdcaa25dbaafec392828e811af1d226c614bffd0c10ab8482a60b0f7a62242ac09f1754063389f504985ad02c90f919312fe4395f5154af7357940034d4079fe935cd531e4e51998894b1a6519c02b183625bfa5dedf965afe0276ba8c7dbe8706bd37a703aa7524b06b5475c3335468e0c8aa33b4aef12b1b5fcddeb93d7e18a50f4f2ac27aa27de62dce797854250ec0ef8704f04d26c366c99fe57e3a914fcb640ae16b8b2a875804d24b058f4b5ce4d9cdd47691c9af03e354dfb368f0c14b5a4b736878daa8e1cc5dc12872b79c232391900fc87441e8aa8e46b61b82191298c51fca1851dc9570f32ff7e2e53c929c91cc7f33fc62e53ed176b0c01fc412f97942f063fcbb13410ab6bc0471c7325fb2f67b9e3daa9bd2f0fe230f645e7ac5e80602a6105c31c5cc26b62b418e81bbff1846c597eda936ec6e19f2c4aedc648b00877118f1b5b605ec38de284fd82e235fd6070e326c4ab0f7b7ce77412686d266ac93d4b4aa01bf6e4fffffa7ed9bc49c19a53ac92e2ac7a75bf0ef4f4c38aded1f0b67914816664ad8bd88bd021ec715155cccea3a14b4426b275efcec8091b49321b8d4bf5cbabce9404a5ee5f21b35d8154cec55e56afea7671135eae129e6cc690e39ed8f12c0196671e9a1cfd37453ff93552960069137fbf671072b140155950ecd46a9111c19efe0830e5fbe524660d7c986ae3da6014bcac15520a9ab98dff83fed0ffed93ccfe1846cb72f3e501acb99a228e8c8ad0e8b8e1519ea8f476db91069246db90d843c5a5e39b9045cf0ae0b7a6a6f6ad24c39001b79faf691bb32f771bcc8b2b8b0e76a9c6b2f51ec29c5527079a69ebd2ea32e28e56ba51fbb1e66c3d8c1c1141135d5013216859800425fdf4dce9e97398524d2695775b85faca77aae0a43b6ed8d096e9ba08bce5d6276945bc2375f3637aed913c5f2a9e1080997cd9c63f166545c26d0b7d917f2dd70674bdd8ea105b512028d153ae1f1470a6ad25c23d523f3c594ab1c5751109ac5e1f8f68b7de67fcba041f32c155cbbf0fa08bcbcdd2bac5702ccbd3e01184392d888957b34507d62985ee00f4807dc98297f1aab556976dd24474af0029d56a9f15bff4cd814ff681cfc8b239549d28da3af6e13334efac3ec750413eb9218f76d09c8113a24645b048ccab297a43a0931619dd32d6753bb96f2a115853201df2e71a7dc70a8911ef1c917de81079665d1b73f1c43806da5df89c8b563e8b58702d7df4fdf13f6510b91d8b6125529e541eff28e06e8170b5a4d2a0bdf56243f298a3fbd8e46793f853eb86603ed80a3e55721f6f1cfd98e4b850ce47a5008512c1eaae514267b7d961fafc45d4c44459dea47172fc41f255fdd3e526b2f8308a679c603ebef4205e1ef75a86d40bfd6d388a811c5c9ad3b3403d233f32cd9711ecefd319e04de6f4096e28df1d36165ac7b990879df03b1ed28d631170708b46d69aa7e984bd16e165106132058c9eb6548773e4b53d168b08f48e116b97c1444ff1518459724781ba082344d50ff5208bab5ec6ae472d28bce787f654dc3dc8a696506162a57482525f232b4848a3547c7c917e25b06867ca56312914b1ed04ccbd6d1079107156bd6e9a2010dbb5891bb6de50cc522d1794e15dd8c321e0d9d8f93c6fc7b45d534d689a8051f79d7ac0d255a8e8f3fa897f27ba5f3267c3a9849e31b64ef1e3569be801382490e50ba8f38e59d33ea159742304a0624992edadb0f0805fe92ba5009c7c91166740cf72595b6e7b14a6c35f98219f0b529b89d666c007243bc9001df3434fa6e6c382a26cdcece129b7f5b48de74616cf4b08a63937da170618a654e79143cacc4f3c2156438a9cf640e2e5dba46123a5682165848c8d8941c4e94ccff7256ba57128bb27bb3f53f3845eedc8c10304f29e5f83b52cfb1c6375b770430b38e4144da39f943f464628c95cc8b4066a407f9649120f73de4955d81271121b6887f252f9e3396214c60b7b0ef8d55bf49104f02e47f7aadadcbd810ac045de041833d14cfe764097b9b05f9b0206144d663ad99cfdd8ba347294624115364fc150a3a5f44cd62b0617452be4ef9d14fd62bc208931941ad60209c950ebaf24bc5b729d278e05c75e6fe01978cc626ca5dbbbf375877ef78051ba27ca01289b7a5a8c4bef675e99cd63a2637e2a822bd75c82af2bc4f3a7777c3c1d8ddaff7f4ae2c45f2ce0fe5a052721a6d59e9e8e0a23c470295176d03b72ef757b3edab04b7cd1c8ede4717bcd06289d1442ecce86a4c24a98bf5fdc7378252252b7ff807523142b7bc5e7a4deddc5c468325d2924043f50603d21ac77a3995a9315d3b648c935763ab5fe3d7a9eabbe8810deeeb19e3cd8f924149b7f61366dfbea551383d290a3074e1b2c08cfda941181a2af728293a8b86cda3ef3f12becee0fbd9515966be7ac0b51586c08a433afae81d390f152f099344f82dfe65639ae1d15edd2e302dfe8ccdab8b379f5b461c46231a6e2f33b113e07fe2da8f6a7dff5f6c9c77a6485d75623e12e61f9560a74b97771815bcdeb7061b0d1f2b3ae2e394c02f38e19f9a1d15d5a0e9804382c7412789caaad0125542777bf55f7e353d023b91f48a15c1048ae461b06d53176fbafa70672efae7198bdec32b48fbed66696fcf745bb5bf82ce2103529206e3c4936675f9f79a35b8fe48a72c6a8543fe9ba8b0216821f8ad2b82ad394718e811d68e3fb2b3bb101af0835009811933b0dabb622be55bb2cf0bb1be86a5981903df3a0bcc5cbeef311238ce508d3a670c9978a5d38965cf224a86229e7ea5e71f24c05874f4e182ff429ae954b16934ed8edbb1bef2dc7ab6d65ebedd78ae6b4899e6e4f61233aed3b1143b0675b2d413a8a00dc44ece849b08f1b8b1d0e782bd3c1acf0c56b0fddea5af9cbbafff906b0fecb4d0cb08d8b348fb7562e28a21da54ded15f95abe54ac37d4507114a3cb43e69f180f627eec2641223a9329a08559b26c4cb059dc3f5037ed54128503e1bd4962288abf946a1630b9116b0e619a4c667eea51f8e3db48898b60f38d00972e146c33aef48dfc785c4fe9e6dcf1b55e22bed976b186d128cdb2f8c082abbf699741531f07a68525950885cb536542752e7c7a6f7c84dc2d738bd1f9e465e2842027f5751046a8d75f178cf3e892e1fabdb6c6bd2ff3feb4ac469471ca01fe5c9c031cbe484f19b0711f19cb1747755a5c329397b31f30beec5f36d09318ff0e4c43e9fae0ccfcb4b73e6ad6ffbe068669834d6804739272f85dfa12f311bd7f89aca69ead16644aeffee8295389cd73a61ade4e05057f9b87755406429565d69b581712a1ae3cbfceb1c8ee9426bf9bd1a6659d22c7202675af17d1285fbbfc9b42aa331663dec3bdf907e1a702227b535d655ac470eca67f74826894b57223691e45d1ea7feee91d81b8d6aca19713fbfb81c19097fdd099a0fb19fdf3423cad69c2326a187d2b23220e31311af369ca9579c07e9d44ffca7f9303fde9f97ff1e8e98e07b418653c01dd6cad36fa06d37c1479b85e7cb19f56a6993606fbe7c2029f90e8e713d50b5cdc9553d8e07722dbd1c44dc911072a5850228b8719b3e6aa3cdb3fa75fa2ef0e3acd2d75e7cdbeccd0aab6c88249922bbd3ca12eaae79ecf9a8377542c521a13c9ca8ccb061db30cffeb252f3d603763bf55bb29fec9fe7a05902bddfa513b775b56c624fc012c87e679ab86b15b5f878631de3436644f1a827ff46ae671b794a6cd43c15a16a4628bfb26f420eb7796bc6747288208849d1bc00173b665718366a3e047807c2052ac4b0a96a8e6e61ccee4336f6ec8be609239842eb844220e54dc98526b597d425ff8eb0830c76a46a266f620d6fae411b9c1e8c5750d42a9e69287727c56cab474c5e9fb2b2c0b8b92015eb66dbb4304fca6e2b8201d802eca7335451b20b7574428dbfe985799d9039e6c959f6944a40c28464decbacb5e6641377512e80fb285cc53f42daec0619d775deda02731fd6efbb20ea48cec6cd4501cc036e449f09f0f76d1883ca75fbb9dfb9c3a5c0983b3c90ea9811eab12250aad5b96cc1f276c859595032d1e8b6672481dbf3466927baecfa7a46289f2e76e7954ff5d3e9dbd73a9a3f256620ccb2f72aa0b1d6f5803ba16e4952a0b487b8db7bf24da00ee23558fe748aa6c18e45f1a25404cde14d0739aabefb08e28c90d72da0b863d1743581d7101c6d296d362ec650e3615e84d5061ea9a0fb5d6bda60670b32b22c4cde0046f3966377327eb83fd1e17419f9c3f4ce110c75aeaf0a7f42119962067a60485b398ed4a7c2083a39a0e6e8f3087207133b83d204fb171fb6ad918ebf4eb0ded2c82e25bf2e804bb90f47f72d9a5474554c338825be2fb8877a8068152bffbeefb886d77278b1ebe3838d0f510f7fe97a0ed658282de4d782dcb3a4bc85e00c211d2de19d362d54a26b8ac22c0cf005dac13d5df61e20520fd8891480ccd0215805d276ffbb501206beba37472df00478fea7adf2e59d5b055486b4ab5991dd1e4ad5d23961ccce362b2085e702920323a09ada4c3c102e4d6a82e4129891fac9be73ac34260383ac10171dcf53f7a5e1f0eab19cfc9e7f99977c39858ca0b8b9f4c9eacc087972bdc6f3b1df7c29b7ab7ea1368e9aa70082ccb18a5d82097b445ed0a8f02d411db6cf104322520027ead4569018fcba09913ee9c1e2feaadd33c81237c018f218d40c02fd524d6a653bb73973744240b9afbffc5ae602dedc9799f3588b1ae18fe937b633e29096d5481be676563ef5c97dd5e8d8c14f42a52fcf6adfab9789908130f0c07650bb691e28f0aa757b8d14ec5faf128df04e97295bd9e0ed3851b9cb6d2d91e0398d0efaf9f942246f5ba8b07b071eb85b6e5c019453d43ebbd6b03fba23cd099e38010ac707de067f4fd5506a594fe4dfc8e71a98a653c7628277b2866edd449517f0d32412134960c9570dc71612ca8fb843df89e143387b64318a890687afb8ffa02091341b4cbae0e288a89e6cacb732113bff9ecf5987e88fe9ebf6ad66b1f116a2e9580c39b2c0b717df5b4b81200ed98c125b379253c10e7496e780bfcc05843a189e8a8e2c5c1568ff5616928c6278011b6d3f96e47ccf6d21f589d7424581178b8623830d679b9d07f05025e7143a77920dba81ca4fee49b1495d52e350fa3cba0410bafc8115dc0ecbd85b574660c3becfcf08c37bf7136c7ff3756c86cae40dfb8488acf7c47c4552c12766dec8f859f8c44ffb17a2c85fa29073411d43a3ab15ffe133a3d6f004df52b5b28e38656f9877ef7376f8abe17ead8b62ca0dbfe12b2082eabf0b9e49e421bb13b8d1da75009d37c63314d4cf8dc3aa8499dba5ab6928bb75fc24a0cbf4058f060e2891465d54f16c72839c1fce066426f8a728876c2fa002bf420485ba007aab80c0e6c88d1eca6b6101c5f7f980718a89db96afd6bb288cd5c06ef501887d7ebe9cac4cb882c8bbc7823e745ec0c8063ffdb6005981f82d0e3cdcce9e009dfad8c6211c6a64c9acb4483ae938690f7334b064002bb78e91e08e24ad73645df7abb6a539d8006edf02c6c31e255ca2bad13603b032606262b5b23acdbda2c148e73afda368adf8b023b529e130e159e085bf6c44e93fa7c8e0317767292bf66f460453ad6de09678a50831328a60214641c76b1909ddbd6b539656112a8e3794f666cddfdbcd439f7a00d891bd98045e1c2b62872447a995dcba76bd46753c6551b42661704ad3bef32f6a097a70d7baaaf142d1a6d7b98549a88e464a199975c4a444045beadc07a62bfd791d1fb0de4b69bc36460705e3c1c3090242c1efc4071a310c292c02b9f5e3fcfc360c7fcdea0010796487f50c3da0339496042fad44365ee5d504ed4ecacc6ad281f7a0575aa70afedb79b6e23f3b44a9e9b8d9c8e28578cfc6a7fc174a296cb4f1c20f8520522d9c61f4fb286989e503fbde9a696b8d0be72750c5d95eda728622b4c331a55c813df59114e508e747147acef067d444607c5ac9e7f0f0bc1642d63ac7cae33ca04f722314f30af4142fad14351c94dab0f946dc078dfd0dc33649faea7b9ab4c263e994c645569b68a5b9a02de550aa53f77b95df467e5e08889fe34bc6abf0d6de2c359ca063dc7d484f63c56e4e78cd520b8d4df9644a4f0a8635a8fbae20edfab083fa26e419be148dc84c4a201432d6a3396b0cebe45d713f546000c7d0f65c3b3fb30639fca5ddec4897a68aad01ed21856ae8875d2b1d8a0144bd26dd760a37560cc8b14ed6e5c2cc55d4aebda9e8afacb22d7623c468e93c323048351d2b7e25776547178f4cc14376dc6fde40aee8f039128d0a08b5ac03e8b8603761563f2be68c11eef95798232922ecf62cb17303d56efdfdedcd160709066250583f382cd135a925ef8e1ff390e2b849b35675c5e561b027e77686c666fad98117e95d10e5111dc2db57a19ec1725f25bd7e635f295a581e480b52587c0d6fa5ab87403eacc461d3ceceddfff782a237996ed01bae3c7f8dd0bb48cd5a9746178b70ea1bae8bb0b4f8b1768513f63d7224ed2aacdf3dcd671d883ea45445ad5d2e42f1779979c70d1a4604cfed2820106d4353f4c8e4fdf09c8e84a22a7b8d36fb23335f3f68aa9e16f6e299203b6463f8f3a719cb371abe83fedfa7e4bf7cc8b9ee7af94a7f228b0cb521f43de6f811d0bfc173dd88e24f0f3980ebfb9b4a30420e09dbf24c7f32d608d553e5f1f42b42fbcaa259a0e0cb8d3a2dd28b3483394c525fcb49f36220bb635720450484a3e0a87c9b6910775f41362828a4955c7da11e02a0d1fc96405e09724d6e4b110504f81b73a2034472c8e075cee9670a039de0a6ec0e4d97b868f669d30eb5d3fda00b1d8f55dd5bdbff0558e5f07b5737d4e526ebf5c57bb92ae66105a9bc336fce1d238c789b2e83e79a480be6612e8589fe8b61b163c8f07d92139fe5e5986a6a2ba04599c0d2b1033f55ca5248e1caf598838c08841f5294fbed2da1ed1780dedce0ee1e017bd66f44c925c803d20aeb4dcc7b9a9e3fd843b941f7ce98a85ebbe6d4d71b52b2a89934854df8ea877b930399a0f90ea4d186679630905aadb93b8685bd8d4d2d2670e201af04c719c24b5e0d02bac55a0d6ae056821bee8dfd44b54f511cc6a69839ee5203d2f8e7abdba1be6d353b24274e943fcc685bfa5aaf4177f477d0b7523f3d3e394e223f2264d0442b1a49d9685f45b6e5d30b47ac1e5087d60d22dd4dc0beca82a2e0e5803ed3dac69ff6f90ed25c415b567ae5bc8d2db1b23017333686df8f5ea2974f6ee9cc6cf479dd63c9c6671c927a2fe7b93c2e5a6e402d797c8aa4816e03a58884aa3adbd73026e359d974e68c5305dc10e137711ca218bd8c17c1d99a687f0bfd5cac368bb4e462e761afd53903687470019977198bdaf219fe1cad2f1d6b8be192c482aebae986dd59681eec99a47dc9a050a8115aa267249ff0a58200a9ba0a3c92b3eb670db4b905709234f48aa83e0fb551ff8c8c11eaec1d986e0a53ff00e89c5442f7bd0b5f1d865e0044cd0f2d47f6fcd68c1234d756f76fbe41a942fe052b7c1d02d20ae5862359454a45b1480a9d03112f7c27b08afd51aa9c0d8512f63549c15e890cb26c5f0b6b8d3b188c0c50195c122a1f7e8b5e38b45b41440221510a3a1bbf1b5fe9a3f3e5988641fee4e0568972e7465569c73b8540da25837b35b04fa0a5dab529d052ab8eae7614c67acbbc7372f3d450f9e51e71aa1e95f16e1d4cd168efd42f094fb6d655e094d3f94c4ead71449a5da9ec9889a85b9ea1d378ccebba292cc7a5235f6f7a985a211705edd17be2bf805f6d6531b9d7f3e4a048618e3c86347a6e2e28b662377229f4e8731aecbd5377daef516ff6169640403842b6705fd341fa61016d94f367854bde22e6b490f0d8031e388cbaa97625c822a736f78035f71e74e24bbad4ea783063363f425f45551fb258dbd499d6648cd7f5f8441aa45999b33fbee39fc5d5b4be5c2318375fce0da8188d07c05c0ae59700dde6b3cdee400d8b0a59eb21786e587590e0ed41c6a2383c5aba2cbaac4bf4d7faf1996a1bdb858596e15990f9b4f4a8a6efe2b7c3f5cf9e82705dfe98b414a65707ba9ea353257434ee60a03cbce85e07ddae34e408877765e2ea7317919007667fefca4735c7844f41514c4723b3270b69a3d4faf13d46475297a628ddee956f783284b52a1e6155945fa9b7d916ec64356cd2366d4abc2984426936b0be71470615b35d619ccc05efdb27dc164fc69a331f9159f4ddb6ad4656aeb351dbd2c675ed6374bc7dd1a41a16608bff0c3eba4e37e18cf9508c9bfc87d4e3bce9423d8e56f91285dcd52e90af7596f3e4c872bc7ec1d4ad218ae2d0785d5bfa778af8f24102a14d1d58c766475b109c64553531a25bfab39b63452a3211a6a85d96fa3dd36219f77338f8d2aa8e1ea050002dcbde2313ef3d2b510e6e0501fac0b36f85372ac95eda87e41638b9308c3f431fb812e8fd9f61ecd63c8c83358c5c6dd18c6eb45fc6ef8aaa7fdcb589a0a3b4ef8cfe869f545936d11cbd0bc3867a6b5df9fdb2a14866d6b68fe4c8ee29713ff334a46b5e673ea051d1465b0f4d0ac77b4b30e2a012e550bdf95a40e41f45d2a7dce4dfb837824b5a9e5e9f74a2abbd1608366c2ea9d7fc69a7c9eb4f9822184d5205ae3a935038f3e862fcc21e41c7ebdde5991b2f2c2d552fbb9ad58ad08af07679c085aed8ef09efe01b08dcffaebb9e471119ad6778b9fd3a79c1e8fe0ecb08797206e2391e4a8a59a3acd1fe77780c1cba87117fa7f0ad7ce8b5ed61c5fa071a271190b1a98eb4e2cb203d35534b95a99072bd52cce475acaaf370552d7ad4a6a99b2adfddee7145c76bcec1e422d79a0d6e893f45faadae9f914e7bb376a876da37f6d52c82c24c13a8184fdb84ce5bbb5e505b94a251cad7a5cd6b6634bf7806f7e64f242c2a068110dea1320595a568accd2ea3b6cbe4e94b86245c035e34a686364fdd193bb5345e6076376362af31d5cfb76f504c836fd64038e448576c689480a5651cd51f50469bc55cb24eaa90c70b954da5bee839229c45b1af8ccc5b7c66d25ca5bfcb8dc8c183767e4869f03c9e5fd8a2d7288faec469bce1c2a6a6473705457044a7701ef52b78306ad964f2efeed91d73f4c170907df112611ac86fc97131ac3fa046bebc46a946c885dbbc2df252062a30949cb86dd08462ed7e05caa553e6477a3bece76491f387c5abea62aacd95448ae80caf2f12cf3440b97e2f8500f896c8d096b972f25ad9a08fb410d09b2a86d5e22fdd197bcb8a0bb150264011c8b646f4b99ba5c1ac952730dcee6cbd8682eefb7b88cf2bb74d830482fa7a66bef77e8d071e1f56e90ffbea9fcc901b6d47f6c7305f04985250e041f376942c25bb00e5e451e81a754c39d0ad369c2f6140e9481ef5584e9a837a766d3e21cf53f86ea28a11d6d05755fdbaf1464a7a9d570582cc0c6b3e00e67deed72aa404fa5f5f7a3e7fc89bf5b87d66cd72d4c6b574960cd483a87ca617b3ffc6f59fc05d602a718489e8acab983fec61a20d3a2c9bb8790a91bd73e061db5182f4c65bdce2bde111f19863143d959a47c4360ceff5d5b063e551d6e146b0220e0b3d01cac2f759609fd7bfe6d0c024a65839edfd3259e869975f7114d4938abdb8d597386c9b83335ddc8b2f589a5aa44e238a8c719c862bdf1a6ed8bd03fc6d6d95f6c6d78e8d7b600b14f25320505bb216156b78682e8f22392ded9fd53f6669506d47532ba7cf223dfee290377d2c6124d01af452c34f369c37c805e83909098b36b0bedd7a15deedeeaefe46405526bb545352d58d346b06a4bc1213360596c94cef8385a97261f9e974ca7cb97b88f57f29c214c3707cc567912ab9b9d99991666d7cc55988a7af38a76d9bc69223874c42f5884f76b3f8031037a453485bb6d6ed9bc4cb5e43015750e40de6fca3a513444492fe4cd68807b29ff9359a7d60f0b204bb88ebfcf21893a55ea318d0db651907dade809a714acbbbf36734a12396b0fb085df73cc9575966c2d9958c272b04bdb17be6db1a065b7fbb84d86f769fe52f694d71d5fd8ecf9ca0a0f575bd564f257b76d983ca928b8e4f4a26ec6e2b8830a3a46f3b3b0bd574076ebef543410bbfb8ba3f8085974df5598f01b472b68a26abf944470c97b37987a33f72c68fd11f4c7d0d2a2ff7670e7aa9e88b99017d875e090b22735b88b83b5a7a67f4f4dab0a501ef96e8b4e012b00767c2c9f842e394e0763a5e7c9e674d8223a0c4a33fe36412f9f1a16d963d7c6660091ada332ba2d2e3edc3726504627484994be413b286ab3f3275815a8adfc9e14554f34b32c96df7ee861493c66ff81381293c60f96ccc9691c3013daeaf6f0a22de1650d3515a41fcc052f69b74a505b34b837c8de1e33dcc820745d2cda89e7fc028cb9542b8dee76a85aa1450ce7a1639cafa3f94b54f43ecf7b08c725f8c96f16e309e3cddf513a3270a76fda1cecddb7796a0cf2087f4a85b060b32782e001c3644d80e38e07006f5ccc2c5ea95b95ffae9903e54599b0b20b9b5b82cc179574acc8c5ca85697a8e0df2a2d5b33f9773e2b564aa8bde0967ba41caafd038afd9eb3611d3c2000ca5bbfc5790b173b694dd9632fd2803c8db6da1f27823c7bc65efc4aec14171532ac1f4e776944b16e76cbe234c4d84dae73b600e7f8bf16dcf32b8504fb608207b035b4f814967fc743c0c9b39eb57718b0fe055b6e78e5f920534b8a8167a1258eb71873d8707014ac460cb1c6bc68f67a94eabe56561c76cbfceaeedf58f5464ed7c684cecbd4ef944012f7f2dd87c4230852a28091ac8e204e9f4b3aedfa1a7a23884e46d922316d4ce03a0dd00a2875119f26e80d666bb1bf994825dc7520e710df14f16f82eba78a2cdf293f213c3f141e4fb4c13d9814f3c2057ab403aeac3becc6a132bb9035860f89cc8c2d4296d7190e6384c07036680b5226852ee6dd498932615d2b992f19393f815cbd90e68c7f09c1dc5add623f75649f52a8230025f470bfcfc240c6ca7c915cbef026c2fbc285bfa347acfe0450024802dd0b0bb7cc7b0d7dabe9af10725364e367c63a9a7fcb85a4de28a2c43227f0c7c31ccf63aaa4435c1b7681cc9ed514fd575b4d5054c1a0d5834e78214a61b4fc8e74939fc5c08be5e2b297399ea91ca28ba44de9a723c29659087109e5d2c58758d4e31c14fd325335f91f51998d5da336c9fb7d816af66c30d538776d3dc0d5497e8dd77768d69a645dab8ea792e5c9a247351289655c3bc4df602d1c0c77684528747d778b45d595c4094fe80655e3cb61057f8f1e779d1215efd0e0c7dd445d07fc8c97fda8c897585dd07a37267dd1eef7fb3da7148ebb83f53ec16048ebfe089e5a7ad2466a25d6e3510a70686818b28700038d10643fc9058774f54ea0c953fb6c5f4cdbb559743648ddb02346ae3fa28ed573b8df2610bfd466bd89a15c0a3dd142ea191d599534685e2fe08ddff4709e8c0b7ea6c80b100a170f315fd949812dd07823b09166ec89200c33c1e92155ea87ca2eb561b443882b579c132d16738cda2e7cbd7c869f4dd63d1f948bc0e83689429f07937cce1ba4832ed6cd73c07e77958d2e1499e0d59a8da7fef8c01931361d112cb00130991cd5a9d18c0a7440780f369441769bdd4764516ade05fa84844f29ec0f229fc9aae242508ab1c8c0c34ea973fd8ac4fbd7cacce13a8bead555937d76fc0e0cfc3b032916a8d73a7deca03fad9714bff92225e17a0335abe8baf1f0c2def50f4ab5b1fe2554e6877d87416c5db11b1526f57de1d9cf28f168fa97f02a8f355d9d760f5166e431c6c56329313bbc54a88298062011d056094842ffea9bee60ca35e27b7077306928d6ba1f983538c0713ec1855d5831cf3c73e7c982380945aee0ae6d90fbf2b3314ffac60278312dd264de0d5732c691394cc6f1c181abd65a51f42c28800994b91d40bf0bf067446553c73352034bfad5e0b7cae5eaadc18e4323d5114b81a89a7400c684f2f6bbe3957197cf981af53d11e7245f32eb4a3ecd05855b7455e83538c3f98f54a5347367721e53c7b0dbc29f0b12e3d0e1bf92bd93a14c48ea530c44836e71edb42e53f8a965d580d3d421a177f87a5f155514ec7c9c59486606cc315a5ce02f24b23bb084509cdf7af623ace88c57dec4ca6635aac1616245877ae9cf75a7251a28d689a01bde17f2c4007d5042f36298037144c54a6218e3d76fec18daef624003461bce18525f8d22074611760e4968707e509408ca8883ff1604f9d7f3f71232d237a9f20c2c55aee0a83a4c919513f5e8ca2ebcead6cbf536098f896e192a712ae8bb1809ecdaab79c99112c0abc7f465659fdeb10355e6e9cdbc8526566cf7faf6ab94b5173c42e82c1ef22028f650f99919652286a61f49262a123a5f30d96bf30c95de393f3286c5702962d47bbcb390456e82bf6defff7a12e107cb63c7cad50ab4462265920c4f59648aebc0ad2407184876f2c368a9edc2a32c4de77d9db7cc6c2426c31b49c8dae4c02d43a407e3f19796d8d1bf5f00a3037a5218a5cad4c704f93dba925327d0988ba8562723ebcc001a111e485f90875e032f6aa3501738195fe2cb6dbfac5c23981b6a78d0eedd1d23005cce1fa4c82835b57139abf97c6f29d3717df47c4c86e709995f790252e9669beac9698a8708926659642762679f99b84b160750a2c8cb89653dc61d3586808b9f3948fd652f1427ffced04a443f1a6c4642fab41efc896f08aaa0b32b25a55e0b4b0fed600cbe5b258da595d75543391d0422d32bdb09ba096950bd9a3f00d1261203f5c9abef895f14e3e81f1528e23d6343c55aca34127ce14e655baa31f2401024dcabb8498e99f1433f7154cc0eb31c385920154d34759624b441b6032f7d4d5bf8a8f68e39db6f85c854e593f313b51250bb64ba2eafffe4b9683060360ddfd52b79bce1711a14b811749c47127b36534145e648d9fd62b9dfefdec8df12643fe5c6aac02a465621239c88b00209027ebc2f14dd77a7d71edaecd5809f2a2539ed9f009befd23174c14e1e2f9c4dce750ace10c4d46e41a49dc9d08b004fb3d2d16e985bf98774660880aaec38416967cbad478bc402cb68ebbe2ad5b5213f58a9f25d136736051b328c30bde99f14b653966bbbae54c97f3e77c6cef3aded3d5df4732bcfcf49d2837bcc775580cbf427d3dd6ba77965698651c5b92d9bb1453fa25024f9ba9137feb67e4c6abe6d17dc4e21d74460a4c2db2940abae5de57e70b9c2fa4616ba0bd99811a4fed65043137c7b798ebc51aa7954ff704bb17201fd27abbc4d024b5af89a652d07f48fc3826101fdcf76b2103a55018e591f558c7fac33afcbe84c771915e2c23d82f05d5ebd7a649ca890b1ca9c7561a3365cb159e6a5717be08d00dc5037ad9dd528cd70fc1eeea8b77408e5b040cfdafd3a861a55c4ff29a5dcc27cdd0c3b0cd7da36691ea70be891472ac2bba8c98e962056c20b07457d0213dbab6fdc1d407c0814c788ef7fd660f1284fde69a95f0ec5938cb6dab2d07f7a8e24d3be3f1730b00d80fd623b5e11ed8f1145ceff4876a00754698eef6fd9816be550dcc1765141e4d6db9e8a723ad3ec69edcb79954d0bb73c4bdcc9e716194552f7cd26b27f47ce523120c89d9fc06fa890113a4839d2db22c98e498e139b39ac15680aba16be2604863ac808abcb0f2a296989bf1255ff2f7fd6d4f497d0874b4c5147fb426b925263bb702ea5105d722f1cf691c193ce346acb1e128b5add95041019c410c8ff1477d71f51de48ff324d4b3246ae61659f5408e32776ebf0198136eb7047a9719b655f22733511e2fb4d336a98e661f16ac11f446579cde9c6370b648a846265b8c10a5183ac8448f62cbbc051aa1f122336b69f4aa1fb022a1d5e71bc087373c4993ca0bc6b6dacc9ce99db4799c07b7862a88dfdc135d2efad543b49364e8fb78bff471c81579f82b05a8a6f6e541b1708f7dca7b7214eb7ba8c17ee2b1fb129e988b9e02e7022f976e9b1d7ae664a0a89e8b33817885900fd88cd40bae564479a14ace87e1175e5733ac562328985bbc6786435df966efd53583cd44f8833d1387c27d09e19f180fd50305f867e82a4309590c2275ef0db8c1b8e2c5c831c729dad60fe8a8467b38cb3d9c39aec67f1d5c821768585dd4be94f1601a83b185e2493dc441f1fa20fbc1eb6a29af843dc3c49144c64572c903f92aabfbf67949e58c04fe79816f271c6d11854565db91e2c7adf7f6a9f3de4cc3114d6207a5ef3fe809daf2509c433af2a77e482d685dd1a50e980425294ea5b289ddb6fa71d2b50d8115f3433ce6c7611bf675ab2a2c936e845e17846184851228b14c6ac0c95cdd7244f21f4e707572ceeefd6806be75b6ef73be206c1f5c1cb85c90a298bba9703a58c5bf9677fdb692fb8067431f8651c45fce7b1ffa899db9079448a4bc4eb7067665651a1b85d23239ffdf0e5160428fc62ed3d4482337de8ee344b1c08cd85c1d4b44a56702c4ea18bc01f7811b576b783143e8c507258c76ca4631dbda7d929e970447cc5a3debfae0cc73d654c58ccf4cbd3bee23da5ead75aebb0f052654e1860cd29e2b1fd998e81358c19a74b55e53bfb34dede7a31e9b4f89b224a2227be3d25fb06e9e4c401d4663ecba8cc2ef823fb9db6b97de149149f85814e8cf15775ac7f16c4d52ddb3e4b5b0b44b20d204b3ba676252456cff240b5a41780326b13b2b28b13debd44d6b521f521d5079b96da715687b44959482e9defcaebaf1fd4e6db1ecb48cafeb03a199a96c686b69db75ea049cf76668e95b83ae7bc3e6871fb458007b9d939a95eff909ae917fb53b9e6823e0b3c1f6ae4f868a2242415dcbec3a9b9bfb8f669cec4b13ae8a6c609b9f3c69ae7d01dc85578aaddf2f0234db44bb219277b65bc02b15fe4823a7cbbe78f948cd9140bdfb2be6bf7cd53546e9ae87ce3a5406a050a62d534bc390c1922fe5deecdd6906122385822c538a00a462ad3f90cd26f09e39289663e3f5e6814dd5ea9d831b6828bd57e7491e367dd65bbde1308df108b7234ba4ba91dc6dbe5f1b79b65b593fa31d5497ea7d2e05a6d800ebe335cdc7148818cafeeea2e32876810ce56e0f51138806088f0cae1121ddbdd8d9d9d70bcbf166d16bb06d34fea8565bbb7c648327e8b1829c3a0b8891af8ba03083e1a104c9098aa0a1e63de08d8bf1c977dc7e5c4b00bfb7cbde29c15cb70758af36c5e7e2098fe01e4b8b1dc2de497e064b61316443f388567a738431b8d24ecd3affabef6d4ab798ca1456691930550396eca309457cecc7ae626d41b4be55c3d26f21079379cf17351cbca75b7e9bfd47dbd9b3a77e15dfe2df7ce51477a1689fc8ff2915b438b2f9803925798767b7f2442801d723b2af79c306372bf9c80a088512b247b742187525b39065c0ada7613c1d358854b32189b1c195e4172ae91429bfe83d788c0a903f7d1ab17a53789c11cbb5ca12f46fe04529248f1142ba19917173633ee3deadaed4f614c2ea88b9e1578ba8d59eaa5e4aee4bae690424309c31dea44814feeab0dadf0faf526b805ebfe65688d93499f12d34df788e4682bb041b28bfe167ab91219292c0cf66dcf7c797f60ef346d24f8e4ecd64322f6530c5fc94f3e12b792b524fbc0e50f41ed16795e472076b3f8c2bcd3672b8654303c8bbed312eef3257d35ca7687745122c39799a18d203a084762901e3e15f57fd0173f7a36c6d8aa2a58245afbfbadb3329031fa12fc123ddc29b5dd2ac0b362f5ba43209eff3d2e7d68e4cff716aae513f930659a32ea1b647a3fdc0944929ce87b3634556e31fa10ad1327c7da14d7536b003570e241c85c38457c30f6d5a720c67ab076931b31095a13bce385b5ffba48507aca8539cc0c8c6fa83d4c1ccff0a6d2f0c92e8bb9890d693a5ebcd8e1b9b5ff24637bf536af6b4f48b090028c414d652bac8a564dc254d7ed495dd7e08f330d46d6b3f15a004dc44835add529385947e217d1a3284f86990d2c9a58c2b090fbdee52001bb89a92615b147addecda007a967e8db1bf4b8dfeba98f4612636b61d6ac2011d9f596ccc95d5f13b7411575373ba5677defaa3f8177ecc57842f03b858ad95b77262c7b61814cfd0455238d8bb1e646d9ad398160df2e5a84567771faf71e42192e641b643b694c7f220279ea8e2b971ed38ddd4b2c82974ae9dbac9403769349ffb3d89364134bf2dca0e9b5af5ea448c36e820b5cd368f031c15af44a575bf290362eb7baa9944720ab74d3959a0c86056b1f5861bdf281f7af67a9d5aab31ff721c7a8aaf1e1d31c35376bdc0198e9d9a3508bd85ab3b0764704cc7c7828e58b01aa9cdb115614b1cf65eaad7a7b444f8f783610e55d0ef8fb8068d43c6b460fdcec6287a4366e69e18b4f74130183c8f7ad752c64a1053f8a931e77518f5cc2df01026f35f7ae2ad15eded7faccf48a6c890ffa82e280979416aa67e69f881f4fbd843dd966d7e3fae443795abfb8b22ee4911fc57fa89212263cb8f83f67e21afb811bd0aedf1e28741e786cce45acb7da5d87b50a1c2e91275ae0d803c89148dd60fc74ead08eca7ceba82476a8c2c0b5b8c2558c228fa7bbf58a9510352fe5e3a8259ec640816fae312c864c09552b053adc0e1aff562ebd8b05338961047ad31aaa8df966b0ed2be6b39027f10ed8ad62161115cb3f1950f5d5a4ec9ca9648c4179e192d3b1f62aa52d31100849d39c5d9fb441b85cfcf397d79febfc588c3380590fee210a042581c24180e8c5963e80dd08836d6b592169e2f8803d908f4a8ee61e77f0ceceec8dd45b0e2b6d0314c6cd71f4e58d2fd3d882b7fe99db678a6d7d3a1e1455a47c469ca0675b4b7d0acc3a7018c7be6d3a1cc3d4b4ab56f2deff041ebd6851d10e2547f5d18b1b3f2f2ff3057a6cac9f89d30d2a8879d4fb63190dafbc94fff722242ee8a6af5614ac0898a01b2d439f7985b9725e3d208fbe1ab5cfa3a86a17816fa03769225a1130c5b9311c58ecd7f3468a5a6243478a61d4dc5830a345194980e57a0ceb9d28acf5a804e6957f5e10d3b26e390e5b8a6a0a9ee2894447c18150d1d98538a446fc2d194b2de052c940ec13ce72f2cccb0c9d707ed98348bbe6281fb857a885389270a29ec2dafe0b24c77aa9163fb1db9ae4ef8cbf90537212b4b88a03fff211fa96cd3df0a0b2c787bde7c16ce2ecc7d41a4362e42007341e3077bb710f5ec7f45ac002187a217caebbbde7d88aa9bfc833b56780208a903549347abeb88257a4bd3877d943218ac1c4a0321a9dbf3f7ccd4937cfa9656afb2e017f2cc6474e66643cf7257ec5fcadbba03331a530e771c32f503149f0f7049aee1166e9fb61bbdfae7b1497dd8f1f5ac7136c074c5478041244b2de28bd8afd5a23354a7d47719b09ca22c09e89377775381f0741275887f0a82b388aa59933aa57d2e110dda2998d2e0cb7f9f487d6c39c1371e58d5cb83554f0d0147cd0f9a09b88463ee2acbe5c1b88ff8934794751d7470190d7e7ce9794ad57151ee97bcbebeb539c4d165577cafdffd6ff1aaabc86e29b2354a0c067e7fe448dcde6dcc78fd4558ad3a5a63f0f954f23c4791cc8c585c51625ae11fd2f02cb1b7a1abd232f67d8ce9577dd51702bdf97f6f09b0392c742895901a17c01064f9c29fdb9c42bf58712f1ea5ef48e9eb0f8933681e59bba3c6861464b288487d355323218d0d82c2730307455fb1005f37be81c5aae5278c923ed0db011975f4a4eddef016fbfc26b01a901a9ada4eafe93fecbc72e3693362d9f4058a5e68e8a71b6459ea57ff4f83cdb9e973b70dd433ff29bee57bcc4aaba572d128a565054d4daddb0f7bf093e87e8949ff222c35284193edf57f70c5057283ace615a53fc29696d376a44954ffa687800be9cd8d9096a894547ec8befa0720a92562d45d32945e70b8ba18cc51e04171d168c89e09828a229ced1cb1595289b1e0e29353d9e6293d11447f2eb7f0c896a1a227b1f2617fbc894408fc8d9bc2ccecf7f76b598d8039d3b43215eb473211a509a8e8dab6725ea83cfbbaaea511736e3f08449bd0cf81d273fc3e34d717c203c33e66568a6f8b0fe6b9eb421b82f2f4514395c29de6878ec33a9eb47615790114903a658d2b8e17a5dae4f3fc63e3ed0b19d001ed79db19155eb73c464d30e4a07928d680794925ad481ca83dc8ac62116e865644a08202286d4fbc98e358b5c686374f9dd698ef0dc53dc0b3081932251aa6787c58e2bcc0826aaa02813730ca7b16e6fbde40e0f2639170e33efa37216b8386adff3653ad499afe14d4506fcecf4d7273d46f72460ab270ebc9de14d9a1b33790dc9c5b46849c95270ee62a862b4203fa1fceb085f5937b0accd587f571b7a0af95a876988a491b5763819a5aee06245dc71e353235db59c9ae438ce28375d17eb68fd8e8c3d2d3838c924b53ac00d70efc6f4bfb2d8270d29cc6ad3ef484d8c431e19e552d51deaf471b7f62aa5773d88fdb1a6914d00de5022b44d6d547f5d8d9cdf4e8daa3ca94d46be0d9643ace7f77d7f58e769fee1f16708919a9d502d8e5004894709e9ca979efa6b308f059ce4e16340cb3f4b5134ddcbb28ebb565537408102fa787961b0009750cf8ba24f5d2f7376d7fe450abe421be05866a06f9034eeac2cfe7646d34ff85a24c8be1fb648981d060ca80fd5893e7365bca225bf89e0c2547a7858968f9fc6e4e8164c48c16f18d33cf0588ee67f62abf2b004e1b45501b077d0416ef0b6776fc3f3d2f73499b80a8bf232a2c1056fafa5ec84817af571117ade6c0a554c2bd890f591967dbd07dc1327b715e9a2bb6c9781bd6fea890b01517e00d64279ce9dccfd63d5bd53b76570071711227da51dc6c2068c2241584b4942be7904a186d0fbb7682646034b895079e6a9c6df15759781ed3bd92ac2f37a8a0d48c43f356f1f74fd83d26afcc7b4501fd9350b807575ce9482aca6fa7fa7a604543b3d1ba85fcb5a5c44167725c3bee7c5282c52c6468aaeea981d143ea2bb4c9a0c26e6d81c077a17bbf254d60a5f147783958fe0055d00dbf570d84076ef673159f7b4ad0bfe22207ae48f9038126a468f562981fee2f70536b3fcfe976a2d81807428310cd332db5beeb559463b5c9ca527a2cab8d9fae3384a68a383de24ea2e65b8515b45fe8f0755402b720783308f019dd006697188dcbd26933073624ae0312e164ac5dcccceb833de4c0fb38d261f1516f9ca0627614c3f32a490f6fc0ba3337b99b5d7f7f1c1091e5c75a419a372b78bb339d7867b432d604d4780d5bcb799e8128944ae2c8a6f25ccba9931910e08d9ec632a8baeb99c6ba5326268a34c620619c8614f4e6db513b59f0e6c8bd278c4512e2d631dea25eda1d85335821d3e8ad96000357a225da492ee858687b7a3fd050959e701b15980550a83958e52279f18e1d42d1ec278ba4bcfc5f7b3054e2d463affa7eb7794a713e2a491d0943cd6c2710f1cd9eb12b3a0245a2c8c54bdad8cee5a648f3f95267325ddac366b496904f91e0d7270d064eaf7158db89104cdb816f701e753addc67865dd65560adf843621a224cf864efa9ce90882a5ff96cc7352813411359a7ec01695a4ea1239a20d846208844a8fa7938ef451a0436d110dda1c2322539016c9ae73273d1a53f99a242d7d3efbf69661e4831ff5af290162805b65714a962861c73915cb9a2ccb9b4681a3efa5f15a41946087b6ca386a3dd433dcaf7e78dfb419899ce1ec6910f732953a02f2055c9086ac6843b6e60bfb25854b7ee93e7fececf56e1ac740f2bfc73e296bb18a56d50cf5660beda71b3205e8db05cb07de1a4a633fe8a1404f08163cfb49326bd916d615666301ffc12fd9bf9820a571e37b4f1f483350ca3b83f7815d19f4dac781a73c166ad37599411e68f211439d0e033dc6c7055f627bf7c549a26fa28bd95f13a462fceebc35d0115f3dcfcf66f039146c7d480e2a0a829ed23f4f77b21a5ea86800ba03e89a49d7fe31d83c93ae6ee5ed2e8ff4b298a8da2d4f0767ec69235311e619a9eabfdb5fad6a9f7b558232249525f59f11063ca9f90fa4b7dafc36ec00ce8ee0ec11ff9e6ae9824bbd3a289d68ad09c68beb0af19f63efe523d8362c0cb9bb07cbf2d0d54b91549e57b722e79f99ccd47e1264e78a5001fa5e595e80dc69b82c19353effc1ab72d586709369afbdbaf623fa6804c48060ad51b15c799de565f64da4fda680627059dfec48524c72eadba654303f9dc1b204d4617bceaff9d2924d7b5ab579484e4e234b7d14991638da9c89b8bb5786fd3e9ec1142fc2b3b05d69eacef9a9d042664ebe344b648a82364a32aa543664763c407e5a5cbde7d6300d966e7e61d11215810bec1805b2f7a84b9eeaaa0d96e3be83166c4566acd890bf1821ee4909faa5800a975ee9f8706e2a4ffea42e7a8c25855ebd1f76dda827ce7cac97ac9823f6ef2aef635bcadf78197d3b887b12ba86e0fdccb52d9a4e3ffcc48fd76081b2d55aed11cb5d18ca909a91cc159384c7486bee2a6616c4a92ecd63adf25a2c5a51ac676fe9bdd7d67912ab8255409bc29c1402cf8cb88447ea90c3c5ab0ae78666aec308e0a5806a6c631e78f8c8439c7cb15bbf508cc4462b2dbfc11d986016e4bfbefe0c099153767dee827d4710e90eabf306104fb44cdeb929cda4942ca079032a70b03e0b879474597781f9b83192c013fce3344789cc6b4541e0e4444e37e96da8defff2ec97144b3ae54d94d33150dcd6f722b3cbb0f44c5f17808345001aff9d19c7d13c271be8a3b389247166e4c197ab3cf5a62a74ebda8a20299eb17aefc02b4133ea6f380632f10d88f2c3150b3ebd924ba64151c1347d275becff325f191e182b2158b7b08abfa30716572b544ef2b0b7916ad8e4e4fbc6a0584d6203b663e2cdd24336a4ce7eb78c90f4450ca9fb1b83c240465d55b3fa6548fa72a76a9469b2d1be29d6bbd61624be1b4df8b08d8eb88765fba7ac57f87b96f2caa6de50cd78fe9dfaeb9fd19881e24e46adc1e6e9a59a37f234686c87945b79ee73eb294040846971bf1a73127af9023a19f63d5b5ea35433e8c6741fc9467508144ba53d2e2522c9dfb0741d8917894bfb3ed42c3acd16f29c03d7c21786bf31a196b921ed96b333102e12824b5a933366c9a116556b802e9c5ffc4bdd3e37bfca945bfb28efa5d2c6d70f88046937df42b5d9a70cad47362afd77495f969b544fd8f64c1220e026ae6f640e1885908fcb1bbe8f8362f422f9893e95464a6e7885aeeb2edf848dc22406310c932a637810667e2122d1417fb2b9388a1ed960ebd85d6cf1497e7898bd9cf008a240648675f3196c149836927290ba1ad73a0e1443b453ec0c1f6c9cbf98092d18da05a8321081a4c7a4a46a4a7da91145290366d9e5cea469e86e88a4052bc7ea85ebadd13b6e6d6e3703aabe445b77a0f856c940bbf4d1a796e8829ba71f86b32f689ab3bfb1bd82f7d43947bb776a050057ae3110a4128efd041207de00323190a7018c95e9c23d180c8bd14f67bcf29106a7faa14358eaf4e540fe55f362c62d65eee39ddf231d8d1ebb3d661848ce847516fc2d3d230e55761bcb8cd78ef8ec529993260873674ebcfee08125495f4b2fc42ce04daf4e03f0ad6c2298caf13091234dc1da76a2ad59fe0240a6f0519d44e896110617830e678514fcffc6ff8bda6e146aff2a18baff6461223514b6afb192d55612478bdbeb46aa3642e415004520bffd890258b514beab29f41a81fd22279dde2753c761165349e99db249cfbad47a00be00b6970ee307409a3b0aa05a043220ae5f9c770c94beb7c7d954dc8c9045b414b3caa441a68d73d3b58d37b854eed97fdcf22e55840d35032c159bc07d747f7ff4ed0f3d1cc9007ef0aa63a573bf5f5d3eb8410b35ed15169d0e91fb1e2da320a7bfb067e13c2d8e4de6cbe849a639eabd5e8e409a47337ff0e5652e37ed41f43ea4c7cefddbf1a21329b82181391250e459a4a4acba5c9b35e48361c803b6230d42d258958e7595043f39ee7421f5e0d6a260c0ff1afba1937a4845b61438fdb80470ce66b2578d1757e17d569ddd5264389a5844ed82a37ab8c467cb95d5c9389a566b3898ca6354870c930dfe946ecf99dec9ceedb6f309d50d321a54b7fbe97756e9b4047e5ffd45b9340029e14b344d3e6fa188baa95fab1d0ff5f0b76e1d526f37e8ae59930beff092101803752e5b2966ab63f41f9037435e035c4ab589a5f44e7fc4076a8145c0dfce59858b17d25b41e4d3c09d371c23134b60b0ab9a31c2d82a664f283eeabf1e138d5a48cf42da7cad62f494f3214825611881cb8278809408293f70cce62c2df1ea4bf17e40fec46a502e1d94af433155737ff94742f9b3c395270f1d2691843fbab79d33e692629210fb9681545eed56b5c0c7c2197c9c30a765a03efbfb79b7da9fe47b50015e8fc6c6a0a542d85386f52266accc52c08450fdcee997cafb0b3c08ce6e208d2bafd09fc721d0b9a4d9a013bb64f04582eff30340299300103265a521a3faa0f2fc3ac99d41f833a186f4a55e0f5a315545ebda6f7b6c30957cefda6ca7092bc25c481d2161d890a2e686f97103a6a889a2033a0c8c01f4071b080a5a7c78094471f03f9bf331b46e5fbdf08c4fb3ab7b67fc09950917f54e01f22f336cfa86c1d65da16572eb2bec7454373c40256b12b40a0f4e2bd585cf3c81d4a95b43e78a066d503bbe8fd017cadc98c96ce21cc4945763cb3248c94c41a0483e598d0314c6349d0269cbf5dd8137a748cb96e08f677fc1a293e06b53c6cc34e0c114f7575d778b3871f22ab9221eb121b22fb49f9b139f0b73f1eb33649777a2ca9adb55f032156833798b38da9d08957a617884d2161778e03472cd3d27549ca0e495b83f133d57b97b9fcfbf9299f0ac22053e9683a4707f433aabaab4f051993ab2fbc4fcf0d31936cc918031d0713c2f4e814bc4ac3c5a208b36030d253e1feb509f9685bc4a4f8c54bbaf82cf0d5e8697eec33cda213cae3fff661499571fc7223dc1774b91c683e929d2e1f799f2eab929d8bf46444137df5c933d33abbda37590ca52bc8eeac6ca261d7ebd0e52637706be15767141e445eaa613d76500d950c3e3c8c2ab1c217724cbdbcede82bbdf3a637b20605a48dd9dfcb473e7f1c5a192e8409a34dd08ed51db518b9c8d01c549f121cd2e25d664801f5062b97349781ba954c81662117b250b8bc967aa0a4097c3ac3c2b12664a4436ad14829d4a53632453afa47d752041db08eedd7aeb44d984b6a32cfdb4923f2ca613500c307ded49c0f04882937d527b9d518660804756dd49b50a4ce1518a4ba74dd6f76a78ef90d09f53c36e54d08af1f7f444e9efa317687dfbcff0b343f233b5733ce87bf633f0434b6e2a99b7b600961dd607dfd765870be2314a953f8b5f2f20ca9fff0f283eab6bbb9212aa5e99e8904e03835cfd0eedd7e83bab9ef854d298110959c7f60b8d3cdf958a1096e05a9e5ec8fbdb13d9777b99723c132dd073651aa2678a7a5ffc69bac58e2208ccfc93b0840ff3e3bae4cc438569d4c6a58791e56c51471d75aa4800d7c0f57faac996cf2b2f9e44f639a4d46b05821d394e32a26c4f55a13af9af6f25b7f617983ecdedf48a920ac4050a8700fe7a5a055314022a8afaacd2dd13c52f6d6d5e0b41d623da36f55ed2e8fc00845468e1163b2aae35062149149308401544ef0e4a55c6481c5f2273bff06d3e1ca6fe848fe396739da00c44091c9af11f93e05af024256fbd8c767da2c27d2c504438c225d06c43771b24f17096218c36c8671d6e55a56805fa4bb78295444e8bdd8982a1e97c885d3a1f0f3d5d8218681874c186ae906ffef69d28dbfcaee39fb5d3b3628ccea7ebee2bb616fbf8c767c0b912d190223af75d046a1f8339d23e6e115bd8e69ab94395dc0fd5ece5140bd5c5ac3abc4a490518f929331495fe825f0aa50aa8e5a98a40f14209343e88cba38b3d816692397db1992eee377edd668084de11fe0cf969174e6d74b98bc448a30d5bbaa11d3a7cfe56c19d1c51c14adfbb2b9be4ea458dc0b0be8e7fa7eac3f3033ecb1203935fcf44ee827124d173aa204d69c361a426dff6c600a59707a26fa24df1f773514bb5fbc4501175a588c89dbdd15c6e846c9f07d32b373bfeef4be00a3b7f6ec5a6d63ed5b17cac3d1601eafc3c0c19971653b2c39fd4ad4ad848e3930e6df4799bd3d775c0240c610db5a5e6a954e0afe11d1422a41f8d10307270aa9f1200b69bba5a008f85c7844227478d29bf761a0fb25655b4008a20ed6f84e27c94b161d70cc410a3105700eb54172e6109ee3de51cdac5feb8ef04a866756004816e60c7d2220473e74db0265ec1790ba4a9af2ba31b6ae5c40ab020684058eab6ed0da6c0c065ec653c27727e369b7581b209b0adebb17f8e991ab773e451b69b6991f36a7ab6f07c69333395e81c003446f1c4c5c40417f0b0fbf306ed1043a27227f18a39fed2dc6ccf323e9d0aa25cbbbbbca15510ac86bec6ff3d8edf42211c582753c3e3dbeff38a1b7a5b7465691509724190f470d492472784f4e7b6797d4ce2d34c9bc9a43b18fee635c5e6a3e17cc31ad0f249339d875fd4a938778dc11bae6f87d84b0909f2cf88f336d68f37770f56e9a55397c322bd89320baf853ef5e15f171586b42afde621d9097d99f650bb1a33f15b2180d5fbbf43e65fe94f821049a6e89301cd9f801b150e32149b27b2ebdec466812ed9f643353fdb03edb798df03e4fe192b083cfe8f7ae48844b6e0132f8e2829b396311491504a9b3595169c065d48d066439e7a23039454c712b6225eaa8e4ee6525371e2f527f76ef9ef7f1edd674a91548f017a9ed896e366a978497b35be6cbba47e267fc3362bd1c9e9cb70417c2eb3dbec18725a70fbe6b3e330cdf07de8a594728098c0f79457107a6cea3472bd77b13757e95791e258d14b1d15453fabe635571395974c190e72071047916b0f0427c742122118a176836fc0aff1a154dfbc5a23a61303ea5963a347c81cc57724b442b6f99dbf435a4eb0dfb06046064904b0e0c9d43a126c9aae20028eb31e7eb47c21e6c65d13e93def9b0335b017c90aff7d87ce4b173692fdbd1013ce009ee86ac6fe28b2cde55c792894c8f87312726574e0c7fc5fb20a0d4165cd686523f3ae6c236584b9ea3c04c3fba3510bb101071739dc09dcb48646f51d4fbfd54fdeaecd76b412b00d73bafeaf05570220202ed85cfcfaa5cdcb54efa922efdebd943d3b645dd075591858b4065f32897660fce12c6b4a99846788928c43c2281f67d342b73e74fc984b572978d98c84caf0aae4f941db165a264cae71a349aba989564fb24957682aab2c4069ce517263295b3c04e9bf71869ea92567a6470d4b94738bef0423f3b0de93c36f4b4134fb0377d01fa250be66a5dd52fda56a75d00386863d07d005860dec1e739452aa959da14e030192fde2d5fc98436c5758f0b6105e9782f423bb65a41544a0e646ebb937a35c6debdede0fa8d512bc830d8b72c66dceb462825797b84b4698ca06fe3d60d15ea22d92f3f4d0a4dbcae034865f22c8797517633d583db18e5c0a7733b3ac8c1f2fe963466e8ea83abe55375e8890f945ad7890cce2fc226afd92b239c94936c5f8141e4d250147e9350a6ce85f26d8cddae391e87123d29744a58110d2bf1f38b40467b60b48903dc080acc736eb61bb8e8c7382c7fb44a32a1805cb444335c470af9879d1843e50638161c4c6b0eb0defaa3b27a9bb02b25e6b9701389571da0a46ca466ff0010c16d45290ac7b220936fa665d60603cc9c2a9cd76ee73bdebb619062da0ff8add48cbc37de77ba17e308706cd399b0408ee1bc225469b1c297dd2e55163970478579ea6585c951edac0ce584705cee892acca12cb47554acfb9f50e037c51f0251894f0df52786f965b2cf8c39a17fdf57ad13dfd9b9a27869bbc493ff0d0966a56fd5854f649e9878a98f8525070ad920b81451411c39c46a53a2c128d267e4c4bff0c31d8dcdcb66cb78ed484d6e546c651c6731bc6d02b30d493c25f45987d282e226a6d79315b31371a00188b63db46a6e8454d0bd88ee80467e13c871bba93f7d17b8b3660e8a07eade5f15d41efd85dd858e4ca3cce4a7659bb3a41372324ddc72cefe151ff92ab6c9e780a11070037ac9b3dfe022d2d66ac8d3368160fe77e0c25ba78e18974e15d511aa4131ef401e7aa59e00947365d25234845e2c3dd755762f02f053ac9e7840bc81263981fa3fcf760feecb26235b52b5b3ab7d77afed121f80ce024a618642b789f1ebda0e5845a1ad8c68b807089b69396bd7b2873940e1f0e3e38359f3c49a290ecefc81d0645771077c7b4de81b3dc7ad8ccc396686315e33cd73206a896c3d662b9a09b816a092e4e70f694444664ce9c601caf9a356d352301711665a9ddc8284e60ed2b5d85eb401391336a02204382a9766fe34d2281a80356057286f5f0e3acf9f1fa91461805f6a47c7cdd456a80790cb3b4cb0d7d2fe05bf3cd8b94e8419b8dbbf33fca08baf2a43ac1c0cfd889de8907734e7013b5398eba7b884745b1c2f14162c53397fe28d8760b2afd5b97d08e0766876eea75d7100da0eb1f959bed32e56498c6b3960edcff0c38d23d35ad2f8569e6907c8c8697e3d474326b32ee0dc0754b6f8502cc1f40e2d39699c4487684a98a41c1096bec3b0c39187c210875cadbd93c1aa93b6564d26654c0e271df05f3bf53654a9f6e279c206ecab7ce10cc2d43dea7785445d27db05aada81fdba0631879938a568e294e3c57e35949bb1e51782b5c1e43abeef4debb8ab4274dee81a1ca4cfa9551e68b70f62a4fd802419ebf9ebd37f7583471ffd0452f450fc308cb4551d4fdd12003fff5383cc4a9381501d59b1f745d9aaa3bb233406e6e3aef544798390abc095b680145dc9a26c5cbde129038c7a44a031dc2c5a0f1aa9077011659d3a9d3ddd4f84ba3cb7b2362ff18bb0dd71f67a8033c224e429a78cd4ed75aa37d066dd4f11308517540011a7580db022d27b7a6e0573ad43723803e07779e877ce2f15d78fcf29075fa502811a59e08d960f66e06cf33f9559adec17766d4cffdfa5fb27267c697039c586cd081d923fb0e5d57e1d67fec3aeca5e0c93bce47e976c8af00a2c44834be94ab8da27f15f1ce77295f9e4f70b0b857b94041c6954187d10817733202286132a43ca32d3b1ce6a4b8e2bb1bee99ba6eba0282d7a2970435c3852bd979753294ef84739fff520097c8a3e41e9be082d72c702f819352034006e08786a9635ed1b4e67c38dbdff3d843b5f0d8ac0f02728ed0f52950ce0e54391f218acbc430342bd3a47fe42ff943c22b9c8a9d926093bc6026969d92f968721b2a3b6784d2bcec62279cf092a6885f1e4bbd80c7f184ee0b9475c524f784343321716d3a419b189472df43afeaa9e17b53bc6757a20180599fd7ce3502467ebcee0a655536b8453a7d3ad28a327e2feef415d6861eba8b5935ae462f68602819a266a7ad132d450a90c20f4ce2341746341b467011d4559a74539860b1179488a0ebac9313c9b46b1a3251144aca64294567bd5a4a6886704203f64e03c6a910a28269abea8e61faf767bc954a9348fd0953719e4903341c5a5dd1e9885927184a18b926c2347ae17ee5c846efb60d47777e7bc9b225be9446e5e3a562d23e8c821edc30f1a5917aca5050555eb120fd544b9ef665c277054785c7c01ec884ab010a049d247f71daf8f42ebba0d26582ed6119888ec4214555071f2fc0e32ed08bd4ef29b495bc357d3e7416efbddf45b72c8eb8c98635d2649c4ef7e861a2d3937a98ecbc6e61473c41232762e9a540eb5af4b3a35aff1d7858c4fad29452e5d57cedb338c4dc22e333831420222bcc47609e2a0e05893059b3d06f6062efee6960a177b2074c1cccedffdb6108d48af4f34d901f3e54ef99090f8eb3c4e3df932f062468d84fb0d06d9a8bf68fdd5c998f58795e32e590ff1b5e2f6b47247a49f6f954a28ebe959a57e9293c7e939b3ff4dbc718031d07a48d6867d88e4d7afe47e3d1cab426636170a96de0f0d61482a3029bca44646ae766bd533fcd1235c9b5bddc528921728522dea61dd5f480e5655832657fa5afc29278a67b50fc035b5b36a6ca7e75ea23fc817b9272cc6cf0c52e375aaf17332c637bad2ff2751b9956d878a3bc081f932fc75945dd45d2be91111eacc6d8e1050c35d8faa72da4db46496df69ca3a6b111e8c1de8218d95a5c4b92e828580c7c25fbfd12f2c131fad195b9512c83da159e87c9a7a1e17919ff791dde2e912bd32af2e6016fc0f055208eedfeb1bba476f2a17a64cd8afd9614337d12a5152362fc85d9364bf425036132ae6c1f6bc819d00cf0b36e0093b47ab8587b4ce8c9dd5e722e92c604d90c432fc266a3bb0daf1e88513a40387525d4bc48bf5927c429795b2d0fb863c48e7d6a818f1ab6fbc2675e117bd2875baf09386351f586f79958f6b3360a8ce40a479ff91d97e409df58950df4084b2e3fc54bbd0dbb9b24ef6dc16913389040f1de0a5a3ce1c4738043ae76bc9ada3958f73795993df56e600a4d82f6d6f8c5c905297882f20a184a9ff69da493d25931515f16ae7f08deb2e913507a08f351d17f807ff079b30d3985680d96d2a8d608cc5be9a07aea20b7a5e329d608cf4124967f1e7e59e6d82e351b8bbd8c93efd4212302f29bda1e853ed564c5a3ce24c29768c2ca28a969d78f43eadac8bf4a7c503b5e62419d1e7499d564f9bc4369cf544eb4bbb1614ecc9bc4cbece1d7c52e1dffa41b42a68e1dfebd054dc8fe1e7ba02029dd7228c4d90150b05008da604c8ef2dbbc6613b8af98fcdeb5510d29d8d6c8962b1feb8e759116fe59c03e3d856f27a1b4eb76e674f9cc233e74c878ca5356e0821d5fc3cabd08dc54606feb82ceb19d2d0072ceba9300222862332ce2f83a2b5108c4ec381aa704e4958c80787f2c22f64a1f3d354057a1bae2a9a99fa9976f532d7638c8e362f2aadd842e6c984d1f38614d5a8eba67a51bcede1225d0981512105191df768e07400c7b58eaf68aec8a548907162418540d912aa9cadae2318ce8e1d47cd4dd9374cb7c5d17b7ce8ee421f925697fa60257b029fd64b90e33001e6a8c4a2577d91a148993c2be3b71343e06cd783641ab223eab034d60361209d2b1d33132937394b8e62c323302c2fd13d8bb90932f4d9d65ef69634a91585756aa5772eaadc09dd9ca01dd0044e51b7d9e8fdb478af9b6e435c359f4208c25b0e096dddd4632fd704f782c5509b0f74dac886ae6838ef58c9c69855f6dd5271cc0cd8dc431a4f7c8b904ae24455dd8c761e8f5684163ebc24d2c618f51f01e88a43854f7761dad0111fcd0a5e96ea724e73162aa42660901ff24ac0ed8b96617bd9525f5c39ccb8e04b36ad40ced7210b7d795eb5a703255bf4ec56927edd09fbe10ffe06bd257b72223c0ee1cb5352cf91f524f39ceabe015b87cb27ee1dc2b660219fef7e9a5185f90c1f824cbf18dac49915f8142c558706a14fc415be579cb2dc03948c5fe20e9e8793821dff43ba23c63877302d987e16094e316c22d7c8365d9a0434bd89352d970ba637833b3474e6197256f12ca2f28e601114ed0db92c2e6e418acf90906cfe3cc0879036641e598434b8dc0933302667aeefd6e7130d760dc0ebd5e6c0d6347827beb2238d5053ca38e7dd7e885a6b871646831cb57875901e5cd2f7e28e777330a4ce40125117eb47877aae17efabd907bba09ad2b71a9f2c399ad51330f5df98ac08d0f3941c2d4e908d62d845a6a85fc74738d280f5cf2bd3583cddcf0893510f074a2daaba84a229cc88ea74e297fecbade1627fea965df1e8a2dadd38f0aad6c256f3ed695bc5a86555895c42db51943c3a72e119457cfc9ae825c8b4fdd49e1eb1e7f2308b6ec2045f6d228bddfb73ee6d2171d1df5ff554e2e795692db887574cce7a76d2a0bafe2654df913bb0cc7b6ddfea533116f46186b5e7b9873ba19c78a0e2b34eb0a121fb737abfb892ca4c44486d897a2f66139c27a1d19d8e1acd0dcbe627c303b8efd9239fe8a90c67e3d35e9786d422d1d5794e6046480c92ed43ab4b030184e22a6afed16d39701cf0c12deb1b4aed62fad954a52f3a72770939224ed3e205133a75bb0fa6c6c681ab3f348ed974ae437714b1659c0cdd2f2bac41a085cae358fa6f9bd46f461e6522921be20589a058c7a93061a1c3406ec2025a18b078ae907007da13c7dcad3682d9cd437a4cac89f0e1dfc17a163b69ab3fdbb487c5fff2896f2f27dfd8012622596488653274b9b9ceb218111a5f563870934b9da3fe7c967f6651170613a885ef6ed6bcea4790825d663e6998b686751f930e0201cb77e5339288b36cdd57fced77294eb56e1b7040935fbfd66ca7dbff3a3b372508329d0029072da2bae5aa406f27cabf80f3fdc84a617aace81d38a0e8258cd16472e48a91a3fdb6228f7e7875529076884b2579acd76811e4591d6d8cebb7e0e5d0b331ebb86a660b13e1e108933fa282f7dac737a6b86f5ce4468d17652a2bd8758ff61783731973a15698c7c9e0bde8277bd8b3e0527b6a2fb8beef909730b8a6e5d085cf6e97bc60be299f616dcd414a0f731c3df0d09e1410216e6b379c34f3981bcb0fde5050528ff0a378cab79cd1724097016d4582c772aa821767e16a6f67fbb90143b330053a967694a5bdea8439e8b6196d6539d2bb8f4050aab1884e30458c4797285d8404c68e7002d3907369781af48be6cc776a6bd49d47a42e3193677f0dbe18291e38f7e4b5e62658dc1518d03cfff5ccd7ac8bebb1fedee2721c91215b98031eeba9b4a532cec6195d78ca17fccb49c85854a21cd04edde19f3f4b65bbf81a05d93bc2492158bcd2e14a484f4f4b5edae81b309ecc709b66c77250636099161f7850595f37e1f70af994854685ccad134aa7a0aaeb8697dd8b71237cc4e1908884b0ddd0d1f69b4d39d16ffecc0150f84b111abcadb5820c24e2493bb665baead34396e443d22286f4ab6c8c6c8ac32efce57ebe5e9f3ef73d2d7f624b0747104591f6ebac235fdacd3a8c8e26e5b0b2364c6b92a6d371ee63e37540b40aaf262cf69391587ca8ae0e55c6b9d17372bab2c4c7bb5072a4617a04a3102ec6fd526014dde0e8301c9e5cca7efd8078b6a8629b888ce2cf0e1f47ed6272c8cab44916a139dd8d28fd711f74ae863380b63cbbac9aef4d656d1d3d2a664890ea66d335a796f4db2d077b3b5bfbfafbffef55c9977adb704cad978791c299635f5ff6a81937ef4dc8690235d1d2ed74b40c3aea2c3b96206bb22b177b14c391c6ab1ee67c3a8d3fb47ab652690c5f46688b599a87dfc76e631b87a84aeb702d9b9bc6a14cd00120b787658748169c97f727540b7ab11f676a40bbda05ae6b9da265b2d7c794d6ae1cd43765e44d0b2f012f30383ea8b7ccd346f91bd836d3cd8ea52e38b518a6d43958925a3d306f27ec1e287f0fc6c2c38289c1bb35d70ac6d826564dbe564952c46629963f0ea410cf138c7e8edae67f5be2ef2095b2aefc044dc8f5ec7bf2638940c424bbbb1bd748ab238eb9bbe5a3894261759d1667bc7e20539a5cd32e453cc68cd04b46317d3454e031042fb56445567803af109780783b2237c87869026327eb71ee5591ea5eace426c963cfda8ed3a3018d8862aee2fb71001bb3a41d411f76ac1e4c5958a343c5c0a624738e93dcadd534868b515386dda7baa02810a66a2f1d83c2a5b98576062e80a5b071711a92d8be2cc2614f132a18e6b44427e45ef3494bfe0dc79c44c8aae64a7182e4d4a7afa90e4d6ad06e7078ffe99a145c677ce5b4792e0dad9d1af0f41d0333749842c40347516852170801afc5278b7a87268a263bf99d3a507ea05a56f747ecb15a40287592da13938a8379ff1bebae63fae307e3c1051b7c92f5d3c915466711db03a9cffa8665aaab496aaabbfaf37e0ccce16b0d6b344360c3ea13587a1299f972452e6fadaf6b9efc227ed185db47b836dcd16111f9a3263aa51d1a2ddf803b2199225674d2e59445747d48f8a09dac330153fcb31d7e3451eb9ce9b2878978fd43be642bf8a03db87b9b3b23313ece2fd944d9f248134d7c688070f9b5b09b2e128e682a5d335c692ae914b7c4d42596f6c39ac8553554001b54e5c11d7eed43ecbf799dd88f0bee94ea8a2d043921350ce4fdc0c0738b513d3e890fde53fc66a887fa7ef7a38f126e3e890db09385300a4ad7b427b1689523e0b229c52d7ce6718f7f5f7be98d81e1471623cd5b01e6637c71ef2cce9522360677715d8f6aa72de6cc9d1ed9b2ca1a31d8926021d424b33e6d8a368ef2190b29a4556e2c1127067007e5d3754ddbabef31e5528c6df4aa4a4c295152dc25c99bab8a83dbfd252f86af6fb53d71f8b92b538fc689390b00671d0ef4aea10d0f9d06e587f5d74fc5c725a99c7bd8afd1c5308a2c99414ebd712f92bde2f61a38d5742eb0f6c765af67625e13aa542fb4a76cf09437a9374941cacec3e4ef0c949c1e4b8869e15c25ce98475e8ad36e555f6a19a261c6d52857d98bb01586a02d6f248eb750ca5ccc9ce06443dfd6c69fa64f8eb9ff4998e42f4c26a70a57e64fec3dd31ddb37636e3419851d43c5f059381126263a09b4c5a94ec113450845c3c8098b138f253826eeed397e71967c758c529040e61fd5b1e4549e2f1b92ec7f06bcffb880d8663eb98a0cd28c33a210dd973f6559f646a3443864ae101521fad37e415c0ad6e3011941f35b67179dd171df541ee633137279d2025f791b7eff58dbedbb65eb0dfb14bebc888dc478ab42323d4ebdbae6ae1b70beb3e9302fb3c8a16472d10e75193bca5624ac0fcea223257e08cd9d3f2acc8a7246077a4cb6d3f8f655f97f0e965f7d6b4fb5fdd42eec4100d7f5593070c5d08931b70e19d4b30a107682bc5b51da31b3233fde98a4708081724e7bc3883d48acbc0f5e4d50bd6547201e9dfa99978a33b0765bae41e6110d4df4d6d2736e2bc3f3a40e6730c1dcd9d334f37b1b2c6ff30e7626adc20e4cabf2deb0ecd0a0e93eb52d25c3842e6a1ca26c4c59382ed413bc047964bfcde287c1746ea71e133ef2346ed446afe860a78a00ea2f5f0e369edcb6867ef5d4cbeefa88846b55102773f7f197471f846d804a8d9a7f05ab12414509fb003df62e4eb29001a49477eba13ac9ffdb61be140856f37b00a78f6da4e68c81a75bc05ac84389f4a43cb7eaa80ea11a77d76087997e41b5a8a75620f4cab45617c8c50a0845a7b7e26412e5f84e6c06ca4cae11e413cdd626c9f3c0dc31e7a9eb4501359a63f4f7c04618e2e9133441685ad84741f64b98e7bd4dde6847652a8fe324fdd7db9f91b5c42f1122009f9da60d87d6a74824f214d7133c43e0cb87cc3f3bbd53b61fbc694dd8061b9cf7c160d474d7ada9525e623543f81c7561fbe6790428fb4fefc883984b5d0ed98310dd206e950a9d420c9fb19979c12e9fb3f02f183cd540744b055f2e0cef9b1c0c0ad23394955a30fff3fbbe575222a46a3a77c506deef643fe94c5a6280dfe94d46cf69ab653d820beed3c66dc9308fc02dd4b2fd786d70a6809597cff70aa5b824b325dff4d8b698625416bed9ce59fb00351dc419108d066b83e3010061c899e10754687addc60fa6ce0db4299d4b300d036c5c43e396ed851d26149d5aad4acd4c80d6acaf96347235baf717d1543271de58d3ace94e46fb2aa02b2acddb904e77ae0054721321eeae075d8cb615a24b09489f30553d2b51302db9f025df60583e7df556292703b05e2cfbde43c8bc0bb1a4638413aff980b87083e99c10e5d475e9f1c4b212580f21cf1becbf96a99379d019cbb23d68ebd6a78639cb8c857a10fa8debda13e9abce0258ebc91113e5c312d1f043ee443dfea6fb933b4b0499e07f7ac77780c3dcaca4a0129b89c5bd6fdf121c29c5cdc72080432c3849500d5967dbf476bc1e92b9d5170fad1adf9825aa72636a093c45896a4ce53c40949592e9ddfc936f647aaa8bf633097e9c6c60f5f6420e284111b4d8db6def2e582eb9fb2a21388280e17ac2f93362f7875d2475a29f7a04c71941b549d747fc245d6e78774deb7367aa2f8cd338c89892766fc8f0d9715afab547e60797bbf80152c22dd71f80be5593721db96968dccebe5ab29f5d6ba92b2e4416c8a7c02c3265711e91206535ca31f793018b97df649d1e806e3a9517e424cb609b03c8671332da8d2a5de65a89d732cb35c0c9340dfefe63a9bb85724137b041910190c355ff95d37990d79399915cbe5e04e68e90ac5818a159db9d4977708550de235ff2b2eafa896423063383cb555d7e843afaa1c58f2bf29c5045075cdc98dae9d827eb111286dba14df47cdf629571db25ae7c2801f9166c3cfa7348b539161d1b5b2f4cfb2cbea6647270bc59bdac2293211b90cf6ea2d2d73684ece2718dcf753c04ab2b1cbfa429dc156007a0d1da7838c79030b4d9320d2b84c474d5915eb5b6f2e1a864bd2b6c41508d2a5ebb14120ce1cc62bd77d98f2adb396214993ff909c6a370519cab7064f9fbc942f34c3dd6771ced39b20eb7d6b5b4ed36ffd86f98d564bf8eb46dd8c6dd03acf10dd2e1425598fb6c23a3f42d4bd4ae50fc458625798829b30c964e97e2b587452cff92137fdd33a7b49fbb2ca1979b09c1551da4e1fca1ea23b4b90666384b5a15d6ee671ab5bfbbe15ad63018e19df809cc33391482827b58a365936754058444e5548986e9847e4478b915b8d8ef134bf5486965f8f4dd2ce810017322b35177689b0b8b366a100eb7d98dd4ed5bba55c89acd6ecd29e6093fda6c0ed60bcdc448b8a998e4c3f1b0a8e7b2d1bd5e4ea3ea2c57d846519a004dd4e8353186cdb1c83c9356f4f8c2fd1b760a399cd684dbe05b53007c935540ed861dfa3e45dcc8f6261495ab7a6ffdb5d12ea7e4e169e4e8fa2e18627593eb5c12baedc0bb719dbfdccedf626e1b69c077e2f3f4d12dcf09978b4415f616835b15bda8ded396d3d0886460348425c04a836e5a51f532bc37f65f364e8203faf6387a85c8db38effd6913d02ec0415d764f110755c261b29965d138a5fd87c6daba68f77f28c924ab0f24f34e9173756ba092d6ee5a93c466397c81f2cc3e30d9994a17c333c582534326a2d2c9c2b2ae2fb9866a85109c5e60e2b31ae8ec8168a918394b4d358331fddd4996e6a21b697eb9949f76419245501eb3c9bd96cc36fc2f6c35371c50e40063104c48c8f3faf1deb29b180aa039273950eb6b8291170aff0b5fdccf557f9c284aeb5c18793787ddec4d812762d0babe8cbd9356a577ac343806bad26d413a857d0f6fbd81391180d199fe0d75053b538a864cb75f487d0d72498728835f558dfcbd2e001569ca197f25a8044ad00e0e3ef6aec0ad9088872d1d28e9d6689f13b2c574cf3bd55edf68e60de0f25e13fd75dbf7dc9fc6a0510203105e9724d98c47ee3cda2be90775cba2eba83728a2823edc9556d05c342cbd98af1ec66f4f8635939266daa08dda208f1284679aab79f985ac1d8aa5b9f56fe16ff78c10fbd04205fcd483d5461ce0be90e10ee404ca1ba2103efa15861041238f24850fe180e5ba86cc84aec681daaeaa9b3ce1809bdfe3bce3e18a92f5ff4031ff391253ef0b0fe63ed39f8fea8f5b90873646d559d174ccc7c0a3d9652c199f026f722e9c0d41644ca387e519527c38fa6caa35be6aaf018ceffcf86e37e66623b9d33d5e7fb9456f551a832d2a05776b333678c2a2501c4e2f3a2d478990f860d5c8a44dbac4414cbcd0a976b12554852664319ef67e055e0792eb6b370d8032f41ad7becf86fab80893b1b6a2fde65252d0e79c08360421c622d4ad876fe9264d23c432473127f062e957d438a96809fb20de956f02678a66c5f0fc6a4b58174b1b8682f761ca6ba70b41ddb7fa5213a03cfe7caf43cd9a3d20a6980442531457bee3d5b8b3b6bebc7188212529e23fc2f0db7d9317cb878b09221475403984bfcc017a204a5cd293d55d6e9d34d3750cbdede3980d35ec39a6fe33e0433c04914dfcec71b30fd6b476968304958602bba9ca9ff94f48926e876bfa91019465de3d23f8628a5e4ffd62c29c55b847583939aa7111ed7e8e379a4a9affa2edc5d1a8bccc9bf879a4dfd353067488aa31508d62bad48a2a0460d8af9b661bcc78b8821dafdcdc9f069da413616a540e87c0c38cbfae3f9cadd5f1eec4cff83618e402b5169fec5e778baf51b03630a9998bfdfe4b29160f286b0e5219e07a098c2ee4f209b019837e08cc05f10ee7bdd42473413d2b65c9a4867541c7ff0bf6dd93a3fb7e68469c9491fc0a9e28b472f571d28bd5d1e52b5fceddbc604e17777c64e5c8cf0ec923d76c6d044815ca3e008872d8838808e2ccb0349ebac4b585efedca0974a257f65df5e599d869e85928930d9e0065edb5c7415fabb1675126005404ad520171ac82acff8c6e01846beff26157323e803966b64fb494be904cf4d8ddcc8f59831206c76f5f621cc6d2fcdaa13b5a3dc219ec106460f9621299066b5e8f56ee998dc37303207614dc0852594e65d6c92302ddf093668eddc819d790bcc6531070f9f85d877f7a96cd4b4f443bde655a2596f0f42cb15ebecd7841ace5024ce77e0bf62791567fe08e482155b500f61e0aa86da906fb95479c9e7648bb0c6be4ffdc7748c341fe633d50cd8b1ef5bfb96690530accc5d34fcedfa6a7afadd718dc4f54bfac1888f06e141d849fc925336037561b0b2ec80781708f4234984671916bd85c1773080caa6652434553ebee9afd3ec0630b856ad87ef8af483d75dfd5444bf6f664ad557ec90b3a5632f2ac53710176cc2f3bf47469fdc91a2788da4e3e557d6582b14cbc62d2cb4fff8a47cef90561df8370d55a39e660d890cf6364cb003a58fce8af4687bfa745c6fefd7edac8d7aefffbd775787754e9dbcba61b7e62dd49d5140235f0fb77887312254af3015a4e22bf9a0a186734d27b58e1e8c7e2b09a276752d9250dddccc1b62a5d9dd96df5380955038de86fa66a8314c13a43d5eb423b5d805fad16d7ed2a40a95e8b7cd7e26b29de26380e90b4afc29c234643823e27403e99c3993313c85a5fd7993ba6928df8913001a1d64d57c085f43001505624532381a9f0085052848832d059351f8d560f033858b288623c18b974679b8f49262e52b6a64e41c306baa95c34832e8c126c18c4936471dffc6612a826d260ff64432db258f8d33c33d85b54aafdcb11857e83273ef6b6089e2b3ab262effcc1341ac0b17cd4f429e016886961b62f00276255d1126529883ea39fcb2fcb36a0ee503ec226c1b043a25d074ad509da4543614dd04541b11e30b63b5633aa4cb85a2cb8805a87c78ffcd31e0fa8247b2153690911ee7fe9fcd72cba0b536cff627fb0339d8768a618cc4639f797c57619a93e3f18dbfa914a6665a3bb85b5f93d17e3d30e8efa6c707f5ad79ae2b60de2bdd6c869f9d8ccb282bde3930a28e723e3d03efdc46ac7486f512ab722743b9401db76ccb182a20983893018a77b7044a8d32cf2e95c6787f4449658ef1d2674505d5d613fa2df6f89c642d9c7fe0e0bf4bdf7bedeb47a65031fd4d2302f0bef4782e8ab313b92f5f98db0d3adcb440bcf36573d6673914d3a060fbe6e5427b40fcd5611cf0d5374ac6a42296e4a5819c32578bbb41499fff1fc27b2df9f4bfc9c5e9c945f27b7a116649a37476e6a77fd9fa693915fb65a3f6e0cec389b08ee9777a434bc1dfea60943bfb2d4b0bd6564e420abed7ac49242cf8689a69c04f49c2efe55a19117ac5879c4881fa5eda85de243ddf39187255b006b33f3c5e4f0b6ee9d8f35b0fa5492234de369563a013958a26016d75cc5b1aaf8b2a091e3e839629db74798fe7216ae9c292018fbb6a761cbd4c874985a2af25c1d4dcecb07e102bbefa0da74369bbab3097830ae0a9438b4df6ff3791dfa40799a582479d1d42a5e88ecb71bcedc42416171b216a842720f5d3fde32728cba24baca125e13aed64d2a58b8fdb0ca2bb9698a113d6b2eeb42a44720b43151b29852ac694bd64ae8f8aeda10a2fa0a3698807d23c48f3ad8333643a8b22f8cf87e7ee21265465329e316c4acfc9f999a1f537a4ca059af797ac96c0e8c81e051e867f0d212f5b64177dee4c5ebcf0b1129d38a6d5f7b1020e045ad8527d28075b691494e3b961165d38c62fcea3fd366072753d4a2cad65b6fe6ecec84b6acb600f6294f9c4c9e6a8bfd8937f1b67ccff1baaf9ea57a54627344fe73b82633f23e833403a03e263ae83fd9322447eef4f72a463ae4e2c23be8ff3c50c35426221d7d604ab8e4fb0358bac41cc44ebd4a006d7f1dd8aac9197d0ea743bbb775ac1b75326fe61f62968f1fc98e4c409f3599abb0ca7aa599e0c0bc00bfe2e5df98cd2799742dc71d57660f385a67155cb9ea4054d42f0b0a5d727b12a37efe75e086bb9d8c1441d90b89d20d5c47ce6c4d5cfa45d70c289ce9c5fb5c672206eafa036cee71741ac4a5cfb8f29881bfe293c79972bcf2ab68534c3d3ffa4287b0d3ddb398a485e53ce51e505526306a39a42368d4d7557339dc0b8c9fbe333b44950768e7cf48d80ce19588e978cfa4d86c54b59cd541aeaf0467401ffbab0ccfae51a8950d8a02349c4665bdfa339a67e553435bd216affb24683d83d82d18aef0949fc036ed1867782b4f3b9ce084e75ab1ed342dce0b4c17f4536205c0edc2c157e8a9ae13bb7d1d7fcb0b9fc30bf36156d4002a93cd9a8ed6d72f115a2f9e9dfa33044885e7de6a38c8eaa377f37b41e38981256dec830ca838b362f0d9d3c949ba0dab09fe509f5c64785fc44b5eefcc885e6c54dfe9c4563ad24b8904467e1b96bbff3c5581cd29ec6379bd50395471d97d27cb432fc84bd07f28cb487fb9b47fd641f7d44ecef1fe29c840cd8a65e695f1bb9600e2c6193f4fab0c37b426b01650c7ed853f494e66c3d3fc6fcae719269b5d043697253bd6ef5314d289d0097f18178c83778c4a593b474638f667eb4fa200b741bcef668d8ac08a50bcf792e9be288a6ff4128a28bd12a494cd3c49da40bd1ad5d6187931100e262af64fbe03db407af0182163591d0c461eb321f50a8a1d6ccb997d390de1b98de4ca84437a4da9c8253937f4d3645da988c45648328b9d8528be924899e2b69ba81d6fab6613305a4d2a685ec00904a6034c97a202c41a0e70f5fc0f951faa5581485ad507de26cfcbc96fea659f9342c58380dc7709e42d40735c12ff13da860c86d22efeb99d557d9642626cacba6f77a1ddd4b0de00087e6443acf12875d3c430f4beeda65dfe52f2557aa8b07e2bed98d8d31e4f5ba139312bd50e20e999b00d662a0cac7d1ba5257b18265891801289d5c34e652a7adb8a19f7e66ddabf256bf18c7782af24e36f03d94dbbe58465cdcabc324a5e230a7749cf9170278f68ba8531d9bf80bfd999639dbac4132968c356881b48d769ac356b2b5c7d85023f9bb14ce69e08d8800d4e6e4a1f449ffdab659c55f82a98d88d47b79d5947f3840ae37fcf90dc793383c7b4fab7bae10721e24182943c5cf16839060b68c30b98fbd543a68669e2575cd795450293f5f913949b99d8d04ca5579e45acbffe26cf7a4d412d30d952af60a133b6dbbe007aa68ce1cf8f7030ca49c84ac875032dcccf55a7f25c204f44094f1e341818c228bda3e66121ee5c817a3806ce7ecf4d91f890773b1d451b6e83a303cd0c47f549e50a4f3288a4e544fed4d06365f3af63efbb3539acc3708e7d814cd8a91f240347eb8d16c0396a9d19970ba2441a70a4c633e760ffc6e668469912e5332c610e1540142662caee6fe73c21b310b0bb0e0b826a95692a8b1d0267b8fc8008df1e8cfc23e279be3ce90a041915bfad0c7c29940ada6f445acabaddd30e65f2694bc10b1ef4dd51045ab29ebd82d861af2a861ffb8d1673f805d6acee7f1fbeaaf85de1532d7d9b3d67cccc77f1121e7180fc6eef0bf546538bfcfaa9dbaa90e7ac3daac1dc1223b306faafb6af63caae51a683ef3b9b18646f31abad1267f6e33f7fcd1737d48f3a4e7bf5b5149ce1548900ee352835a0efcada5c11313ff7a9a6027be3234b139a99742c63e3ac7acf3d579b411456158140e09398a80724f75cae67506d6f5fb595d493d135345e6de09041b2948a257290593716f13c9500d67afbed3fee616f1b35f8d025e6b47c5257f54078024233c50d03290e19adfb0bc28e72fa85bdb55b4e61b139c21dfa53303140ae510aa2f002d04ce9025c22da25a6fe4ab33da89a56553a14b36fc43929e44089f4a1c4d92ed999fc55eec5b4bcda57316381c391d2c06dd7e98cba5491a03cafff18cc09802a28ce175359265ee1e8c49c31532b0e7094a42a7c9966f44778b4d5cf31af3787bb4115f5b2f444d8498ee5af9e6320f413f13cf96269c72117d5d4af6cd96883948ad416e151d80992fb9a587ed89454168a6efcc286e2ed544fe67dcb3e783b10de4e35e2755c47ca2f9661e7f0a16c846fe43e8b31f3ec678325e9486a02939ddbc6df4cfdcab1972ff6993fa6aac39aad1c633a22a7b41283b0206694b1b2926641ddf7a9c697cd64b875b62436a773e424dee14fd280752726bc55d4a7a2d69cac6f61af18e343a942d99dc1d7c76fce2d18aecbdbd006cf5f9cfcd6fedecbc9238f5a3384c3ebb9418f13217d020b40eaa0352214f58de1f9832032791944951e0258e6e86f9abb21f6f520f10358b8e9fb5304d154c85d5c057b02c9465cd19489558d8538e136f4658cf69810c7790281db5cdb544329d144368d782771a5e474404c2b1be2ca978825e5eec67c4ed371dd6994819298b872f23f08280951c01c4954a858de5b09cad5bd9eca692c15f2cc8274918a3fb71dcbe1a029bb85e0969954039536129e56442ec4e17544f40e28791e5604c1d0e5578ce98ae91074cf5920e8bd076bb8c2235ce585c52397de453c95d54c54b2c2575de235841f28e319bb758902e9865cb7d6c24482807b31f2ede646b233a28bd3f9256eb7f60a970c7e3817149cbe61a018b6ae345fc268e5f50b6876eed683e3f4e476e13b9ca5faefc2d82ba5da143d07461ca6d3e2b8bf17551ee2387f50848ccb83d5b966ad8ee59096e264847db9e3c9177384c71d735ef0c73b3dfe8290de4215fc924bca1a77d4ba16835ae243e8dbf8f90c3c4ff3b2b740202f1cd39a6e3b56362d1be30ff6098ea101578ed2d0a55c43778e8cc5dfc20a3e8102f511078ef254048467fa0d00ed6df7d4b800740de6b3fda8fc0b53c033cb3fbe178a925609912c3930335d915344ce0c59f0f258a828facae68a28248e9a4aeda62b850c65a97a75aabf043bb3e02899119ac29c85e650b997f47e5d56d6d128f47e3d5abac27764a7e0723b59860734944e6d86708cfc2ad640cd1855bb975357bb6320f132a087835326e58f1f7eb14a6ed08dcca30f584761bec9be0a4d767ad233ed9ce9e1e40a62faf167110032515939b834dc211dd3ce40a21c53f02f6ccfd70211095407d583fbe543224cf7ddecdce19370e1bc01d993419070e75f0387bbe82d4b4e92a01a68eacf2617fb5e2351db023753d9b8eb3b7c5b0891f363fbcfc020db608d0e32e0becd82c3e93238baed37eabb5424f6b4e65f89eeb0ee83ddd4ed5e7cd4a4ea50263a499e3b7f02f58c0a536071c42425324752d189db564754c3caac9d77b5a2452daf577babca92dc38ff382e8e179052f398b632f4df956465cf45a6e2da40c8d8e3d8662a722a12e1d921dea2a9add17ed0006bef7a7383147ce6dcc2e5b61a134a43eeaf4e833821715965c0aedf1039d497c1c86802ece99d689cbd05dfe147f116972fdeeb6a7df55ec7c0c046b4fc41622aa6c7672ab5ebe30d91a0b328db29e27129b491b68c17d27990e30089c488d9c8bd9e3bc4548cc8cac16c909f1fa1a892cc0b4d279e58d6ce22f09b47b81c514b71f022dcc2f596f932281aed84088ebf5aa0dc0cae5d864fb18e928291e4c425d1073c14834529ff0a720d1dbb59e2e531ed6b7e71986f47cf771a500b7a24579c3bb602fccba77c5ec182791ce47b67c23b590a3cb1341eb3704b1aa1cbc5291dfc271959926d660f8ddf650a9223d0f1bf4c35fa6ce07d51d47f02a03822bf2a7530494d73f598dac68b40cf10077744f019e98dd5b4417d7a809457b506aeff79f8996c544efd845b7adcd236983cdcdeffe96b932277f8cd399e9c8799cf2d8ba1c988b85b4cf6e4bc7acc41830c905f4aa69731d5c443f3e389d89bf2c63901d5b91b0e92144f3a84855697b0b1094e92a888bbea015f89e8b0bcadd1db1d922cd9862698bfee9d8b864c3344730a6cd3cc11006abb1e9706e389efe940c808bde777e93ee6ddce9ca39be9a6a339a42d4569fbf5f46ae7223010c423494b2fc66b67a0811340d6a5a048a39d547d70eab693a045910b4269adf9b4045043d7cc25e18feeea67ff27728f54d1128609c611684c4bcf44256c321b07fb33046f879fc01be79a1cb998546c54bf6294b792fe64242630f428af506d7fbd6d354c07b974f27a63d1a7d637ad9599b1f1fd7237595974d6f09946175a8a70e7c2208e4ad3c29cb51e13faca3bfd119740f0df66faf2f6c7b1905973964e52aa23369556e3b4b50cb148301c90ec21f2e3514664b7d48b5b986b51802720fa832435de1b4f626ac408e7468e8486f52c22d0c2d5716768bf2453f018918f2b8134fc6808cc2e8b2545c3db6c63ece80dffbb57d0e772274ea4982b79d3d4e21727a671c3771d234c584910c90f009a69ea1f614c897061e2c9121b300c557e0de096925fe80baec3e8523665bae01bcc0ae5ada2796b2b79cd70451433037ab8774d8bd4d1d8abe50882f33e3cbc65de4e88e10f4510b96d49cd5b71d76893714e52471dc7bbf4518d5f0050593203add46e5153090c62a769584cceca3e055f28ba17ce1b4ebb38179274fda400a5697b3d4b49e71f14afd6ac4364ac03edc1ad407a89b5333ff34909b2a7cc1311f307076040ecc6bc5c0e28b75f0919d6099e2f2adaa82d1608acc933bc48e794e2f7808d90ec06749202296035d626ac360c86334fde5932320285e999c8e789a09fe6ec4f9b6ed9c18cbe28be28f048a36753d5555ea5ac2eba6cbcbb00df93957f1ada663cf5923b819342e1dc74209bfc9c9b6a5909c56e027b1d791fb7b3668836fce63a0dab32d0092d3fa07ea5ff41d3234c981e3568421695b9f5db08e20ca82a94d26f4bf6b7cc892c92e3b4192300a1657854ca510fb4fbb4786fe2846f45905c09dffc99907e524fe95648f40bbc4da954c09eef26b445e71fe0196aafd89df2b0ca52bce0cbb69a4890ac686d6378fcee0df55cdec2ffa1e3707a3da744dc63437682ccac962fcabb1c634d2ef21b782eaa9784dce77f0a00992bc3daf03c98cd61a68b2d9b5d7824ca5091953a2c54675d3b774fb8abae5c4da20cfbbadf82dde6285fdeed30ab00fa62be430bd2543cadfd82bc65a66bb0649e2da7f717813d93e3c13efa07d60c840e5c998dad29d79e4f33b5edb2214e8fdb968ac1a1ed46e7cac55b741c51762d95ea31747814eb10ab206bc87dc9f1349aa3ea694a2c0583f048f50a408f7cd4fe90688b2561f8e0fdc97d9dae94c363471a50d7362673d8076fd3af6f44b617318d12ab132dc29c538ea0dd5c781d9f8ca584e6dae9f238b8177ca2ac259a68aadc64c3a87f2c791bb793f7fe6211f84844d83dbab7e707e85623f79478e46cb5199952bc103a8ee027d6c5f85af07fb5d37053dc5d7adcdaa7566dc1f95a9e320d99bc3402cb4e82660b804a01ce333bdf82db3aa2aa9c118830e94defa2073039d8027e20077a2d75ac77dea1c4d3730663684e220d5f47bc190a35352fc91d9eb24de158583d0cb3bb2e1dd0a9ec6bf07979be9fc39d6dc4ffd50e00dae4375300255899bbf3d7fc345c9d4b109d111c2ff05e49cab2bc6ed3aa7db7e3d076542ce5d5fb3cf47412892d1378fbd005c91cd74ddf26d8bbb218ef706ad9e4e8ecddaa8f8b3385e939d081aba5c302e26c0e0bf9914ebe1ff47895212f82cfc97228f4f4c69ab89d7f885cba5f57ce9dc43d960f544a0edab435c1031b571ea24ba724fd0f252e04ecdb28b38e912f333290af4d598f1c214ccd74cbe3a9465def4fd07f2f6ed1368991209104c3fc4583ae8bfaa19264882903d65f10b4dac4ee2cdbb1e9df0e15ddff475a69a1005f54853257d8768bb35ac237fa216fa758b18e7145c90eba225433c04dfbee01601c6dc32911c4b2ac02447001f357e68b0e66a9e708cfabead07996fa28a60896691191396f4f6f9a86c26130e66eaa3296c81499c370b346c8565748b3958a4722b209b66f9e15f6424a228656c8b34ce5e42e9e04a63143d74cc54a3cb70e318b28fbf5977a0e99993af726cc9daa2ca8915b6da4aeb05912242542e4b42750554df1a23bb29f5ba945d67d342fe655b4f5bcd285369c0ebf3a23c884f93ba7f23416a435bb138e9ec0944d566f56957b72cac65b60592b2c07adb104f2ecb1c731611090f09392d4aaa876dc050908ad4a9baaf7429de4af111802892f1f24126cf95203559d3fc6179739cad522ce08a9cbdc1ca0499886ede823cd9c62d779f1020b56be0b711ea2d8fbb09a6895d002d619b5bd2cd6cc1dfbae30700af89362c0021dc54e88f651e27e21d6b61ec4204b8b9b6391634fc3e040b672e607e6040282e1c743d7c005aca59893766f5ec80b5df3c0a28b54644c23150ea5854f3daaee6c648e282f3cbca2214cabbcad290b06ea5e5470f285558b6532968972f964bf52eafd70d2af4bf889d9f7be2025ae54ea657cda8e737b1322cb191c279f5de95ccac6fab249d04c7984a31e1a21cf4678046a77d9f49b9e810ee8a72edb9e9154940d3c4513c39bdc96a5e74739e63a9729ed9bdd9ade03e39f33e68340212e5496a7da45657bfb33367f7fc040017a35598f6bbd3bf9d4ca3b44c5b4bcca4355d51bdcc025e3f306c98e409e712dfade101a82bc036e9766f99869c1edb0b2daa8d379ee7e524dac36583a25c27e9a3617c2eb0a27fc01c635e728828955ba446db634ece191c6ff7f322615fcf4b29d4bcd9ba9ec8a45364e0ccc97b2cca7854ac0c2e867c9ac75a68885dfb437b84bd768eb9d31a19b400cf026ce343326c4856d23fc5d357ebaf6f6de74df1d7062cc1479520a92a365177b6bd02509095fcdfa81e152dcd351cacfd10b12c1f05b1739280682be31dcdcf40039f26886170c856340674bb8aede95909de3ff65f502f101633b7d4f7ddad2af97c89de225878f83f23fea49a217f154710c3649ca885f7c6c10795a54696b2267cfdfe32de727d2c5fc64f37e105bb8273b7568cd5f69199f4903c7aafe94b00dae975a237c42937f7ce48b861b6478166f21d87cf4cbb379809e866e9461cdac6ce7579ee9e136603465e6fde00aaae5c31eb314931674df7523a37badc7faab65d55880393a84e2f96232d9cac4888689d59d84db91a37c9c4d7dd430d839c17610fc8494c262c55826fd528ad79d7b89ce45422086e00d8ede7d058356b7258acc14a9d4f2246f5c7de0349130bbc928f6b892a1991b2f7ca99764fa22db868c02cbcd7c002a7c3894dc045fbbe86927db367d536c5f2284f9da126c1f4c51c6c3872a4d58422c6f061438c1ec7633dc7e389d1a2def000d9a81d0d5a9f921f736e0d963b36acbc0700cbd1ba4ed52da7085b0c7ac7a5b0daf44b9e3b3f565c317ca91f62fbfdcdd1d57e2dd5f0fd64ad92cf3dedfd61458bbdcf65119dfc0944b59d534b8c8eb202d064da7b3dbb8a66d5ad027d12acfcf0c54a7c41b167c27c8ecd8940f4d7d78ba465938d3b9026108a66c7062b6fc5f5d0294f952c824fc623894f8a13ee35674861bbb9e15f1b924b7d0516837948802981e521ef21182eaffd42151d65d1a57753c3e09df7eff41a81951be64a12bd1cf9335c92ba56f81736bcbf188823cd2f8880b8cf69ae33da9417e33733b8dc7aca5186dedf838060ab65e661dfe2fb36e0649c4b386497d587b4dceac0128d18cba0aa53989eaa356526a82807e6239025cbbddb4e8dc1347eb9978a7fe75c4f8a3a2aa37f3cd562aaff43efd51ae40a70c97cbbcdf3298a29d01834069c0a7e7d347c35b98b8f5b41c0e50fd3cb32b3993b8e1ba4ce1b4a91d58c1a56cc77833c8d1ba3b2b0d1b640af3032fdad155dc3f7b7a1fa282d4c6b91370289de9c46ced4c93cb62919ac6f90d9dae0701a21e95e58e46ddcc24c04d063c5cfed602f05e23c834d22969d36a4abd9feca1fd886bba2f218a8726bb8a088d0c0a6ababeb2606ae73e089891f8566d7823c44a24c2076cb6f2efcacfbbacda6f6ee5faa4076022898b51197e604fdd049027d63e3b8f8e3e9e32078f24b975bfe712f24044ce90f711ddd911a6bdf9338f36c41c1beada47c38da1b9aaec71b966d608f13eba2b69df4b8664db72544e24190280e8490eae5854c4481d0f3a13b57b06bd6e1e42419f16ca8d4c8a0da7e5cb7469846db85c68c5c891045ddc00a3577b7fac734947cc6230c1a8e13a23165188fd8bc84ba8c8afce1624923deebc310784147d9f311a079ec7b9d17ba9ffcf33a078f2bc22e321d28ae3a427412c559395bc17165b6da57c8bdd97a97d658e741402e1b4ab511ae9e3b41a78fcb0cc186d424e5c18c28f8c00804367f82e28ae5b7bb51f2952e4068f031d4529c2c057012c318108088c41c7c5d761075d2e929fff1f2cedde2ac0e66e84d72b0d2380c5f39681296bb4cc54dba96b77b89b452584260e82b459afb929b55b8983811f405bf27b9112235c97aa84e3a869ccc6d1b9842bb7ba5d50103ef3b83593bd3c4806a92ab652ad6da078cd02f1dcfeec92a742ebabfe843d29fc27243c9f66c9eb33f66c4f6bb3ed0d1d209822876cc7877522fc74bcd9688090361ff3efe6aa739191892ae476f23eea1baf305fddad3958b73bc0f620d1e7ffc61d0908c8187c1bef9d75d68ee719b9ea495ef867df3612486e6a13a45f456f24764624553ed79bae40bef3c4bfec1b4a251b2e6f139b43d0084192a894f07fd2f7cc361f8098ebffcd868e6bc2f04fd1561b46697c62516604cb98e19d6be70736e1e628a10b2b3e49cc0d3d10e5f24b6d54b1bbb03e5cfbfe76978a1168d4e09a3769fa1bcbfad51ea7cc50cc95af77d1165b9121b9a6c7a7104346bff1a82b81af75543c9627b3f0928b36033cbf21e4d792b747c5bb169fa6c05ee66f3ed11e4d0023879d57f2a7392fcb74220fc2014d55e255ab2e44b077d1afe815b8847a6692f2311adfd9a58e590168f46a395c62e03552caf980c11be003f6ebb0a9c1ada03ff5ab61dfe1985f571d9179010168d36ea2d0ef31f04f42778ac96f5548ebc3acf21e24441cdc3560d825bfd688c7d57dedc6b25dcc2773b129f14ebce634b5078b7418be4bf630c03105763a2039b77c17c8907c19e615c531019a2928fcf06a971136641ede93ba8aeeeb33c2c6428df95a105eae28386e7abecf30e1c5f9aad29dfd6928bc082a2a855ca160a629cb671ccd47a4935aae18075c6eaa6eb3dd5bba2b1e47e29d2daf24ef0ff749e561ea0749fb385eb44a983ffd4b7e1a0bbcb890ecc6fe200e7f3a2fd9a0f976ae27874db9b7dbaa582370c95992d119352eaf8d12711854f078e7245c1dc71754dd586ad3ead0127329e58e7d8f991bd2b0cf1f7697a69adef6c3fbb6db857325eb32fa77bebe600161206c792a24d5100e46153317246741a888c256e21708fc0681049c633eb6d5c999a2c59eb4753cb29b9fb1dfb05c2ca28008d42c137fca2d18b9cc41803fc470efdd04fae158ba0386ed99b80464b857ce3dcad64fcbf7d0c4708e68d1707535dfb55561f61c102de7a42c238dbfde06da5138a8c64a0fe7db03450c8850358c5c4fe1efe684e532dd9a6dac24b73d9285803c265e5d7523f7400d4e59b9888676bf833527cf834230d4d60aa50330d6b31a292314b5c49660785dc6c208b3da46a322dea8222fcc59b1e8bde59c691a30beb75693675074c202b8a64cc77e9981cc798fd754a86398c84b75e099847f1e34d593499a249143acf6bb8cc5db89242f457ffe1d712eeed533d54b6a9d7415db001153dd36a3c14f687d73ff9a5707fa372e4683af7aa4fca8792eb14c52e3efebe133cebabe63111a802bbed14bfdc82a3e1415848b7479a80fee65d3cb9f049d107869a75eb59d3178ac8c030ce44ad1ee8b44cd3cbef07a0ae1a968ab0017d210f5f838e7716954a4f63a4a42309e354f89f76ffca631534adae4e6662a4982fb5dfb07f2338c9183b7d11716f5d52b5b7f4d6737445bfb026fd5a742f2e8658c4715da1fd528187fd7a32ea9adaefaf9438bf996b3a491b761463b2c19a77d53e54098af53a024c58b31d29a0fb5e7a2a06d056f16b9ae2f13846b1ed06d816871de1d9748d687d69f4df7997a16a59848a4798a205aab6935d4dc9ba34cc5c5133749cb6285d26e8fe5690cf8e0d715607a08966b9fb3096af2db2a90a43af9252bbba9bb0d9a80806dc8dd4ed0a5d40527e90783d2c60eb0ddb281412969743096f37500bfd25cb34be2101c81614b454f06723c88d88ff892a8d6bfab93d773cae2f12e5ef5bca268d4e94918ed7298bfbf4539ad4ab93e944a12f15ffcaf9a2f4d9d02cd9ab3d6a339e4d9ac131e0897baea372d3a99b29e9d4b1d96134634761fd2695e239c507e1ce409cc944857450b2db16480cf4f77ec61c5f6bce4d0c4d9b69154d59c3f5c542e934f3ebcc85dde13acfb7cf7336f9e3c2e7a14c2a2231c5206895b67aa09cbc8461b6712589d79ffaea5bbb567c88556a51890efd93ddd8458da8f1e469043364083fdc72636309a7381e1a3d931d9c27a433dc2ab9ff9c0f47289a3925acc430708512f78884c4e08ea5e605fbbb8d5b8b61745dc5f8f7e99ebfbf699c4c171ac6f65b5381dbbd074672bc98679c9994d2328e7239043b745c8788680b73ab829badfcdd6cb46f1fbd0a204d3c1f7e66ee66305a6eae3b06917a7ff5a31bef05613af05c7c658cf6d337f0fc158fdac6732d80c30f9bb54910fb8cc1071c19a3f1fb049575fd253d6e5112f841b346209b8669799c9baeeaef65cb33d25d176f0708da023142adbdf79aabff4332da18c633c35ae915d73e0bb8a9c70fe9196fc4a381811dff615edbaf792751c44dfcf437d0eb9cba200981420efabfbe84242c4cca98bca30eea0b001cfcd63520d54de84099b28b052c366f6bff578352e2a64e70ab233229e07c5c6095f1396983998c15e7fa3239325195f8083ff9313b7ff1b9437b7db3b5a893e44bf40c448bc88f04f098a8be12bcdd80b3544be8b6672c404b83b874010c1e9fa1e5e6fbe76c4046cc84299c7aa6377a566627c284f2f0e5100c546607a8da208d003e64ec3a21e8b0097ea733035d564ebba81d101803409e7b2d6ef882dba1bf33d9061c8dc909db469024f50ae73f098a125733b561ef034a0dc00423422baea0b211f4e355a5a4b0523008b672b591815aa2440a3fb0f286758010182c701e1faa2ece5e68c5728ef631af46d5b48e40839b5f3a891f2d52c0827372af27b8b907af6243b94a877c72cc2cbe606835dcc6971dce02791c648e3cae46e7f9a32eceb4f27e37a322e9580a9c643888745d91016631ebcf18838548672e10529170dd43ce1db914cc98febd8843b5e1b53f4b491f353784c30f5f1256c7b4a79426e564bdd13b7d0923f14f5cabdab6eef5f334508d59f628794686f831ea10302a27c4ce70b5aa4973bd7540afdae75b618c98b0519bbb3cc687dd12929c2f800b0a76317574ac4649068b0ea5aa18335e51c1bf7d04d5a9108ee20193ebcbf78c7bcb0a7e10c0afb9ea863faaa744c637c61d3671d4aade053246d9e196ad4c232360accdeabb7caabcc7951c4b72132c8fec0e728aceb26658e020ad7a86387b64cc29c93e19e757072f671eef787ef41bfc75287ec50289e0f5c42d1c0079f231a5107ac2ffa6d0e5470317a30a7f3622c59c1e085e24d8e2dd7619463e0a1cafb3f010d7f4bc7aa971ae331074610fe869d9463a5124503fa04c2eb568ef3ea38e755c4910c38bd04810e06dc4d48f5e647972283354037c67e7afd40dfdd3bbff0d54a2d87a8f4cc8c053864fe53b0ced60dcc0f3cd96f3dfc9df8a70010b9fde809407290fe618ddd401cbbb2e8dc764e79f347238c453ded08815135747ce4b5ad8abc24b432166e826790e856f414b603bf0e446d96be7f177f610669e5875cf83cf6bde2e78da2841ce6710c45f8c6715241823374b737cfdef19d95c5d4f921c51be8f83dd0f51964c61dbe0672e57f4dcc430d4af051c701120537d5eea11cbcd0744f194db5bab0f40200af9aa4385ac825ebac475c2a8d30a43366351cfe051dd7e7d8c87f0cb306d3501d77c7bd9e115878730ff728245010b6dea19c24d994416fcd25531863be6d57484accde2511c3cea73f69f60481d113cad0f4327e094bc9c5e8ca12fb297a689ad65caf9af5ab12c983a6ea8dc99777231a6962943710a5b23c34d5da2d3455762dfc75b762c66a42dbeba417ece2a112c1afc30b07ebc8e796e3e6a4956e66b2121b5a75ed898429ac12f199189abd1b36c19c066b7ea5cd4df81f7c88a7347b26eab626acb8505b89810a1dd9b7c9c43a2b77d087e1941c836c866eb0181decf64be737bfcf285c315a44f1da762108def05b339d2268c61fca05ff84cd31aa310c2a77cfa8d6a9af34e1b9781b5001bbe05d0d14840e0ca62e74b99ae24570d8c9d2e1a6731f249d98002ccc0daf3c3b254e49ba940b74363cfdccd53e4e5c2e8471c0ae03c6fedd08f87333da1a4ec1f4528a7540eda66345383f00613893574bce9e498c8c7dc4b7f2d08a19d809a8d187427d1d5eac0d275f9ca8e0cfc4e9e0af18d27a4f32a0e79fe2354d82b56413bc6e52746dbb695ecb7ba2b016bfb0c25f52b45be80749b6f07ed5615fbddef92c240eb4673bc0a477de5e41ef583a63a99b185c911fefefad951debe7ba8024e2dbd0efad0da9099cf7d97ee30b99c33e4f547b0da2209020ad6c203ea361b247126f03ddf866e047317fa34a5edc263c25c60b027135c2bb2ae140bba37bea31d3f344ee6c75b2ef40b06bc4fc0b8a2850f40c5d3e2e162eeee6636117950fc62015994fbadbaee22ef78c520b7b3c160c59a5dd1e785f961c4a34c4babcfae976f1f338a18f8f09013608b7da9f1c5eb1bcd37a7684b06aec37b0f76f3f0b2b5f42e5ad2ccce0f151fe01fbdfa1d66a27f85a6ee9a1bb9ff44c22dd44d8fe9db5494f969dc66844cafaa31ada4330866a4716dd3f1e44081d048bd5dc09328cb2dfefeb45b8bca130ed4b60f12a6e4fa8df2db99bce5bfe69a0b673a5dc1c76b07d9e0706870480ff6fa4316c9da02872819ca15153c8ae111c1384b150f5946f4acdd2c80c35d2767f4e1ebe53d7a18716da2311e1594ad781e9fa25a792209c25e00cc26007b058b6d882c657bc21f2e42b169921586879463640693750206ba36ac08dd424799a93500683ea7e4ee37978851a2d5672f69ffae715a71a3b4f1ce2dea6614a8952d22d4a44e5e0e3f51cc650d238a316c0560e4929507462c2b4b905462e7b3f1b8be23d9122a5fad8fa62cd70c35df855f3f044c54d5f4ae457cc0782d423568493b9f7332b1c739546ab97c49f7d0c841ff88358418a81a3ed1d95e1dcf69fa6c4ab0d3d97e79c5d30b02dfd25d5d0647e619187382ba78bf37536104cd156986e1d7cee46ab59c2fb8bc7521c47b15e74f647421e81370434ea4b2089ad9bc210d952dd9ecbde81d3908c8ea25564d932141fa64ba99d2c289e5d4ca52f4be584bbd2bb1e3513b22f5e0f52ffa620ce316e412db6fc810f630e01edcbf437f471e0bfa759c5b07568e032d9cf16032d0b63257ab4ad4bf254ffd3c850b5fd290c8cb3eaa216f26c1613c3733b22fa5030489161a24531a8b0085afdcf91ecef2650190841d4a52426909ec4fea09ce313a22d4ea273c7f40baa9482d9dc34eebd102fcf9d8c6ae0742e2baf254161b872a14c592a9fdefc16fedb1acc02c716c0e9f8398219b3b9759702260899e7b24094c553b7d8492fdb788e48183471aa11c0e9a3120cb6467837f3b0fb07be5abbd43a3947e5331f4539eb11bdee870c17985a9546aef730779752ce87672d1dc083701782f35bbe9b82f3dd70bca5ff7b11a8e99d84c19d806adb5d2de48bd401178765a98a4c02580f97ef612411653bbba17bbeb43ea53e727a794bc59a9e728233c8fd4c5f943de98c0e6e49c88cf0a618b81caccdee78ea86700074b9b34ded9425af4a9be14849d22d7d0c1eb669565227d228cd25b6a6b6f734c551dbaa95daad21ea58e0d8b751a9f42c03b372fad8890758c05cb5165cda4e93bba26cbda4c951db18b494f73edf407a5b8b6fac35e4b9ece9b2c3223a3c3dbae844127743247bc31fb19598738f495af63f8d96db91495b31e6c2f25a5e5475091f638291ae8a5f3c826b8aa6af4087085001405a125c0b8f711c1d59ec511017973677ff7f4a9e9011f726047afd80b2929a0139353581ff97b8b0bebd11b69da535370705a93ff57f9e62666b77a0d9d96c9ef00abacded7a5f75afe8a0ae9d4ddecfe6037fb54a1c44c2cb43e6db0f5bb1cc9ccd94eb0a981cb5adc31a5448e4e657d546fc1555808a51bf02c649088efd07ed069721c6ce04465a0c62d5eaf364dfe100d15711cb5cd612c8692ff710f20cd58cb082c7eea784df6564a4ce5e671a30b7536a32da58fdc52214a0d0f363edba52f64cd2628faf6a09c59ed62ec4b7dee675da1f2accc7f9dae6b73857ee3f76c336b070a1a07bcf5eb4cdc599d107618d56c3a763b2f1f1c0e61363bd9995e5559a7ceb661c3145d72615486b5b8531d7d5e6ae68afd3432f69bc05bf040347d54b8ae1c7a3d00d6c45222a4028e34426490b11461f23019ead2a7551c7a85f422f6810f00434672bee03af420120a03155e7cf75f3d945a8076175b68eef540f84b20c78a45c7d7aec2efbc47507e8eb2afca29e60ce256f91811e6a6a0fbd8f795456014d2b5bdd86105c625ec5f45db68d2677f4336c025c2502f96bdf5aabf0bef5af25cc37440f51cddcd7f29405be10a15e53a26219d69a8b6149c2a96c281161a5e64005bcad8ff53b31a79c9aed1eab78eb66953441113bf65dfbcf1728b01688d2b6e6aa9395a7eb6f354de545da579d267ac07243a95d085edf3033dc851ef5a9006a91d725c3641a0eab2f1245ea995a5d23f8fad3d358f2bc88951e5a2c17ebd4cb782ae8b5973620e2c418a9f378d39eed499d43e7c2eb27f33401ecad056bb4f44aeb5c3485f602066d98f593b0e651af1de1726f9266c4c276246ba763f2166979eee0c6756d6252fbdfe526f6e7f78969701ac43e7d0487d14a5a6ee7b69d00879fd72a04596d48abebbc1b1f4cf9016011c75b52dade4c0c22e3ed8aea9dd87d89179e12166072db176b28359856d9913c660eebcabcf73c1e4bfb9e2b124146c402699640f92036d1d038bd7ecdf9b8878b0522090b38af865387922d4e144e581d73f9831b4aaa0bcf613cc4a826b3135678680559ced5dfa6fe3a1d68fc336314acbf4297af673b8d48ae7f632ad9d1abef787a3c0f93fa3ef2876d260cc2223403b0ca2336cfa869801ee9df7d811567054574b79fe5ffa0a5cde0052b319b133108797c7c8755672ae07cb71b9cdeded108abe9ac2dce90bf81bb9335b685165cef0ee15776849bc6ca11a50efa21d3ae14ab20ffcec867456db2025c0b696598094e3c61e7832133addb8716cdb3f5744786088da6999ca382c51b16fd6308f3169e6e01120fa74b8920dc4fa6f7ca7f6d8184e7b299a87420af2f109e1c8cd730ebe3ddc3318a2addfb73cb48eb8967c6014cf762932977b758ed9c4a6d2efdcd8f97fbe31cb8283483bb4887180379c83bc4252898445d01a230ee96cad7285ee097a2965f7fbc96800d0bc84e539e29cd8da0c55cd9d918ca98a57fcad698a738e1044d9d772b0338b9c66dd8ac81c615352941f1f01f20aa294ffffeba98cc6af81691beb398fc408f3f3ff5965de638a4e4f6d492c8cdcdd7066b3ec2532cd91a3a7cdc54460bf9647a1f699d4ffab59f496ee31fbd9360ca926e18479a350341c4009b5cb4041e60704b862821a585546368a2af7bbb6cba16d318c970d6a52cd2c915afbf2601c4485dda6930f53e35def370e25f56842b77cc83b6422ff12af95d4ac675d3f12f6dea94d017f182755f5aa2e97b1627e14293552c8964e9c1c59c104bd0aa856dd9f5f17b15389f1c372cc5cfef8e920005daf5672a28cdc258c662315a7419390ade7457d35d02606020696d43d2e166797a6950c848f19d2f3665969864e3717bdc7438115564c1e355c20c3d3905b0468a0b027fd8b195f893b3ad61efd8918dd68c3cea5c630eb10591f95727d16a3f566db7ad1e1f670fe4585c88948866fecad10e4de24e0938cc6eb5eb6d27d9e788720644af8f0d633b32eb6056922bd1dda8ed979ef55811639b881a0da57f1bf2047c4b62acc01bf2db2b974d4e46478c79c4177559ff90e6a4cda13d30eddac01346be5c492886d601941dbcee839596d59239a7a5f744862e1b858069e6eb317e04aab0fc920bc486005e68ab404efb6024ee9cbe70c8d7468379a73d9ed5f2d3e90646d663f5d5921aa63da82f4bea9e87f3064560a1b08c7226b07d291cdf8a9926b862530b96cb3b8c26ee2001edc2e817d806c522133d1f23e6458ab2f7aba43a185fdd0698723259ca9cc3ab93bd65fa5040fc044250d3cea3ae0d44aaa916aed64d647bc6ab42e29fd950e1124c96fbd0fa3c21d62899d2d33469df878037fb649caf834f7cd38c8dc5982f8c4e38ea1a0b85fb9cb4f6c27643a91a71b2e6a6bf185bb095f862c7b3dc48e15d93272704ab5ac177d3f45db5c517dd9ab5dcbfb2d9acea4282f0e68a65aca66ace26e0df9701aae74573db1bcfd40a21555a15d72d04f250190b559d1a6c1b9729846a0c5f678e40ebc9ff75bf4902d5656610a6fd410de5eb9023ddbe48031ee2ffc13dbdb6d985001085b04bd95370a84967ebec477d6f3987ca5041706c765179b966ade339901842e73b97169924c761dbaa17cbb48f0b9014802a18cc7ebc3281cb0bede2c97dbeb3591ee15a4e41cf1c5cca8332115db167fddcce54ff4f7acdc06840a8aeeaecf7f1cf4dae5c448472643f9a48596446ac964db5bd33bd65ad00da0d15fd77526905ffe3165d9766c6c44026a4ace0756482c82ef9cfdfa6939dca1214d6ef1708d5ad23b4326b667a7489e197bbe9286431ef919447a151c19466b0a6747651ffd2104023736c3f0ec0ce3d3d663416ea29bf99f3bcebbc0b7301a70b668dbd94d87f73c09d9b2abd4affbae4fd8d6d9752c516c451873461ff7179cb814fdbd4e511d1d1732c6eefddd79d58b4508cd6ec0e9dafaef250d9fde0086db644ac2b2046fecd3164290880ef0c766a783c9617f6957452c833b4c9b555107b4badd923c6b3b43bd635b99cfe6db6d5e1cc6cb29ccb4e6f9fecd7be4286911d90faaeb9214b380baccd4db227f82659207674909982df04170f348193bd7493166f61c19b593375100e8f9e95319c264527b1c71908e4b60841012f535f0ed7acb266960685e2b10ccf026636c0831f3fb5eacce07a8332a83f0fd53fa32b37fe64c7b12ae25b1c0b55496b551b80e2a5da4024a33c4b7441d3f6f073245438ff99cde19878732a5c889038e997bee66c8524cf6218b3bcb3e6493ba974bbbc9c941825adf61ac075cf929cb77df17eb8a1b5c92396ff6452040d9e6a678293a35993bfa45e83218ada6493a29f7031eba212ad95a7775f00f1374d8a59014c18192413936e0671b66e2aa63491ee19f76544e620ffe1650ad11dac23b4ef4ceae3eb1da726f07e96c9d1367655e142c8fe6861263c78e1a91c348e50407b5ee53a2091d36165fba921e53e85d1cb24bad21361c4ba1646c9b4f3cbfd6ab3920d67fb0423825d048fff7ba0b6af206d875aee38fbc9c734f6246f954a644d302fe1f6caace3d503c711a9d82cda30e4d476eb77cd4cf6c917a5006844254cccf3816e977a530e8e9adcdd5a793e7fdeb7f2e01a1b37b51ea9463af675424e1240e9d39ccd5dd3762ba2640ace15fbaad75d0b2aaef62fc72a9c683cd57d6c39e946e4f8e5a8fedcfb74d3fd3b17360058afce007bc17966bab3ad4b97d3a9643efeb83c325a551267d8637bf09e9bc72031a46472c0b20527f123afcaad972bd1dfe11785850bc1f487810cefd0dd2e49610c2083231ed482de210d40454e564a95999375f891c8c1fa2b98513f759ecca4bd77d381f26cd89b2f5a4bd31701bf9b5a115f56272efc042a210d5d93abe9e93297a7fa545bdce534dbb138805f655180e464f5a5470333c40caa7d214061f91031498059aafde7ebe20b46090b7ea89af45f34a6c165aa48cdb3775bb88d240d4d4a412c9982cf2b380f3a56b1b486fb292f93af6581fc584e70a5fb5962828ad793fba4ac2819cdc1c8f72e7ef373f2df5be4a46fd1348a9c70ba4bc4e53dec54326afe8101c35acd1e707ca00e985965746b140459b2a805821553eb0375cf5d81231439bde55da2ae959ccd1bdf84b2fcdd4de6dfee011562291e07d5d923db45c374e908e49e1870e14192948de0c75543dae7d01c06b48b888f7ef08f73d264996d16d56457f503837d2693b357c2d20555163d35d1e80039e0d37e819bde52a11e88609b23a3943f48cbe5fd4a3816fa9fe63731cbf22125162cefecd2530b3ad82dae17ed6979333cf13d0346e67efa3fcd9e57867477cfa699eb825057a1607e339b234e0c7064e98e20cb72edaa9832b43d20b3523b76d46ef4e508fafe6d122bfc6a5279ffcd7d7caadfa15a373b27c9f6461c745f17c572be9621805c53bac93eda33df26a03f2fdbb4fbcb11a72741f8cefc5e58900a871233b9b574d54bf06621bfa282d0a4079ff5fa004d742ae22f17a573d75747df8e4f3351ddd36f0f3934ef9e201468f10c9eda021c05256863c2ca9f61f76cc3fb0d8aae5173fa57905b41aace8179f4803ed811e49bcae438a706ea8a36dc013d75edf6a4b2c9cffb824fec0d184a501944a6a3905588c5e21815a625e75375f3c6f5e3115ba628de99e8ee3d179c9f1586d6801376cda23cb17a7493f2345ce8d36d5b8ebdedd7007de753f4cfea8ef48dbeb0eec9215b8636691455a2faf401fc5973c6d93c334336ea15ec436d20c8474474ca7c5cff57de1dabb9413710cd202190496bf20e7b0aec62d4aff4d805c6535cedb9b2e9c34cf475d7da83910b2de764340add4af0d6cdb10b2101061a2474f3cba6f6acd82d42002a173e995322dec8b8f2913b7f1579a070b188930bcea97c9124b52ddbdecb496eed31905355b5dcc2951e35bd8a904e204e4183556e1f1bc4d34c86f0a50d317fc78926543f7cfe15fdda1ba28a1db8438d091a4b242a489bcb140e7e6ce529f3e9ad4b1a815ecb7a7aa7c836876273e84e2be9ee404dc77c51f452325128b80873391f950bf83f9091bf9208386b229eea2dbf77ff42bddabc4f58eedffda74582430549bee055165e96d2e5f288ca5be9218d3879504e5f662807fa53f21d8e06f4ba387180839a9fc863e3caa87d6c44b1b0da6283fe9b869ace2d27744f5b5033125eae3c85c6accc3f19269bb3cf7bc7b76f5a6985a39c8543d576aae62985b64d78e7ba18872f2f75c71d50458e2ad46dee8c53039c3d8e4f99b84cb6ed8101ca976db962a2e97cabf8d707f5c97ae7cec18fc0a1d9accdf068ad28c9c2be4c37d9894ce621981388b899511f70cde91cc020f8fce57815ac56684e62f051978ffcfc511a48eb110b81ed5e021247defa701e90b6f34ae7e47f06053cac3098c41e4734fe498a3766d3143d708c31872f00c562d31fbe509df528e4f1746b40733d0fd195dab7361f7ffec27feb731ea7a05cd8ba1ad00cefbd115dc88b89e24e1b62526dc60711e0debffc07e56ad9c699b9a1c7f171e305a3e67ff23aedcd0892d0ad0a3a4decb6790dddc460006c2e6169abc23d598ab8193ea405dd62c2f7ec57694dbe6e9dda8317775760c2706af8d27ab7c8eef4c93565f2e05e21f17d5ff2596903e439a90a1cfcac5be1b0b6c4f1e0e9368e034689062d94469f994ed14ea1a469b06a0843170dec9403de4de92856da4efb018007df556aae48362c139350c1668175c6659f2f948cf579bdaad0433cbfbe2c43f4a483f450b6176074d257961427494b9137d259f8d53ac0e62573237526593ff7f5d30d327787626160a1411a67bde1a283ab232b14987a108c21cf604a5f969eb534bfc36043cf77f58b265b1942606de54b2f8f692119ba6f40b4cd3c2faa92746b59fe06b57577920ce5862312cedd1087e099548231f34e3fa9b0c96b0e91db817309e7d898b6566068defa9039ee6a42f451ae50a78a0d849e0b714c8a0167c1d20a0e24a7246349cce1e0394f98c2b2508ee2e05f15c713db3012f053336d2fa5e449b43860ad2f523bb4b19fadb6684ef615acb8078397de7322abcde7e46e3e42f20d5b0a8571d6e8cdfa20933dcb2ad875d0244966196b0b10d57fbb17a8f4f5d5390ecf3b64299728893a86008dbee3cc4c9c7485d795bcfc2d287aa9abe8eb2c4bcb7506e355d8628246487b26d4d1fef2f249c44f896641e41090bb3afa0efedd356b650ec8d6152e01c3bf19c565a8cb9e6d8814c4c7e2948b95883a75e08f204e7a53203fcf0f6269d90b2df9e8145e0ccc9ab92079da4a104dead71593b1cb24001d736e5711aab7f193571e6e811904e0e6c49f048ef9a59ee58d9c577d3f3a9eb29e8f47c54f66506e5d14d2c59abf843f8d551fc339446656e3079e4f612591eb39fd440b9e37ee97d429ada15849710f4ffb3a6f685e87aa7cbf599a00ec75a2c854c4eac215dd3264c8a73746531578974834aea77a9477c8487e11b19d2e453e9f39707b41dc47b393a2cff09d12123414a7740074f91df37708b7419b02e66bc7187f373deaa1c99982b4322200a7f5d78a27be758bb6846242e652ab12025154d4ac78b846d67beea3000d4d12ab820bca02b5caff42e1697609219d2a6f6ad4db84afad2b2774161d65f38dde385c4eac1e1c0a1fc21909741f1eb1e6e6f646c75bd15d168647bbce165da87fb27eb7851180135191ea13cdeef54e5fb91ba533119028141f80d0155c73c40e66018837dbf20c21852ba61497641b788255670261f3fe69fcf583c319a51483f6e3466e69fb4e478cf5033761f4f3d49cb83913ae3195cafdb06591ddc0230bc260be858c964b90c465dcb271951d90ee4ae1963b1363c9fc77f0363a2ad1f2544cdef74d2706cbcfddddd87f2cd186aebe9be2e5c0cd53ca2759ef3e1a1f21b678dce42c42918381a5275364180f7b8a86bdb963d67b0657b59476722a321bc91c517f92a75f3b71541bfe46cd6f1ca790e4e7a75e7290c5ae3ea271b6b7d5dadd00975eb4cd02329b3e8cc2172e70406d0ea633b7f55c80c8e693a2e28b9032f6f86272b3dea3b09b50a99e2accd2edc527b7e6bf8352fdc19cc57f0f8c44415459b78ceea049df9d8268bf20e3255d85c4fd8d06c436a130788487271f8af495b9ae3cf87dab9b7b133f858d68c68b2575d5811cb41c9a820d4b3cb1b0d00e79dc24df85fda23295a39680f786384c1d1f2068a5967480dee0cd57bf4e8a1045a78594f4f37bd09f07ded83193b08d3fdf48bdc2ed3444f4129c990ea385a6fc80db8f04946ae0f13c28479155becef30b10469b553d949ab3e662a64db13267d1bbcd1d32c381c698d90f8795ffc0d653d93712d7d9d8f0516cfd9ba1a2189f768ae6885dfbe191a8bc171899be1f5ba68492f2b10c11ce8cf3493b8b779e91665a7158f70a41855f6fd6d3c4c271af13b51b216b0bda1359978631fc4bf8c9c2eefa230aecf15ca161a26bb9aa2b6f0097501aae918658e6f9fdf244d154ab42c26314011f8962fcd7325e66ee8524110698f7689f04fdaf54950f0d50c9b6837b869a7fb10f2bc2fc3aace11bbad93a778c65e424bb6c558a2ef1ea23e5b2a505ce0e3466ea6ae36b2d23ca0992a3704791d19a467d13d43722c3fd04c784ceaa22287227ca7c7780db8294ddb15a48623e3b36183aff6e252c95944608387c3d412b64224daccf79f9d44740077f39b39af0de8d491e7fc28db6462686c784ca4d1ac5e3a4f06fd0be66f7c23b4632e14335844b14ecb115f1ecc98a7ae1ea173a836ff89f6c2053316428ab3fcd6a580f59e5a1853a1928fd6f2ba56dd5e11311c349d5f9fd1bffe5e8702e34c53780d60396c214c2d259170ab2b69ce7f390c8f1e10a71c09169d33bcdc73de824a28665aee015675f8166bbfd7bb98afaa859d7c4cf94bc14c814194c5cb0633a0aff883b0a8cb4429579e6b00393a7c239bb16d723390592ebcb776117ad5d71cda6a0d532777aa3669d7e0d5ba015b58121d212c7109b0477d2bfd7e9941055495e7a30d64f079864ee0c1e1b6c7244c246a5f5c8475ac34cf8f76c9b757f729be18c5c5cd6ccaf9d679a941fc9793380b78001d442db6fce0a6650b2da50ac4d42b46750c207791d380a3b10eaec9063567720d4d243b7f6e0b00f10c1142090e72cfe0e7929e224974b066e88cca571c4f35f10c2a2cf01971b2ba1b6ec3b2e4f35a9ede02241b95d645b92c8afd1c0a36619e33ec7f64c8029a0f715459ec8b72b76b27f22f56b66fca9f155cafc041c91f3ce363ab6e53d609a0b23cf184e57b0ad5168dab002c4b1cf9a51890de59e22bd1396830cb21450898106a1eafdd674b2b236fc78d1c680ba7ea9b348cc3fc59220e1a0d1d7999cfe16deb95514e50c5ca009f75bbb676dd78547a9331c25c0de1f9bd48ba9ae84f385edfb1e7cb5697b1904eeda6fea9c9e5747b89f155cfc1290b636a88fd21aa09e431eff1fdd536669b082af178997d97a71857a2da988352b76500e93654b529183e73f6646ee92d927b6bf3656a99c472a220565d5e7409e9771bb664e87d5425198211b0384c93d0fdb4037ffc325ba87e6d73ea934ac34358d14e2a1dbf8041cf061604cace3c183d4bc967a250fb9168aa5d5640cf79f6cbbab3925a6e028f860d3ed620321341483ba1450270c1b1bba6ad42b55fbab7bf73b36910307d0f4f4a6b3c0312b85747d4122f1702e1e19817fa61306e04c5ad1e27deb0e2a6126293f7d4d1574ca4c3295465f0ec0edd2414303c390078ff87e03e7f0110dacab66115a854c286535690de89b705bc7936997bf5f42fd47f6e62dee66c7d78f11c53c0a491671abcf1261342bb9b1f228a06c0d997a9c53b6c5109a337a11da581ab3d9b4e2c6d7abe49effab7ee1153f7a0a0ae923085964cdf5f307b26a1eb9dcf2cd77a4f9edcb21cd1be5cb2b62b340f190e1818c6f65d3fab17926be98111c927bb5edddc58a0064c4f1691f7576adea54b1cccf4bb9e2c7a8030018ee3d7c8f3ff193c0e0870330df57ea7cedf3ffffca50049c52c6363cdcabc304ab4189d4179aba2c45eac11d0b76d0461a5fd4456c7b0a01139fc24d4019c733ff58f3ffe81779b859e43d562f0832ee99b2ce743d61779082948b5b8390ef807b02d473372048760b9a3f4ba66449c71015a031b99788bab935fdf7c2ae7219901187577c06e19d5a1edaa196e333c5f25b218ec6d62259e36c26fe699e705f6aeed29efb0e8d90941037640b13bedcb85c88b0b19b3a8438980b67b66bcefb1ad02eadcb373a3528da09cb5a09706b14d33232aac530b2e25e00a9045b0de424a53e3c1d5e326077b85a118b5cbcde17d978efd072556dc5de2436086ed0424d615610ee32766a9c1f3d386f7d101d64a62da78bade27c9a1e53e3f6cc548c80726566221c33b5caf68bf0f8273514479b3fc943b7930c97e535ed2f1d2c45c7600d95dc05fd0a98488b939eaf0e38cc3eb4ca9ca1ab6b963ee065e07f7475bdba7a4016463249a12a65796c786cef391e907546e64147f79db20229e1cd3ec0399959f6a798c19952ef666c80fa0ab8f3dccf2d86a36532df699c6126b201c9ce52d5bd0182faf038734510602d8afe68720ab339d1f644654e2d7c80d18d83da9143ad2258983529574238d496d6f2cbeea02125a63c2a0227153090059c66d4f93c4b2f1e9b65d455da9450e45474951f70810f9ad9fe72b3e44765cb6e56f8c18364205f20779d65b90b647bfcce409770ddd9d3bdde86b466fa587b6001e7eb63d0c551ecadda31d5c4d1d2ae820f97eeab43f5dea5b0b0c10b42d13f02925a24e4db8362c7167156b21764bbfad0a84dc89eef9f2350113a5e5723dad754ab78011bedbce9cabfb0a63c7db6f809ce5ea60850a5ee55f65cc9fe7d5346beff3001f4601ab333e4b9de824079ab6ff62e0a2a71c9c0e060f3055c832397e9bf8118e956e489dfbc2cb8bd3eeba70ed9fd3631dd245a403a28231d2dfa441348c9c30f94044c136e4cc48212757adc9a33fe360618a7485b48b987d1bedba9019502b6a64276d5779b8511338f56e055bacc67512ee85825d44cabf5734dda32d536213208d2e060cec878e20e8b61016e7d0f0c358ddb7bb033e8dbc3c56edfb839b088999db616b7f0dfbcee91e140acb176d677487b86ebb8636a32a17dbd94878fa0ec043482ac9e007a7851b19ce99fa89815bfff81c44d2611a1743e9c0f0a834ebd9c2b020f3b31f0453315fdee35af3ad974cc6eeb955d366726d99d0a3fbbdb78cb074e79e7254b1ffa65b1b01dae31870720a11e9386f7b3652cdc9e32804d700e6a6609a2703702a7e4848191d0703880a9937cad2df86e971ac929c7f1708e8d3c82da43436740dbd368ebc59a044cd2480130f491364703bffb5c4d21cd9d3e2c036400ed8e6f8524ff80bad5fc4b6c682e1bee45328b1add5e18d4a462edc1773259ced1c649d214df871ff5e02d3a373dbe2ad1699059b626725fb55bf953742e9832a267d26f541e0ea76558ea6e99fc54220bcb17df82fbe0463679658e18c37662fc6362575676addb92e8fda38db62310b671ff2e23300d2327f95d65c6f04693978884a5814318384d93ebb2507fc68eebdc6e643d96ad0002bee0114b00c8e5613dff164ed36e1524f6906da21e0f765e33c3d40c2e0e0c94ca943f3c14fe7537c9c7b08115c50218df109b200df3c77324cb7dae56612620821022a358edb8d9e94778f4beb9585734c12efdccff3d053744974ada8c236ab877705c4d27ad43f909f4859118ca24a0329d9c108525011683d14e2734e04515ed8df66e6e2d5d7cba11cdb5c5ab8f43523eabce8325de58d24a28c3a5261fafdc95d7fd8b3c06f77bb64e9413e53b01300dc694f54efa37f9a7b67927a41c4102974387c9410b338230a2099e35aa96297b41524a34ec38bd70f23b6e4d1f11720f0ade9c6b6c6a74338961e4d847efc97283dce97e0ba35136b8a030f5184e68d7b4bfacc1030d0c52b73f34ce84e4fd6ef9bbba294a6b4b6db877d59c42e24bc5718cd1c5f11b46be8b8198dc0d9dceae45bdca8c1dd715f1b254916ad8dcc65485248e5ad45c8badf6cfb278db8b9200a34279086748cb1a5e5312a1fadae0d69cf8fe261727576d27de3d024c210b4cd7014dcffae57fa2645233ddb11efa4e61b8f42dbf896ea76a5828c71339e6027f0215ae445d8a22cb7b27a4eac8481d72d7799e0cefd05ae0b6cf9db50344069154ede5afc4b72814093e29956bca4abd639e6c3a8ed4451a09928a7d13af9dd99a20f1b6f9ea0dfa5222903c1b351efce0ea7738ca16be3f7e605bf8c273d8ee8a4442b35f4b8e236db2771fae0f35956effde9d8c410aef69250bb8c0a97c764e26fcd62234c636fe1ddd514885563e71ce5ab09f7f715bdc12319e7048a3e946e1c6d3f3673a6047a00a8bf4826d96a99734a024f3c3017fd0eed70ccffe5a65b7aa087eb210b77901bce148fd12eadac31187ad54ae228b0be8fb12907d63f457b8ee435491fec32429d3e21a58194617d843404ad162faa3407427be2dbb3c6fe4b5abbc946e8114fcd0f95478bbb06ffce0e4da3d0f5a1902e1e33668d2b020f024f57365e7a03b58420d87efca2af2d0590968f2812ac68de4a57a7f9371c2def749966038f84dfdd85a846fa9fb3e37b94b72f7b06c8b1979afff95072bead955142d96d6c302388c31a37b70b2d0337e2fca7553653387348da3f1eb09625a954c9e600cb4a3e6629153b63fd7c2af1efa65de8f694a85797aa86eb2ed3e58c71d9c3019a39694c69d596eb04c378b5ec6aa10a183127b8a1c3f66bdbac8875f6d1531b4305f73fd3693e45f26a623d22f02919f9b6795d0c90118581d98d7fec43cb992ade85da50347c3bb29b958420555e399c15bffb5c0f40d14318074da47522883d96bfb2ea6fe7fcaec5ee44bda970cb3121b8964b50b17532a954fc32e357199a22aaec5ca6aae0ef1d7d4cd67248154512a0f683f988653ad3f1898bbb8207c21c885302371a1e691897dff1283d700a0495aff2c55d6cef5d51ef905fb5d25144499f27d69a7089f3fe984cb7a0834e77907de700089ae415ca90f5e7af09ebfa8ac7aa0cd8e1182b177aa0001a562dd0eff60c0dc0e95f4d93ccfc9c6206dbc62886a4b34a34e905271df557cf3f07d78cd773e08c3e3758d1bc3b7e40c80ad15c6bbc7c0fa8ad07af3b1506610721386666b685020f8e12b27e46bf61d6ef697025b2690d148d7dcdfad9c9b3b3fb02d8b78697505afc569990cf4676538981fcdd98536c8da6f5b2af58518bdcb72a64ced0c9a0c060855d191f2d88adef2ebace3b655b17cec3260e24c1f483c7090bdcf761c5d84005942af0448e93a02be36f259f27153c7e1bf33d3481c2c8da33269cb8371d24c0ed4fb28cce1a7c5622e2cc54a278c781533d0b3550d0a23d254ec5bb660ac7a1a3fde5bc103e5b3114767facd7e6876b15669744ea34e6b70d00436d55485d1bf3afcabea02043625773b623154d6a9ff0cbb958eb2700309c4a946389f95aa1aacddd3321bf7b332d81ab0840a057c591e13a587d6c08732821ac464989662fc69fb9eb2df83b12aa5228ff7bc00a47a7b2eba1a231d0b16bca77552b9325c01bf0bb6fce1630127678c07f21bb97d5d570be8350c15ffec83d22e0f3bd90d517217c77b848db691d7ade5504e5f3e2bcfa52c4590840d325b1adcec3a35bf489d0807462aab7f0f0984f1f0a8783240b8f161b2941c48a52d9a2189721c7278157d507e6d203a676de43d21017cf17a31d0ab64b4b07e6029f434b13a8e57938b91788b209602d6a580a352537077bcfe87eb5cb797788564ae50aa667f27d13d357800f90b84b4da42b6409f5ebd287689dac5ae29a7af166d48af371ddc7aadfa82f6fa87d6b882ce74d0b0401e3c4a156254c772da9a4e26a15952a9870e89e96079145d51a1bb981362bebb53ae563741c6076d963a628cf8827a1fe9634b5c72e748b458adc35463592614ea5211ed9815df691dd5a3b29d4a9ae73d9b113f7001a8c52124cd093b75853e4b8113fa4b97c9d06afac0ef9802bf066e0897b168b18eaacabf1ced6fa9c48d24a4050dc1ea2cabe55f5a49f25cbf804d2496edc12fd4ada754ec4d392e92832f6cf3f747a68bb29b075b01af1045543de4e1d388b7ec3b3305dff3ee8eb94e19b2f8afd90fb7858835ebb6825513953b3422a262180a7b1c4a25b34b67a50aaddde351d93c29525f868180a9712f64608a84a29b2e367bfb9ced2c58cec76c2a142d3e5adda054618ff1d9931d58a5f1577203a512c683dbfeb71814ebe73d59589f28c7a619c1d56f46d8270dcf20818cad959c5f0f5e928209481b489f92ba6a0c18e8a8ff73805ce4b7ce5010505d2996913091ecca569fe8454c94b71568737c436e2a2e5fddd9b437ab8e300a6078a6b1fca09cc6b9d65ecca6dfb9bf231d5d7b07b998c7a7a60a3802063234302c6040fec54d65e01f2a586d36f60c25dfcf539c2bbde3add0989e9ec72dc420eed535a069eed8379fde5f50397ff4e0c6066c417f725b3e9f98663fd35802ffe0e426fe1030d9a0a30ce4059fc28c1002853674e08b01de71e2130c96d8d4ca6b8c665fc775c845ea0d391049fa96b4045f13d3176e5a0039ca4eeda38e30b5f10f1ce0be1ace64384980b8818afc398d6aeeefb5b5532cc011c8e4d218093870248ac740d66e72c711bb13d13d926169d19efde0380f2b81662d7547ba43dff7d53fdd0755085ae2c5e7fcd1093dc8e806fbd7f516aac9d2f955eb413729eabc0607970cfe492040de6f9ba80703517240bb575fc43bb39186d91482e6323f6b3d3fbd66e0b348ce9b775d52685888c8a1ed7cd9aaad7ac8b35067cd432d24b5997e1377a75ae489bb180c8d1f3130dcb6ddd897b5335f93c6f25d27310069d467f1600ecf9775e0d63dd8e612d737bd5fb301c5126e9886f59c661cc384be032acd94e65a0b199ddc8adf51d9a72ff46dbeaefab12c7a71fe65c512f4ea28d92455074d043a9e58c8a91235da785834e95d9e0442ac604bbdae5600d0a8622441ad679d3f68ed63f9eae0e7a730937a1d8a1be17ca8035691fd460d52107a04cfa9b97129446220f74d5031b92cb0e7f6275ce4ddc9530e47ee0b62279330ba4db8bbe42242122e80a3420c8a2013ec7538749f87c87d3e058c3dd3bfbc6ee111c9b0e0c045556fd9055d9d1b3fbc41c6459db9a8b325d07098ee1e0b5d5d41972ab60bcf33edec6717e7687ab60de1aa5392686f321f37b0490e5690fb8d0f75edbbec97ad10193f3ce94bd31b18f99c7cc3d02b5cf1cf7ef719d353203f6ac33afe724491baaa6c6f8a0a4d3221e02154d2949f95c466e3077d46707c1df1bfbcbcc5b8078e6ed27be5fef5ba297b38e3a6851bb693f4cb8a4e8f75a6c2935db15168c0ed6673c79e3694999f14877e5946968cf7bbc3913e2490f036ff533bfe39d258569041e71b6a91d0d6245053b5c119d53d4d575f207fdb7240168ea5ca2f27c1563373744d0dfac3ca3e698df0139fad2a987bebb351fdbef512a1ee7b0d62d9d6a9d356a421e749e7844957da62d91a57c31c9dcb55b8f6cd3eaf4fe79c14dec2b7a50e0ef4ee35df0bf39657f2e5457a2174be52e74b7335cd770b6cb5c6eafd132590b6fab636fe17a66ea80f1c01f34e291a9ddbff02c9328fdb5390770a711d15b9b7244aeae0cdd7006781f5bb4431eacef1aa5d1abc2960ad7e45424195884cf12627d72013a1308b011d3f6ff98bb21697d257886c1ddbbc55b8ac09eb31e57d37c8d4cb80f8a299bea1fd388428d3cfb7e4f68ff193fa27b682320c55d9411d29c010ca82f752553a687848ff1cce097aacaba9cf7008aa5864ed71e20817a5570374ab55a4db3e03883af307295f528fe4f9b46aeeb56466f6d15b760a3da5ecce450ccf84c166e020293138f665d505a2b1747decbf0289e98570e545c48a18b8f507f68f0519b18e2626f57a67efe8fc13b0552f33ff3f519eb0aae3238284813e7c4e67e6e741d7972710d1b5c21da068595b3b739ecafee3f872e4f9d0f2021f690b47dcd3b500f2f4efa723dee57039c7921f1fcc6944b0b6891019aef9fbbc9415d378e7a1861ea8604bae7876a5765297b7dfe0414153a64bd3bc0dddfba8e1ba63b02092820752e2ce96503afb35f3ff7bc45c12f67b3e88713e225da4a194cbcde3101757089360056610dac5c52623615b9f65a11ebc10742721c0569fb30acb570cdb8690510b73d0ab230ac4b468f707a93581444753ba774ac436bebd9091c02c5d59af527d43e9009d090ef5ec1b953ebe1274374bbc4fe8252797a01f8b2cd0881816f0b2bdf2acbdb0b161d9563335b5b3591934b3f48faf10d1bf667db2392d2143c3ca005b208beb2b9d131e2b82f26ddcd6f62634b262f77772d823d92a5e93b52003f7fc694982f36901a30d1202ff131aa289cf5a4585b187ca443c81ba85321b61a7b8ad59ebfd6a9d44963f430f7012705ffe7135e315b2b489d8b32a3acab77f90fea05b5b3b49c33ae0c1ac3abd0b786bfc74d93c4b8676fbebcd7cc0c7116f5b173277ef2e58f27e6ed51b47b91db5eb91ed112b45bc1ee5d6ba0042b29ba16bfc0262876fbb6b4f986b0610a7b7e2ce9cbf244f406b904f4faddb3354eac70f96df4f3cbcf366bf3a391b2adb9e308f303be634c4c9336445783d44b771a6102a494ad93a5655067594c22f1f122249be1a32dd812ec56cf38cc460283d977e22f6706049680abe910bac81393e3414d865c5b876dc4ef5d79d825e29173f0872b2a0a684f309193a5e2a286f93f9296ca9bd79cc104077878addfe8d00ad13f3679b59c23775a81905f757161fa35a87e7b29313dad59ef8f4f310414db853f3dcb0a9166e26c552548822ea96db8da3abdfcaf9d2eb75798e5cb9c4064401fd430e1cf1c19dbb3deb8f4cde3e3ec26c045355846a51b857d1854078887785c1d550eedf9216ec6ba47548fbb3ce8aae87385bea6f08415e320a1137221add542837b1aaffa98baa7f78a6ba40d432728351148f1e043e7d8349d80b7eae84f7eb22b97895ab13f007d088cdea56b859cf941b421f5b79c9f47ed83eca99ff1b1da363b61ba890d18e555d048a0924daee9918f5363f91d7d4d9244fbff2ec8901345eed6fb315ad8f2fc4b03eda72d41ffbebf6865f215a1750a606e59ad445740222ee34935656cc2fec2e17cf17874e488faff03ce72190c15678bceb8b9134a998db40c25effd853531aef8ffa4f3b9d64cbbd96fe8d6f51f5f3dfca6e07e2ae430de0aa2bd816de5453b2e29cad626e495d688dd6dc1f8913fcf1fce536449a452d6115e18940b71e3461d3b291354df090edd1fbedc41997576ff9e149bee84bfc4bfc14c65c99d9f2ccecda27bb533aed4d326d97cdbe161ad69ee078eca5f59a148fb2cb63b1546685c753d51c297e1da04a9818a059cd85d551c7354d7f997a2503c8ce46ad855e8de18a463ffd5d1e424ccbed7a186a1dbecbd855a143501fc35d6c607602a7f3018dea3c1fc3303f19ca15a842646fbae0d0fcc99dee1b5ec63b5361d890f2a4c186daef9284cccae6f276472c82c162f07b7be61e730e51d8975be44c6dff2903aca5bea9b29da44e78ab1cacfbc95eca15b32b00b255751cbe9430d3291253129a8e3223b77ac67c5df5691489a12ecb914e4198e4ae35f527d2dd1f651469ed0dea6d3d99d15c0ef5b2f4a81559ab0f511f5427567f987c441eb22c3bf8bbb6ca7a44e36bfbda1d26c5cad09742d1664ab68e710cf190c8eda6d49af8b5fe019590418f795a011f4a8bf6b23623ffc0aa5a673e78058210eb5a8cca5a1e0aa93a4918609658d1c71b57f03d7823ac0b0a4fb2c8e41e12d03271612a1fa62a20bb937f4034ba87d5fb487a41e69fa876ab816ea2d6a7257d01d33095019eaa9414f8b7e6d80e8944db60725d9604f5cb9538d5bb641db61760201e8f3d0b342485341570d4299ad5af6f20577d27f13405857e4015183dd595785f7885ead9e9ec8b3a4a36adc94a15d7f8793b8c10b5c9650026c49e3a5305e48708c0cf9828a8f61f2ad092280efe7a9cdeb52c2b52ab1b8469cc308b0b0c5d666f1916efadbe56b311997b5430109af14667e24efc578b4de87521529daf742c8161d17d884d8194903be30d9c01e94f51a9efa2cc19cccabeb24bfe4f622a066d083b309ae5e5437ee1a01363d52a0ba92cd4e7631106cecc0b5cf1ba9808a15d56134ba34822ac106b4ee715606ddc3a8289c2eca9763746edaa47aa3af9cd623074044b9cc4db360648d3d58189afbc65154b0d8c16675b8d3ae7fe42df81532f8b3f4a92b95b24aedcaa8fcc3ec5a6b035bb728f78ed7b0e4f4298f22c3f6f4691b9df3a07a9c1be3653991f1287e29668a9b1b6bd6212a598ff7baafa6af17943bf641619a771b35715138126b499a182e5b7c709ef26ed32d34d5f54ef7f0eb88d2f97339f469a2d7e58073129deb528675da1f2b08402f8f976dcbec80095baf205784bc7c0408d184ce20421a02544995754b24d95a460b3488edb588454c0dbe58562dd64d166fac4bfe61f75e521d0b047416bd0a690ab5ce15637003fb3519643dba50b9a6157160c69be43b7f2118ded0f2a4866b8bed3af5b26bc1c76ea4ca0536887b9cfb73e7f75e008f46640858858a631dbf9eb6148be43c1c7633189bfd97b462f5ac299b90dedbe8da6b1d55cc1786722cea1d5db13e013920b185682c768a3872877075a4928f5e48af485d3ab6bc4d6a29e40001880dec5b4c39f15492ab4f084177fb7fb5dff1bd1db6bf35e6d2befaa64b6c6191da2e861a6dcf0e4da71e2061f3af08c670297994268c6b0b38986ec6365ab0a901b76966cb9d212d8b0a7fc3a7257c080ccbc36a1a6d57ef17826d7c96f8ef2649c07d2fc1394e5d03c39c163b6c8303f4e9bfe8cbaceb3c0328a7d228bc52dfc05bcaa6a00dde290bd20e5e5ed1cd0149bb52c9f9ee050228432067877d4db4eed517b4938ca3862d41a77243300b927de25abf8169a4371a8324ec85b991040b066f715e4a92875576dd7f63492b08db2783eecaa5be332c9784a6e00e73354d9818aef158b8e92eaa1a8395dc3cfb15cb6e6f00153c3057daadef3a53c42f48a5396ccdd52d88169a8dd3747f24c7f2e3b0cf1b01c2d0b36397b56dd9a9e15628e46333d182e79a573b95176aa809fa82fc8dbfa4f996827d76fa25c337bb1827f27c29f94ead8c571d4715b4fb263d843f9fe1737e6bc4c4aa52d377f433fe9b4a574cb2c9229f223f36ddbb08c42a46e6ee45dfdb1b63735868e69a8040e5753fc67188bcb69849f8085db2374f73b65ad5c81ce1d6e996b5795f1bb7e2c53e8c501b21b57b137f9133f7845375fd66eda679be18afcd3525d7ac3691986e3b1dafb81917a6cdf8dd83c99f7caaea9cbb258522612046a5d967d988fbe850162f52ba7b91128a7a94984342cea5ab8bd10bf0057a84b591dd240b8492da5d6787dc73b086bc80ac7d650d6078e1242ebc8421c4da9ca17b119a51e9ed7df2aa7ea3ce1e29485f9fdfb33d48951de36ffab13d0d4c0b3cabe16bc41cbc61d4795432ccdc697bd99fe89ce08cefdd5f984127501d8fda7adc36cef16e6f1c3e14cfc7d81e5abfc9bb3d10cee441e7552a556f46bed3f5c0256a0b9db5a8c6fb9177d6ed4e4ba2e19a3b53d00138c625a94684ba9eddab16dbd200cdcddb7c9749dc8484f5cc57ea8cea7d1a959c4a4af64d588dc78032f26fdb9f2ae1e3e09540232c58dea23395ef20f80efcad05dd380f4e513a8c209099686daf885e97c2b6dedef822f131dc4dd106564f88d02a810fc1840251f3db33c405a426aeca8c1e86db2d05276ff2da0b3944cef044589a4d7c9c43b38be8aa2b93ee1175a673142e1a28c3a4afc42acdf990e0934216bc7041de0d1aaaf8ea975ab16c34dc82271ac784cfb3d7094a4e025d28f46c660ec3430b04a27b1719532e39c07a21df848bbe1f172c71e3972ce3885edea96f313aabda1bae08419676fb3bb8b7af62d8bf14b63e0c2c0e036a7f5bf332a5623b2416440351c31a0f7a3c67975bc249f46b8f58025e59a28cf89078ad3b4e6edeab159af410fe3b9a92843e3f621def111e29ae20fb6e64323e1f27c19058c4a84aab740337720627bff949e5ca8398bddb91067c2a86a5a2c00de819c6baad906cd36b2c32bc0ebf05d394cfc63d53bfffa93c5b98e0b35bf28e5cd0b62faf3efcf8a1b195db145c93e27bb29a9ae092d40969f4df5cdb18eda97c064ab6bdc74b7b1e8ee1b48c542606cc89476ab240a87b64abb701f6aebfb91da4f91383c9d131f809938eb6a069b430071183f7a91e7d1aaed47a8cbad7efc8ad384bb21a7fe24fb9f297477aad78f2efe03db89957c2abd8c59386c1f9cde12f142c6c8105cb1a70b45da218b86d050458f5022d16b1600b59c16baa2cac1cf80ec3b321441dbdf4c4559f8b3fc76748168d286477e46b5c999833abf737459745e3f295327c747b9057e5f6fb49d0ce65ea08efedab7ccf1752a61154ee54655da58ee30f809234a41fd2af27cfac54fc756f862987217dfb4f4d87bea4d9819ee28bbd4e378777cccef2ffb0c67053f99b7f6448affa53498ff3b2e502ac0adebb43c7b4a1bb54b8680c8e57af335c9f4d31ae318ebcddee3ab6598e02f6028b1228c966333c98ba54b4af6da09fd88881dddf3f4202b25e18a561677489a4450e98092855338777df7825fcc22f8b41afda8e721ff080f81af357b27c8615e69bc43be7e433e22db9990395d4be683f542912372aa6df09cfb65cd71252bcbce8a4b6cfb053f6d07650c938a665a52e7230e4779f8240f0ef3a963580dcab23e7fae579ff15c7580ea9e84e9fa90e660e8acf0a6c34c1c7d4de8dc3e837e22dd067278209269b845dea40d938fb0c22b1df38c6b39db81dfc86185dbc7e535e9e9085c179e175cba48e4db87f84e6d5e8d6177e4c862e51899dacee440afbd5905203a0f2b2475d06cc0b0c95471d7cc50d71e9401ea7a6c1a247081cf96c55aa07697c883cdfe60cadd87b308d02505b2763f94f50ec978e4d45c4b205a2c331dbc5f14352e54122b9c9751aa3d9c345e8187b6be4be93fe3bca58ccacf7a8332e17d7a0c46d59172d4244b0ea23d81f6332f63b729934c433a41c01a49e32fd0bc8310ffbe652a1c3c62e6337f0310c3577999923eff0b5f2c1423ba0f1dfa2aefaf7765e727ad70d52b93b7d476f2fd2e3af1447906a4c8fa4113b6c6ef07b13ce6bac93ef88b55bab7b4fcf104232e263886fb21ece27696ce6154f4b06a29f110dff8544999b3482f84eae06cbc2accbf220f5bb2688a963e5afc2e23675d4189baf41ac89ce5f5052f9a697871096e274d25a5b25d65328d4477a6fef1914a164709e1695f67c44baa640cce651ab9022e3bd619e19e6d73beab8d2a9f575fb2fecb9e2a79e2cb7fbbf3b8c423f3f8194e7297deb8189de9bfef75e6a33abec593f427e52b8c8ec944120dd8b9796ccd0234ede72dfd24b28b9dde06442f3f4657990b34316ad25643e1cf47199a47a1913c03dd02913cf48a78360f45620f21e2093b0e3f20f3f0e5b32352d8bd7f07ce40d577ffb37bdcf280d5546e6ab4d93ab881000676a66ea52b8796036f59cb4faa0c372113cc2aaef8d44b72edb28ac088a7278f570a16c712538b467076f566c705fb5cf2720a184400ae4abf338db16e56682019c2cbf1a3167e87058f49d8383a10fb9a19d8dc30c3de0a17f455f2c47bd6a5240f2784141361ba47f251a3f4492adfb0e86645b5e70d798a27aeb91b77db196379c25f128f5ebde7d395dd2a149ede1435d40504b499cdb7d0112016216fddb1802a2c736c5f2fbe90f6422031fd160c428a27a0aa24df8a490ae2e3188a81601f6257593edbe5b2d04cb4b09f50b8c94700c04ba354742a2e6b13d399c9f9febb13241f0ec2e64d03b0e1b224b4120995922ed758e08528b601b45f26bd3b39a67838385a27effd6c10395eafeddf9877b7e8abe2f9a82a51ea2d442c6d0f10fb58b2a6128d9192d01766f61e7e19b685487022f373e711a698b695a3be9de94ff05380539952423704593e9b30daaa47ba1d183ede682bc4670fb5bd9ee6153f79142563a8493fa7ec0a11cb92b72a8f64146f101035cdb774070d8e3b9477ec569f1d947e561821bf16c6d95956113518d04ca69ac6b2847a52719108fc009a7c28345d69e8bc35b77799ad7ab87785a6285751e2c5eabe2026f3a1004e0f3f5834a5cca5a91bd212983f010b01dafabe992b63312a7c62ebb0d16772f1f1de39d2c1e1d02f932ca16f95cfd54fbb25505cc4100d7fe4b4501bafd3ec8d9b0626f214667decc8538c57e50ab6320155c4627a45217dea01281a3457d58cf1d0d59b918ee5817a4d1656971cd072dfcdc5f6979ec0207f3c25ae89e0ccea59ef6d093820be86c043895431ea0b0eab4086ab0f2bd81fa7cf1751d4238836252bf09e7860b3d5eb2dab14bc00cf7c0d24aa9e9ac9be980dacbfccbe9b150f3e45d85fdfdd82d086ba92bf5edf4a43ee046f859d768db246d9be3f09c5c59a7c766bab4a3132787c97f46665dcfcc959f8a6518aaef03b4d066bd7b49b607b4f9d148fc75692c7b8319a9faa600dc1cdb1710912ce209c45f9dc2d12eea23b16081195c634d8e8bb5a1babfd799b5ccc527d0044cbde72ac8feda4b18d6001ab1092a6d496585de3f2f41f7d3662ec4ebc9c30542884d83af30b0b5678c7dd0e67fb45c4349d5a232c0eb9a8976b6c6b24069fa4aaa6bad67b44ad1ef1c00964d4dd96c486e71884a8672d1f28697b86a3d8ba0ac50b54bb32e7fd29eae9d95194cef721a56e0467d96c07ce8fef225ee5de5860c1fbe459a337ee2f8335bd88fae2457845ec214964d2aaec45f95bad28d1190162454454e082d9c6244d1d1be72299ae8c97230e2fb0b2bd558c8c9292d0175734c5c3a1f0d8a923be484d2c66432cb1fdb325aeda94cf19de687c9d01eff420fcec4d97fc143c8c845f695b2f1c111a71eb0584e0829aceb27e2d35120fad6af4516171a94d537efccbdb5df4c4aa51e3b007383dc5a40b6d2d427540da164db80c9b0f3f0f14bbedf0e33f7a9ea5518e35df8de7c2943d372aea59cf0acf30e2e8da3a23fbd66ca4d9ff2758d3f0236c20bb9832d307f78dcd4559e048fe144ed13b491c3615b5758413ac5757c22987fc3897170af758e7cd5df9a059b5ccb7ad8e95490d0f352a57539e35ecb3f9c2f00c4a19a0ed5d270cf145377b0e86aee820e0e04186b1fa7d97d9be23c75ebce44c43340cb08cb919a6347c057dcdc483f31e17994f42d439e7b4ff769f8d8a0386da94974a686d88964883c42e491c962267926643269c1210c4f8f468c108b14a233c4760032ca4a5117aa1e80c8c632049c50f3be6f4e5feac09a8a0b95c865cd0ebf729778d32e88d690bd84f3017da5cf572aa49e9a25381b2e85e30e8e9b5befc786a2b7bd1e0b0a88939670b2f62a0e3bc3e5eb5fedf72bcf23956a792e3e7ed8808d00a893ff677bc1b48ec004758a9190f690d8b792b12a7637c7e67169e5c62b74eb2d3430b951edabe4eaa4ae84270c408570945383b4fcf3b48448586c8b00ac39a61f8c09975fa1f7cfacbf2eb1579ec83b4ceb49f49e86c3c1068524ed5a3b48a384a4680c0d8b311679d8a045f6e27dfb38eec7803cc260edc6f4a567f15b521fdb2e3a3f6db7272c74ebdd93e950d5bb7c54b2eb690c072654a8db5bb8fb83fefd85168a4144315b9130e8038a20c754a6fa182011debb6e9a2be0dd883edcd06a273b21baa54d26f6a3e53565009e574e1f5ff55a46de853b0aeab1dbf9dffd21368b0cb9235b2f91815491aa9151335fe7c3f87d6645578157484aa9391cc4fcf81f314d589d1c4bcf4fd631e6b0998adc7aafccf2ae2368266fd807bb78b6d15769cce1b49338d3d3e3e2f3085d6fe56854d3a3b57db7e31014c370735aa16b62de0209fb1fc6c699721ee27493d0c25bf64ba70cc84ad4b35a13af0696012aca3d643ac4fc42176f283d659ac8f4ad6ba4e61269b6e4582f11ab584f32771d665e8758fc83122169804d706012fbd1af980ec2d1315c4eaafc555cdefadd95af8f1f5361c54ad9ded841019e62e93524ec07f85fe24fd3ce35089f74e2f562458a2dc508ed9dd6e917681ca09168f2280f6fe0bf7daf9a4695dbda8835c57b68f02d01d8f999bee95f81cca73807ee02d2fe35b365b0af44309ef2c59c1e5c7f5e67970dc4b8cd01d46b75685c0dbc58fdde7b9296470ffee403b58aeff42a950bba04b553fac5986c90d38be72af1b67afdbbc4e582242cf7cc9a68d666a7d7f992e23687038ddbdb86562df58aa00e0d34cb47905bc68cbc7363a83daced17b7e4a2c609fb273682a1215eeb15bb33993caa2d19dcce5f593394c3974f604cad479df2999e90df7253e1a465ecb95dfeb5b81c78b3c4e98c79c5e7ef5c651003a4f418a04a144b88a2d077ef6454a87ebb15cc978f59f48d155b79cc7032200c07a7ec14ee8ab78817bec38e160194b7d0b78d24d9b04df7b5bb8de1d095e04986ebf7b724b9a05ade9b691ebaa8862a247cbeaaaa7b46c4875c7093f885d5fb51dc1396f19d4b49082bbd78ec4046556aab57051df8335d08fe1f4943442840594df0dfd3413e59229e09721bfc805ad338601c43cbedb35c47cd8e9e0a2ac4219a931030dac5ac91a309a3275b433add4d77f36e7e5270bf8561086e8fca04b527d26347b96ef867246e0385b4849a048c4f089a21c22cd8b5f5330b26211fc551d99c37908d07a2285b54e4b39c1f96cb2b010b36a4c20d2558fc8ae5e7306b7ca71cbdfdb95002beeb5aafecc654220567ab928ddfa625e98d1bb30afa48210e9248b1414d9af972a9eae1d8a2245681f049fa3b6156c2244a4d49bddee7d927bf407f32e8ae4ff316c00985b5e13961c6337d119cd2928f7a51267c3810570babe6083fed217e98262b77b9e7de180f81c26756042e4501bcbeb6b652e72242a533b65c0424ef4bc389e8a70398932ec99035a992288ecee1a4d3859be5cbc151dfa83e976792ed49923a043a6a9e30dd08c9f144dcd79f71ff1c092497467c26564e1856157a8d086ee7f5a0dc6ec60d4396559a79690927f83fa923ec9605f1ffc97ab3d0712bc6a35e7fae78990c4034dbf91b141a9571f9d2627419233a50d6f19167ccc15b3ad27fcfbe32e26d6f2c3f2ace7ee01df190b1481309521b384bd722ebee8f24923ac270936281eb28a5732bc138d255fe2102a0a35a576a707f1633000791a3ee1ad370ce4bdfae0c60b09a8669630f24684f53eaf34ee314c9652d2810fc8fe2d8b46a7936088751d524bcb1edf96a9fc95ffd52210a5d128a1fad2a87f178f8fba8d481175b3a40f2b8835b73255ae479b4776a46307cb056c0d792cec8b42cf86e1234c93463b827b774a2a60e02196cdaab631e951a046c93502ea3f5516ec8a193c7d399e4a9c839ae276eef669815f761948ce6fd7a76076092a077fe24593eb5d2c520e29ab8759b8e37a67270003beaf7fc13fb53a865dd95d505c7a5c29c34d11c9f8c256a3cc80154b6d8e42d24caa954112e2020e5c6d26868c3be5366c1a5bd5a03752d4f4bae644792ea48ba096e72d77f4a9f5439284db241316c52a311fdc1ffcece5d1144d7f5acaca82e582a3fc44e8e90d4a53a064dc63227b4773f38ecff0685ca147a7286ef0b238a02694ebcaf795e5bb9f07c0843e8d522c238fd642a04308d3789f2ce6717af52a2d80eb068f019fe66f4e3a03bf249c76a6fc3033448e7cf05adb5654d98d7527dc98a6677ab7037eb36ac6c2adfef174c3734d22663d81e21a778eac488fdeccab3b8afb1b5f3a66b6989245cb4b90da1480343077a7545753e23faf7976aa476b05b60ceeead020c8386f7a56bd91b7c3e573bfbc7f332a1b9a41fa6a633c682fe73b9f0890a35ee412a5dcd6e4b746e1ac2b0c4181a802f66e053cb4ceb5be17f3f1da28e0be198a1ab57352031632ebfc9dd174e5f705317aade385424d27438ca330059d0c6ac6636186954fbca5a76004f93e05888e0a0d2aa053dc4fcecd50ee1c7196cc2a6973f3768d4640b716bad4ae65579131c5a445f8bb925636024a7aecf520c5fa900eaf22cdb3cdda92d055500cd1e33d9a32fcf3d2e0bfed4950c8b765c6f38eeaf7a25c19b01edb832f2d2fa79f150095c47930a3861d166c1680086e1aaa48c78dde55ab94a3e5a506d0dcc1f920e8c690dde1625d10b5ee0f54409634260b872390466cc03a22f456617b997980c93352fdda17723965151efc6a4b38d36787895d413e176c9af313c58ea50e2225f2bf2398a66723c661612160c7b6fe019a98cf906fa26c0cf17c6bc7ccc30e4753eb08a755c2cdbd555ad4c4eb65b9355e91221f58a7ad69e60ebd0e71f1887af6264c7e077e6392dd8425ad332aefa285e49e5a4fb35474457cc4397743d8ea6104713710aff7d1a8eb7d10e155195eef362438228c621adad7a982488e5c923c67a6db41c105f3908141d1f55b3b11bd8985a20e35a43c0f74105b42dd38c1a85c81a22e7318705fdd438ca3e0f825c002f656370d1681d2b80c395c9752eaa526cda925a0dc9fec6cb588bf000037ba1881dba086fa343270e5273d4673463b5f1379998be1516e28e18afdf6cae9ab84c01c2576bba7a2a386cfe5d3dbdbf3c208c1a77875cad737a19ce51b634d8829a24eeff4a43025946b74d86a20658f9f77e490252becfc0403050e445b649044e874c130a7bd83b9568d104ca28dd00be1cabf802bfcab10a8404056037b185541e529c162ae23e3e6610a02b614deb18d01f4a9c5efc56284145794b48f00f1103aa181b1d5c3050a42cb8928626563dd301a02f447d6151c18fd8c71b71f1d07a60f698fc45c697c083be7c8094622aea4ad5a06449a7754bf40dd485463f50c8661b538de518f81e9fb8c7dbba36f6775b7738cfaca21dcd42d8c49cb9504ef9586088a3982353080dbeb0d641792a416d48068a05f25a2f644cabf6583f3222a311fe52eb46028eaa80227e40da306df4bf13a85ed74829e2510bbd626cbfb99f6fda254fc5de335cbe53b67689cf983188660304b91cb23923e21090f8d9060d2f4f64df49daa231cdb5488bbbb50b0000d344f1a1c67436d1fc78dd4c5bd243fa13b7555e27e676193351ab2918637bba9e23eabd21e73fbf9e54e992f6ceedebb0aeb1f4f1f4439dbda479915d4370fcf2061ddfb4e63224788ce63a2d69961499f91cdfa78ab16c2d0bb634dbe2ab421f736cff02fd1c8decfbee7753f154f914534557d18ff82c6f666556a17f26baabecdeb56badf058a94cb6c0a451fb13fd74bc797995679602218dc25712b5c73b3b40c09b6e143a8c3e3b4f68c2a84e4496edc3262548f8c87dc5432ad0667f4036b2818d3180bad9951aa5252e5c8b5240df9c63e783b323a3ff2cc54df32d7b85c6801998f7b3c3299404e937261f2f8547cad6e217bc627bddd16155a6cf4fffbde6163dd1d63fcd27f49715e7527be06ffaeb552a1f83c4e20ce3c10698e78eb5020cb0a5252e0cedbf55b018e13c4d053b0cede5783de997b901f10d7023ccdbc56e063379cff66df4f7bc017d785a388366571378647d703a899e256f5eaf45457524bcfe8b75dae78abed24a9c488029487cc8669377e8f3a33a4a66000b9c3d761f2db209dfde30e6249a61ba79eb6905d36a9b2c82d4cf370da056e0b4a68cd95b58e992ca452851f9b04e93be26e4719fb005a8fd52b952b881f67689adb29848b2cccc58d26f37cb3d92704a4c1de91443a5318e2424a7eb8e31e1b2c2564fda0bd5bd6c6d69edceaf22cf571afce6a12111984e7484185b8aa6b2dd4cc2db27ee54248fd5bc8460a0e6d9c7684f30e6ecf28a0241d3574ae627984593658fca63ed1c767ed3b05c5d3bb44694448b15ce8729e2a90d903fb73acac3a050efe32e18196b325ebb017b7294be1cf107c9c521e1d85ab422af649b4f498eba04eba4a0250955d0884a304eae23e4cdd880cba59b51354c74b2d7b80f6f287c3da3ab25810d5ce0acf5ec8aede25133bb584d0d3eea1be0c81b1833b505ef96dcde9c9a823430088faa8c51807d1ee6efb04de776a8787c3079406460cfa0a8faee28c7cf5cad099feec6c4311bb288c3cd92d4dc89aeb083d871d529cde07a16e3a9c7e36a666599dcff78941dd021a7af1a0a5d892813705ef8c474ca057435fabb8be5a626e4dbcf69f5fcfdc09fd8a54bd3f97321be940222a41110d02694e75956ce8e6710ca757c384c004b509b5d280cd92691cdae02619c306994c2e6cc2d40e05617b841f721318a849169007024a7a210e5e2d5de330624e4669d290c42b36c9299701f2c9f46500162823fb014292f104ba2c8f0f4f1f2f8f2b20153bff1eabee010d30a28cf03f18c189fbc1c2c860a5f3549f023a0bbfff91ead81ef8b5eabdc3104c64598e03ba80aed9369de2cd2fcca89f6f309e620e4a5834f4feb0df63eb3464ab062d3e8ff1d8731ff81c439d111a9d2e85726296b2c07cf12cc881ac43adf0d0161241f8978f371dde9f86b06bf3ec116694feb293d843323bcfec9c4ac9764b1bff1c2622809c24601bfe2100ef8d82750f519c686427a46a1e587d0b4a3c92a187a36ce9da85a0146e397333a44bea2d5dfc27d5a45d9d331f0c4774395d14bfc360a922c040b024ae848cb0511700ef04cd61c2e9c15127854f2380961b6a871c7a5678b78d30d3335e8d133973f77520364c21d43231f566247b90073affa6eaa336c57f5d285399495540f374589dc3478686593b00aff69330fe2d287a561c9598c91f548bf2b01b8c7e564e4ec2631c2d4e8437f9f79f2b5e9ae5824f7aad288891d08aeffebc7123e85a530d2f9e76e73acf9ac31214017cd27f1ebc81e64ec8642e82473edf2ef88c79a45a0d53be1a05b684daa262bbb7255f24377850546032d26c639bc09ba300120e48d6c243212c402f8f20d460897028092b71bf33cc46d5f88f6761f9f069276e68025a1a25f583b2bd62b549d253d0079b113b8ec7e7807c7204b3117c95b825df29a7b692fae54a7632e104fc1db9f38e40f99bcf825c0a9b701d567f6411669bd1ada8622ffc122ea9542a81af1e672cdaacec9f23042eedc73377f71ad501daf28a28770aa17faa13328e6645b5db8faee8f6e71bad5d64a0d6bc00e0b080766faaa6212b18bbd36b028ab7e393f972bdeecafd8c65a3f8f52069b774e472190fbbfd9773b06be7708dde1106c411c6eee789ce5bde11a74571208156bda4dde4973080610c2fb990a668afb1d66f7ab85c5e5c3ebd3db03454946dad9eaa21437c00a09207d18a0d9126ce537e1b6f282ac3e5e00ac8826daa46fda17ac1f77cc7de555fc382faf6f57e5a8e80ccaddb46a488f8f3e7feae83cc28c26189a7243a11a955b8bb351151354a5246e3eed618a18089292282ad575b6840bbcf7c989509311977ebaebed26d0b122c96840c09da198e4491f661a7f3c2c4f9403c0c2c4c9f22d024fee685c328bdd45ec1f7fc98805c5998a59c421bbbf7ff972aec5fc5cfc41ebb3a9d09ba11c94a4d1fe3d1f32be5ad3af04f6dea964ab9ceb3a9b3f78481785b5f8bf7220ef4a82a01e11c80d952b7cfca7655ee0b4eb931ce3046fb8f59bb16c61a6ead46146adacce01ff6f13a4f60a70db2cf581d5763ce9f229796cda9915b248af040a958909538cdb7d2194ba6361b4ca45e5098b7ec20bbfb3b287eedb923c85ffbe8385d5db21ecf881b01dfd68640a82eaa60051092befd94077b8beff6e107263269ecf99a5a0f31011acbb54eebed443d13e769de07f56ed25b9ed1f9eeada72d3f368e9f184b8cbb21075dd6eba038035404b0cec9d9dcf2f5b427adf091cb7a66ec4c1524b6e67f38d37da911c212c7effcc93c70ff76ed732b6181a77624fff72561108a742a31906d82a18f1a723ddf32e131b386c2c820ef9c68954de5e00a61676c7f62592cb9308e6591e1e13879d140446e8839a3d97883a3c34054f10c275cb39bf61cf96b279ddbda395da3622c027db02b6603381eb9c97be588e5a7b895c79f593f06bf7c7984328a70ea6f9c7eb21eec05b384cf2a2d2a8eb194f7420259919d98e1c59387efcd8bf4f19aa25aa92d64ae741941f1678fb1eed2042b0701d212ff0e20885fe8d97dc90d494afd05ec178a0eaf19977e8a851a845814f527e315bb1dada3c3fb0780cb0a0d24face66e657cef6a492ed7737c5cf8f92de5a92adccf833e77edb39254f44a208a0c410da2c0b3720284cc25f46ebad43c017d01b41a41c7de98f71bcf1d23db2234baba2a47bc7ae08fa315ccf5cedef81350ea1160de44543572d91fb6800df9934fd8787a3413320e0bfe92c2b9de88196b5327a5046cac272edd97383b8b659d342c9da16a9b98100824648ec9f8a14689afb154c30e4a90c894a27a301eb1914ed48b674f823d6371cd865f8a23e4cf5c10039811e5965cf1275676c1c27394b9080404fa163f7e7307164e52aca05f089193fd46e59c06f126413a53f4ee6e43aebd0f24e9b6dc0cbd6a42b6cb3c37a69065279c2c42d01f89cf4acf09b562eeb8c6d8adb9fb9c911d4225134aa2adc3bcc67ab577e9b56afbe03221a22f22725e3aff15fb00602853da3e063470b8deeb37b2142b794ad37e6a5abc20d785b818bc3d03e2f5774f897f2ff49edd71edc785c1a2eb0a2df0563d7b153a2ddd60786084c205869853434301837d8019d9485cb32bc7c5ce6b484d352490ccca3cc7edf7bf905d65da02d84f1f846ae2fdfede06dfeca4cd81271055e65e1f73d37a9efa0c01d36d5a3ecb68761d4efdab2d109bd1f52c3f84b6d057dd388f65df76cf1ee178d85e9fa79793fc6e9db1a7b510657bbd51e459d40e9b8ed33f10cf1f44c7159f967649c24966686dd504aacf820538ca0e66fbfa466d02e4a6be463dea43a90f538f4bcab02835016763e09699d0c9457468f60dbbe7460b931b9aac476d3e5c84696dea55cd223bf0fa6a639f03d1852a78ed781612ebd273ef55e3fb1b999c3537cbc2b3f4b398b7f9ddf4fcb15924ae94149e075286b9f45bb841ff5c268f1bcd553f68c4ad589bb3c26c0b6b4da8c65fb6942892314f3d2633358aee7249fd474dbe5a199edfe6c014193bb9fcbdf9c8b037d1dc086268b9ee2ace64b0785267cc8610c1522cc087c50e59629cfffc61f483ac60ad3a046b4452ea50353239583c8fa9f35f5c1f453994fdbfc34d686708472bf6c5e8ad33422fc6e7fd53252edf933f3d9a408296c85b6d9eefb72aef841d2e46799f197e2dfdbbf12375ebd0477da162a001ceb9418e77cf00f5839c98c180a3f6211ae7f9e03c3f6c6609badc16a5924fd4103562a51a06b96b84af83a90082ed3f79c9d2ebd879bd424e755a15896249be155c6116fa9e42cb55441df688371362b33897ed54eca10f447b506d21a9786afc3566cceeb0a833ce1c4eb1fd5ba226e89d5a17b8c7c4edfca5a1f9c749840d47990e733c205cee0f5d945c10afd70d9282e16d7b7e7ad9253307724b655f5a10f1ea903576b0b6373a81cf97e37236d734280c450a39fb80008bb15634ba865b02fe7158ee5d925bb83b669511392a85d9dd35d9531a8829720e4d0a048d5fb03442e22e81003a5c3bfb7f41d975ba0930e3392b805c32433cc479c26a936d755524b87ab498114bdc8e4db97273ecfac4d024b898e129f71bb44343d0cb29ba89042f799f0ab1662bd7ef27ff04f6ff8f6e4395e0b0ace0653914c5d1f8416326b71147e19dfcef444be68471fd492d023d75dc2a71274b3284889bdf22edea7205b5af3e7fcd59e573cd414eb8b4255af04e60b2f4e5e3d9179870af135027ef2bc2f03b3e2a95776e6d8152928892abec0cd245aa53c1674b0b4ec3dbf1c8905ad13c4e4f0f7cee6c18b2cc6bdac467cc55c3162acce19fcb41ef524d40839136d406308ac79c983459a51129ce915ddfcc2d1daefb833b1749155a802411d35750607aabd4e7039ff17e0235f3bb16a8746acc93cffa060243d76b4ea78792cb69f4e4d36702751a291509fbb07b315de1c2c9ddf6de9e351c2930d8a5a82092bd6975066a3f6c286b9c496fc3f8d4693b0670b0f2bfa4aa92121e00962799210c3576ab148130de35b80983c7c6a7eb11cc2ed8b6b665318c501aed5a121fe0b326440f1a855e66584b76b2d7b6bf35fd4e3f42da7b17eebc5a1f8b29d0f8acfb3ce1e25d5d097c81a102793890da4d1109bc8157e302d99d07f9087d6802562d85c2b83d170ff41dcc94d81482cf6f207ce34adddf2c3cb3486b581b04512180157e8969d3e232874c94d54a609189cdcf31f8ae6f513923b9d0b329da8e48d369d5314cd033b08165b9f947fd49bd6e2f080815154e429cf85a1251fb8a7638ad17f10329877c0bae2cd1f5320fe2bf11cc9e5dbc6b9f8b0ca4b5f2acb44c4ebafeecea4a10b77abf1f6eb66c720465d4c3ff6c3152eba3412d9f6c46bede6adcf8abaf63d217ec1889e570c4b3e03c557915de86d197b347bd186ec26f36969b489afd495b65019216d0ad04b269837316eeb2101866cb814c2a5c76c5d0541d8388b5a23600f0f6baccc321c946c4a2023d95d45defb4c45325c6a1d31c42e4bc2bb6448e59dfa774f913fcb0519df903d8a691881fb9fdde1ceac78b007bf8239a341d4b7597550c7d567d161951664938e2c1cdb4b67bba6649056d53d8c2ddd1a416706e7f359401f762196129f30efc0a37e2c388d46e1d4dd35c63f924be3032c456f2eb4661328147119fa94400662e35d932bda084a2faeb61701e98d4fcc7d3ebc2bd8311411dbdfc7e91bb18dcb32c6f3f9b87b617b83fbf2ed35cdbdda98807355016f3641c8bc55cafe48368fd751ef779554de3202ff94769054c5e9cc084b2b7eec953956015cc8220ec3998b006c06616df32ea7aa3d742e53b719d45c739505182cb108ece16afacb820fda761d01b6f0f73d79b72eeec2e2ae82c6ef8db9b38d80f46985cb23d79f714e1dffab346aebcd8be74ef120acb98fd244e5f3d71a45c62e7b5f8d1b07312ae152ae1c81e503ad99d9058ba0c32a0cb22fadf1a7a308b8e7e8aaed4e531fbaef9e753c54beed6b1bd5eb0d4c1d2add27e9fdbbe018cd36059b065d2c7262b7479d9bf2f1cd6f0e0e71eb440abcf4377d572618b13ad932986e37417d2cab1879f9b79e30bbf49a7aa6f7b522a1de0811fb7de78e9d8003ee0df4b9f62628d12b538c6909a5b1f8fadb0cc547a4a289e5a5f428daadbe6792c44919fdc4bb662f26bac3a72becbe215abc6017b7f16dcc10cd7687909f6b7450caafd9aff9769bf7afbb9ddcd5530fb315c0c4b792b30966becbbd2e21158597400d3b22b2e3e84c5ee388f3f67ccebac4297b28f98f27630c7cb3b76b3c2335788f085959f9b0972d7b6b6d6924d81be60bc5d0dec5f4a3835f340d9fbde9c59ef5625abfb5e69d13f8868deb20e05797e08e7640916b68c911fc452849ac4749fbd83eeb2088f630ed5531a6dad24d3f4fbb10c1e9c274ffa519b105365bfb13a0f78a47d41489a4819c9a7968fb64e5e89950bd3d86cccf8207f829cb512287fea8f71a752e5bd37a21089475b8e03d79b56803297dc3ae23449b4228451919221b2ce21a96995dc1611d5ea7b61802e8ff2ad97090fe5c5cb824115cfc8038d3b917f52594cb4d0eb45c57d9ebb338ab4b956874226c3d33743bc4d163ca6baf3ed744e97c88feeee051c7d39034f5bcf39707dbaa6c4c45e5ea1a5dfb16e74b6a9c4143ebebd5bbd199db5018e55379af87994cddf4cf439e333ec34503ea028a9a1684c22ffda4da2924972c5c9163ffe006a3ce4a38855ead9b32049ece09f867d1c63fff79d644f98c24b5baab9e12f1acbab125e3e55b4d4c735d7113fa614befb4906b7281fe626f4108eb175137add82212efc286e186e9c094aa99b7cb0b00368ae6e3b6e179ac8877d887e5ecb2b1c8b064601e942f574b422d28d9364a5474fe6012193a6aacf3b8f60ccc0de498738b12168a4c3244c53404c6511db5baf2f544633a7df968afde2a42128d6195b5e51e9da92eccbc4891c7ceb52202cba5e261ee0155e73ff5dd7b554fba4fdd8947081f43680d589f9170a2564359594d773cbfc1d235e12359ee0ee924aacf156e7b1fc2cb0058ecdd66ec689721b19b92c1fbf52fc8d801fe10466badc8348855583b5a16b098d32adfe45b9b9a0c8c49a65461d3a781efa759ab23262229ae42aa3ccd6fdb008d91b46761d79a061df71323838b68627692309c8307d4ffa9c3f1055a0fc6fbe785cc95b957df98094706e97332a180353758481dde58014b8fc9133eb867c3a47fe788133952fa2929048a6f2d5c4df64a1dde9d4b483f9aa14bc7632d327bf84926d67be014fe82ac7ec552c7621eb8b7bfd60a77d140cc5cb8029090e4a30ef68bcc730bbdb53d3552281ee8a7ab97a7af8055a39704dc0d0934d9488db134e5c77553882766da7aa33f117ee41a62acceef8e60028a239a51f510d371502eaae294ab9c221f8e3d0c1f6d9b92893547a2a7cb797c1a630968f81417fb21011f7638e494650f16b7165323e7f1f71f89e2db82fc6ee52884470b313572ede921b298ab3b6270043633f03a1ccb66bdaa8a2ea218465f989bda895f8b215e344604eb275f0a07817ccdaf331049a6400b0601456f3a22961238eec7a44c9987f8220b843257042b4d0026871821eaa35daf5ce0b2a23989ee5933fb68848d6a1e5dfd882cede468dd56dc0bb1421798acf69ce71cdd778b5899a5add26e07c8acfe52ace6f0184374f6023b8bfabe7d03e2ab82c7ce7a67f031f62b69ed52f20b584ee7429f386ed22a91be9d51d4b1e725364d508008cb69dcc1f1b476a5bcc96f5d8f91a01da0c979949225ed00951f3c6527884c5f87d72339b779ccf40fc6ce4079195efe698d3da7309ca700d8740c21917192807d573a5084fdfe6159fa46dcca81ec3e8b6f242609cfc8bb8a390e7d725b4aaae29047c96b602e688acab9ec45d42b7cb30a10bffa8a00357cda7ea3f0b81e4e9c31e48a7d6ea3b219b8dcbca02364d45f44e0c9d52cf677eefbb53a8a633c72b5410af956efb4d08ea9f4b1a9e4a9c0195efd90392066fb838690aa6c722746a70bff5344ff6e8bd993e6fa96b2f4e7ec84aeea8b36f061f9db69a1cb232aa340f857cdfde297f1dae98c0205804f10ee9dc384fd575b446532a665b9f8f161e0e95ad0a96ef8de2b28ce107873c89917447bbc86bac47f157ee32d33d38124eacf61f06837ac48b339598b8d0508d8d8c5113d8be050c88e604cb2b2a5896230d0df1c1c7f5c51540c5de683d42b5d27778148c6759004a058d9d057c7271b0bbaf963618f3a087f0d400258cec47301f10d3d1d93ac66ffa16947f3b5c074520d3ab4e402956b4db3d25e3b028d08893f3c263d51f4644d0088eade40626650cc179019c8208e69f67c9f150bf635984c5cbaef3d64156d8123488e81ab94535e13fa39e7bb0605ce407e3472be6c6c8c074c0204e4f7423b415832a626662b2117c4e4cd16ccb39bdb9be222cf166db188ad31aa9f51908cf42e285025dd1808fb5afbe7fca98ddcec4b5a5284d4559eadd9af6a7e651a44dd31c4488e1bafd6e8c6ba3109e77f63529c94661231a85bb06fc9b5d8ed655d60e16a8c060c170a40fcdbd3948afb6cb9fd7b1f25340c68c3d86453673673f9026bb666413367f9c9b6860cb7f0b50b2db4c42a899be2479bd93024e20dfc7f7b29f26927cadb664c3e5f7eefaa480f1c03d8e9e65a1cfe646ae381794921ac3678705194de6076406317fb5916500232215dab59cf69947a8811d516de8a7d6af67149375fc45a354ec0bf6b39f142fd714a2d4a0f777696e613e4e50e3a1492350d64f7191a8a5a3133e7c4c72ff183fd714fa4f5103571e29f98a3e907e7a09d5c60e14ea4cd11571d4866bfb8bdb7ead9220e304fdc79a080294dae67a33590bcf896401b16776aa1996528d86550f2f34f0d60fc52197a0e23cd2ae15dfe8e1d2cf4d373c0e0c68708abb323f466c8f444148e4e3073aad7326e96a91ccc0a7246f3191b5e60353cd1275d00d5af656c06e6ebe807c515f65955d1f7584c1db0d70a479c837f76f3b6bae1bc8465aecdb781d0fea90ba6ebd98a1060c2d10f6491fe18b84a54da8586b8c6d4ef80ef47cf7798614a6dee25180a2ece7fefab78ba8b73edef97d2bd4e4e13500cdbcebbddc48c5133d68884da318f657c947b3f68007322f7d6faff651257042e29f8f6d07855ea257910db076990ef4861e26a2a10b79b2de7d7d17d4fd06fe7037888fa5249e9f68adc8d0f36515190202fd1d92037b07ade1a724820184223ad496d966387d8cd4d03ed24e2f26658988c8e74203ebb35e7fb920077fc21d03d8f7367fce460f02b89a33fd4b083bb4c6b8f4b7f3e69eea5eb0a0906c9d6622b8f9447943b472fa4f66420c17e443379e3c2e830e8593f04d194d0dd6b9f51b15d8d7d8eab2239a8203a3c773dbe7c862b7f10e5ef6c3716eb165bb64a0c84d79c2748d399835512893cdafaf07d71587db8db80cec4219b4083b07da5160ac9096d734dc44de7341338dc03cac85c5439dc8ba8c1c8914923eb3b99763418feb3d6d2d639af71ad9bf8107fe22aa8e6a5c59e8e29cf27b5b18c7768f7a758d9ecfda786eb27f94338c9e8a2024bf5bd7d86e369e41404b185f048c33ac0a6a518e756e837918642adcab7eba90857f31c12717466214ead0a80d0f43e68f108f9bc73df6aa88d3d73fc3cdaad47df9ee2f18a449297944926f174c8bff9ba168c1104191ae3a13f455d7ed724971cacb36cb98b9115973ac9e6c84f87c468fbd03c21fe483bbea85e32a0705c1e95827ad3c1d575305f5ed8ce8fc8f67aec9bac204c164ba3df9c8a1d36ac084b7e5f9d15b60c1c4b175ddb07f5c2dc974fb6218300ecc7017866b9d3e5916a26c1054f68e02cc2af527343ab55a1d873cd631c35585587942e18584ab189153e06b9a512c5048034dad1a2224beef5a316f2c60b59be00bde53edcaae5b37b2ae22ef54034ccb989a8eea31faaa1a9b7eabf0ee48f0dee1aba0c41eadf785a0b5ab7b4fd2893a94a04476f67841495ed6bc959a384ee23391146c4166eb790d988e8cf9d0d8295086d01994748c6f45edad6f8a637f0bd3182733fb66b564433f38b18823be53e958e415f6d8bffde1fcfe070eddee8284f0aa6d8a92fa84f354df51436abfb666889bf7e120c7cc3e4152ddcf6b1b10d86b8ecea0a27c9d14c7786d26347d215b69c30e0ee0239e8b7d28bcda08da3ceefe2b2abca16004784bbc7114879d3711b3b334783c1c494c74adc2256468ae05c42a026a6fc5e42dd6385f305a8639aa02b765e06f39efb57094610b604f9e32faee7ccf3faaac6e2579ab7c1ae818975703ef784516d215bf04ec8793159d56849aaaa11925e1c4353893912918a47a95423394a07da4363096885cc8c1084a679d399459e9d29fcba5ce425a3bd2d205ab4756f6d870ba9a5d953b7e2de12b4dd6fc262e1eed4ceebad95877407abed3a7eb36d1ee6e485609d1f36627e4f70443e5a41c859892679e4216bbaafed38e119d6e4ef1623fc00ddb27e9651bd6ff4051197af0eccc4819b01d592cb844acf6ca3f9fdc5f56b9e1dc7fa0494612d35dd3faf509ab79621b9271f310297803f4686b89250737284b6e6bda6fc94cbcb7d24c0b693896cd4faeb2237843e1511d6506e6b79fa574bb067a19150e4215eea60391f9970f64c1fa76f55f4c0fb675c25325459e3c0cbc8d0895477b78e3a111892eb298c9468f788e6b7f25e335f8593dc5e2781adf6adc82b7e972404b863759de9ab46f57b10050f8f0076137308483ddf8df21e40642c09f84fcb418017bd26f235a57bccc349406adf9cd07622dc29780b695948c5d129fea07d7365bda7c07ca9938631806d970d487a7564316f8b19263eb6b62de75bea971703981b33f9c1172677951842408cb8f8bb9434353fb4557a76a15fef4374e3327d7530f377ac83385f1bfe6a1122221552665dca3dbdddcf89825c5b3f774690c6a88d61bc19eb856044b01a0c752c28a7ba4fffccc4894937ce26c4e22ae19fe6f19927ce50c6689ac9d978cd448a2b9f7f147b9daee2cf6a557702a8e2fe265094799562c8a36b6275b40c4d8bc8080bfb90a4f27d03be722491e1b4657fd397e27cff9331343df7a63ca92e95e57dc5afc8d1e6af346720a40be4208fe42a9b9c751836a0087f9ec27568c10d10dc036f7b8aa6b246018f6a69a69c8991186c7dbbca5932cd8d1860f957bc871c21f215d017b8802e93882f6811c2b3049575affe1e7476b4dc19aebe07d83f6715fd01e973b345f5af0d0f190c40d19c2f1498618fd714f8d32397c1d8a25d607845bc3108f72ec28cc04f35792fb90e99f0edd865cd141bfeebb04968b8783219dbf501a6b5e3b21bcc947ad86ba1b6e28467907c77ae718863bf0486ef5770e9d5bc4e8c7a40ad59aca9dbe1bbeb62578355a8bfd54d841ed198653f6b10dd73b731b74e007e5f37f3a7f3311b3db974e3f191c849ba5079676ae6f006e2a5491b0ebb93fdc0fa9d04ddf72a6f852d50f708adfef5e301cdfbbc6b5d5d5b9a1c99e8b8a5d8cc958abbcb65d1e1fd687c9f5ecbee80150d8b31286149103f875b62dcba29b16d856dd45b010c06f82638ca1ab93b9bbc83c9aebf13031098c2707313c140a8a67700a29e57ea8621b647669adfb031992ab3a7962a0d56b726ca0e20a3e788f38630fada9f7a8271d802de83537ef796d13a23397aed2a2b71be45d66d8fdde1e9cf19969e91e219fbb15dc55b2f390cc871c53db21bb9a027cb82179d455c4cfd6dfbc9473296606c540a7eb1b37e4077f8b5a3aaf3b34efe8c592f1121ba40079e0669b99e1408885f0cf332c4753e530888ac410a8c538d9b0791a333573d5c2cbecfa2842b07c4f897ee97ce38b6ecf776ae3ef6595b48f0d8283fc893a9f2e19275aa43ce68094fb02f714718f1794f446be15f6621c016abc39255698ea99d8f5b5c510ca694d004eb689a43835db93adae9338daa8b8111d9076663dde11e9b79d695c77067efe78c1d7e9b2c3c71ef1c4d5ac247cc51efc7792c870e95c20d27ff581bab27e1339535513fb7c29c8aa5cd1da58dcfa019569db708ac29b9f2e068bf397b9f582703760e3673f9fc734c1c1c6023e0dec89dc40c179b59b02ba829aa9c587aac69f4ba9161ec42f072ce2244da31e56685b50f2d23e42b470e56ba68b2dae9c916c8f3faf9da5d23df617ea8830af4809f3e43ae36f0b4ad727e949da6eb4085d22c5bc6231b6a3e63af804f2914ee13d1f83074ab61451bf766fd4b95cbb44812da235387fa6ba01633ad6b623d499460d95d9ee50516840d16d0cd57def9e6fa28fcf94fffbe0701fac1cef37aaacc7d34e28478e82688db909ebaf8bcba75db4fa1d307c57a00fe23560ab91b8b0a5e886728757668077b3eb62aaf42a92954035a9ab950b635b62ef0890e91ed8b0a71e83d6a435380ec71b84e35d2c93aa42e640d34003a19d1c44949f6dbf5b22d3faa9000373f63304ee8e251be46b035e0afcdd42f8520fce279a133d99dd160e8ceb5e1609e097fafec7c3b8de868858840862b7f9255afb5a94a8eef2cbff60e7686411b92d073e401fdb747353c69c3dac0f904229316abf998c08550f7db76e828a3ed95071e914fcb6c1e6c87fb7d619b21060f077a7bc7f3588548d2f610977227c63814b197ec033c65c3b80dbe7fa664fa8639481b413bf5f2d4adc93ec81e9bc538f396f57d5d8de9bec765fd1d177b700b9148a875e16f02c33a7e6f3b81a8356110390a3510c169f02ca3d1c95c28af44ec89b29242396ecdb43d4892d206cc74ae5a5e88e32b2f8cd99936a2629591b64c74d4611551c5cb8b93f2012f3a3ff6243da9c3cc4a8048aa022f886118ecfe4aa80f54876439c9dc0eb38e6b06af4c78fe2a2c46523eb0036d2aaeb9ae434c962d87f44693fa993c9fffdeae99825d60a71d612d30173bd812cb312cee98ebdae3b3337843ad5417f886a62b314dc1f9cc1a8110356a4b4d1debc7cb983e70e3a54476890d01280e8f6025b965fb8ed9c0ecd6d3ee73a911e3baf6b1c680516c113d23410920fd75e9e1d0a0a0891d2e31af57205b0e80a02b9fdc8fda4b29a732bccab52831ef93936bbf64635a16cc2aa9d13895f68f3c67188554e1753e9fc9325f6079ef27d685bef76e62007aeef06eb6d2bd65fe3c5a20cc57ff4ca943706eafc7d4d296b2d0ed941f09892468a7d955ff4ad12b1f42e25a7cc44b51e988375ff68f3a453e1332b18ae7ce30878dfe5fa26ade41a6a1bdb3fc761eaffc6cdb80b2064b717e51145c5db44d8646801c2ee2c56ac4f9a13c6b1a77b56cbb7eb8d8a81681c90ebd5dafa8f784c51a93dc933dd5ad53e3ce4bc1bbc162065c8a91c52483cc4ad5e0e2ec98836273fcfef8090bb2053da1a01168c4f8a1e8818164ff075106ed8eb32d6b55cb0b9efe9f990c62e4263b4900078eb5682a0135704561f9b891296321819b36fac6d48a13f107bd30f20a30f1a3a41357e82dbb78c3d9633411ec5277cd3797102c47d1e2ce12b0209e353e1af54f3f25195f4a18791a7fe427cf3fe7ff1079922a32f172c1ec4566877f840bfe1e355a354b7eb1512603a8f709c52247745c78bf550a93096a188961c7809712d8e6270edf2261a7817eb9c4257d3d41812395598f4f72d591d2ab158ce76905685f086940a95bc49a5e5d0b740fddb633d77a89959a29c54e9f60d9b81e4087ff6260b2df7d7e7beab260a5e2932e9762bc143c7d03ffcffd430afae5b066e8c10e8f63e675ac9b5aa1d19a6521e914a2845f68c55044c19cb2ad3fccdd37678736c913d77b656b7ccf643cec47ccd89ab172823f49291711a8a669fab417a310992fe1dbc17c50ba09abf9c872778fa0e01510d57cc56c95bf68c4323f1fa0aeaabae3d9ab210df2d0f3bea47dfd27579e781e8151927bd86e81bdc222e16be2ef3eb9822f07875212256fc335d007100d1d8899f4815c2070a1f248ff216fad1124f51f0da0070f1c727b018118a1542380c6dd99e3532e91c54f1f826e38403ab4b84c0aeed203d68b4740965dce9f9b3e672ce13216961b6af56ba048beb8e12a271be294cf0cf637c151d7fc50952c0c4ef061a75f0572dd696a550c0c2370febf1bd636cd0fc7cee540941faeef2ce190a647953bcb3fea3e6af205a772db8123dde7c60c2436ff056a7b494c913a1e96898ede0778491a5ff4102d1aef837d7004f6a7e6a73ef59dbaa2b8e680d554e329840c4e158b59e8218a2c33784ad4dc73185e2b4b8817648161848b2317ff5599a8aae38546d062ade89748a23fe5cadd144ec894e0f8985350a07ade91ba13f851aceb7d49a86cce92249d9b8044169abbabd92bfe945a5295072175c27a79de04a5923bde27f2fcaf04cb15c2c09a1fffbbf0decf554604f6a16911e2d71c7a6300965b3b881a989e1ac2a292f1991e9a11ab3450dd15f0ae7e452b330fc378fa29a38e6d3d494d3a439a90a64a90234ee3fa9f33fce57bf3fae77d5e55970ac904ee82b71f2916536ec2cb8c0449266f06189d086cff10600add4d54d2f2677056a198cae0a9a762b5d3235774946c53bf77374a04d8763d7fe618a7537c2a072b7a886fc7fe1fddd4b237fe06d753c5c3c35bd4835ac03352033923dbf2e471d0297a919678a03441857483837fd723daf7986b666895bb477ffefcc28f663969c05484d0d27651c4aa3e28783c87c249312844f37079ff0cd3a3d8c68b1902dda42649e700cb81d0c7cbb0000a6e7b1704a70ceeec7664cccbdf74d89c67a3596e61e94c27433ffaaa40bf1b26e1c9f904900ac4af209da85feab962877a0c2acfe3ca5e971cc8619709c6f707c4e8f838a5077facb76d8ebd8fdcdf73e31467e4b97dc01b0ed426c91c4d5677073599c5ebbee2324942759e7d17f3b822b08718c8460d7077539607cfe209549fda571824b16de8d75ae086b19bb4e8c110d6edc907b70e1e0a0d155ba5f4ecb50985ec23c3db76aecec9ac4020a1edecf64621e147d8066d7ab43744d5fbb33328103af3ce02fc53fd9927c9075acccae39be797948f484718a5a7021eb5642a244c2615d9a3bfb14bad9dbd8848f6f6937f7e649b269c1f30762c5840f708ee5324251ece2c438d3e33b14702e6fdfc30b07cc4bdab2d9de96c801bffcac2db091ef359f2a25c20b20e8f8be408ac43ad3eaa8498a553782309e874a5981aaaff777fec806b00eb10cf34648dc3235a63d22d451410e327a281bdd550cbb4aede94ed4887d29b326f0d67d1e394ce4e917344781fb4c22c4f7198deecbfaa5c2df2b302532491c7456ed470c17d29f0cb1297babf6af43520e95977704318c5aef89fe04253c19244bf67231e6521f51420e5571ac906749ed33724d01283cf8e927d4f8f624369b2a977816ec6e222a1b742299fa79775227b898b998fbe97e9c8832c62394e6eed4d89678675adf23d3f0a5b9faa8c7a9678ead97ff9a95f33d74dc267415808783a57850fb61c11c2d9c338a5a9139d64b432a4ed34731f3d2753aaefba40417a24d6ed577617767a895dd691e592e32af0aa0fcda1a1857725bcf465332bdaed206367f6e4a52b0f57bb0ef975c49b0023888017b80d481e4b9bc396523a4fdb3665448dca2f3bfa6f4ff9502a702866ec8a43958b8360e0c6557f20bd09f0118ebfcb29b044c757928c3d2a87e09a6c6d8f09a3255bbdae3fccbeff8058db518758a523e9ce9908f46012facd87634ce2a210d91bf41aba35bd396d5831986dfc787db63ad962bbefe4d2f8bdecf5ee1a4b4a172b112dbc92777d5537f6e97030948fca20b48f2e006561f4870d841e0d0948ddbbc36d95d46e6caf021ab251a480f809ea15b81c6967b12a49f35c7205d4686bec31c69bc6459e57f062f8a73f5ca7eefed2679dceb82ab3934e0a4409ca964757e90e1a370072831bfb4af06625a9b237fd5253bc74e7d632867e8eb034b2fb6f700e4031c2a8ba9503cff3b4967922c7e97d411efac16c22a02b80fae43eeb30f1746496012a8f0b1c50fa044ce9b44222030beac320be61c74337cd9a9eae4fa9b277cf74c236b50dba285559760793b743e0a4b298b1a96bd9eab67913bbabfaf6e8be0d686d58eecb745607e87c5a908da259d8a5c8d629f40405e9cc26eb94f248ede617e373d78c01bac0b1adfe130c0f1770d4719feec9ecd691bfaf5182d6b98f9a8a71329073f93673c44a76fdd8e1b334245c81daf02ad54be36fd61d30afffbd468ac8cf65a1b1fbb30766eaa4b5b2ebf0cbb26166ef094d43ee4ea1ec989f4f07e129712339c49a80354113e4edc485dbf83b43c6e02bd1d598c5f030c22b76e36d6f000f9d8bdf2358e1ef7a77c6784d72914ba7224092a35df40a27d005abb7fa02e7b1f013840d3fe6eeb6217f487a1fdca0495c8d84a4ff960b1ac39bae351ad5f592512d8655cc96f36327f5c13d4190ca5f9b62a13b2f56831e3cef5480d1d846cc792a4705047a2976d2890a619f24537350a276837735174ad15ee64d0560f29a3bf023e9e5c1d6172c285229d95943e5f519821ab0dee73cb5dcc9ef0416b2194783fa4807209b6c329b565eb5c4ac7bc916b3a712f6078863a7b2f09f17450561d0b2b87e52519842d9df8bb9270d92c75329191d29f0ea5b929165275fb33370ffa53cdc9414b229c3dca72473ebbc0ada4384b02f84e771fca1c31f543b7dfbc0473a286c47953ec9bfc6ce7a5032a7a1b5891308a73b57495ec36cd87ed80f4051724f127bdb4a040c16a5cc77ce841159c13bdc8be2a508532fa76729cedb73e028d3b78e7ad35b945a87a79d66173b436b45dfab166cbdad3822da5966af5d2b23a45d8a26f0217b0773ac8284d293e708d72bb93ee7e953237cb34e63f574a7bab95fddf26f7702b6ec771461ca09dc7e8b17c46a9b7ff817d2929c754d3122bf792b194bf8b5c0476be67df4c2fdfa2c902006f7fd4222b835e007260c3f946845251cad08378d5570c5073cb4db78fedcedeea69e04e41dc9129b67915b5d1fbb84efc8b65e6c38cbe0914d223fa13648f0397680a9606edc48a640f945da4bd7cafc6c9a2cbcb6bc01ef8ec4f240e20d92d661b9251804fd8fa89935bed5657048ee48d7cd01c04ef52dd0ed0d1a524b971c7f71c991ba74bc7796353c5b8bf08977e9c9aa28636261d3bdf7244cc2fc2b55c542dcf6194b131a8b6e62ad958598cc9fa2973f5dfe92945acd0e34c04c83601c5a115bdba95ed549e49c49ffa1dc3f4741676fe2616d5bdaa8eb86db5e6042081b8d24c0a9fa205604d1ef58779a668839c667c3e92b2986d9af21f961e815ace0f32cabf0c6fd0b4262a7bd07d32552902003c2f59086dbf1e62c0ad7fe67ea981a47ccc486476b78fbad5c0f77990a6be84b87a4c52b8472eff67d499bfce27865a3e982e044e8b5e088323b920c516ba38e8a82cbdf0a58362ccd8a869954afdcc904fd1c9fd05012f4474abe1f05797e51813c4ae67ec414ab051be9b0e3fde35c12f01a006e64caf1588250a33d22b58dc7dc839fca4531ae4fcfcda9c444f141d838b691e60bbedb70bb4fdeafa61e2045438aa9dc9e156d6cdd30350b316ad363f35785a0d76b0c71ac3ae26adc4d9b19400009e5a5f2ab40d09c5bbdc2a863977bc172a8492a666715d2861b1da156cc6886dd47cd1e3192f50241ec163ea4e14c102212679dd73fa8308910e4b7352815ad88f494f73533717f8d5d2f669128651e991ecba4a54bc760087fe7b5656627a5b59ba2ff12cdf326c29427256165dfcf1f83d89454b66a6569c9fff21887930c4865aff1753cd127f5c8b62487801b43336169a0b6300ec62b1bdaabe7385ee189da822c2438c31861741aa9985f7853c980b823be57f12137c4ca31a8d8dba0c8351fd76ce1d86a81d4dd9ea6f64238cb6fa745a6342b46b266d0da6acad048b83af91d8244ed76053217e9d11c57c171c63ed4f4fb1ecfe3c193fd89b2417332037cf317b28f3ff1493805ac2083fc7c1bef1e723906477edcc8ba8b1f75fd7b3bb31f2382ff728dd5a138b3b82d4f7585e8e521db7cd1495caca137fbdabfee4b5e62ab93908523b72aedf8c60a8f96594a1a4813cf9259fd1b39c8b610962e8f568129b19134a1acad1ca20a2cf922a394755794e5190b3620fc621f37673936b6682a26971ae56130504d1b3e67960d107c2773fa65035eb809b2bd1a7d23eeece7928289873a593a6060b2ee7dbc3e16ea93387ecf2107262281fc52cd6782e1e981bff475fca40a0900462f5da6d39d3b12fbabf16db9fc2fefade060a883b4ea7905b118f1e9c072600a9a909ff3cebe0d2b93cf43e3c17b3522436adbd466310e3b660f36608028ea1a725f2997c3e23119f0bd4bc7a9f5f6b642530988e2c5f65b3b1cdfa93045352f75990ad1ea5c9a3214095b8b0c22068c3ecbcb59ba9c8a1eef9363a71ab45b6e73cd58fe9cc08d27d0d27d84ef19686ce44aa96397ea12aabd6995497f2f72f4a5725e8f52a77c91a2911b237af676b2bac0830d502e3fab38a365f458e407579f8d9e953d89286731deccbcd77bc7e34b21b4e2bcaad661b64a0bb617fbb330f97be5e2d973a4d04bde5e8521dc2581491d7c670e8c19bb7b340ceefc446c5f95f93812ea532b13e5b3fa8721e2b3e3242697c5ee305b27e1acf04163987f4e3b98ea83f0fa32ec66fcf73122eaaac0100ae2fcbf5044c2493e02c6e3d62d1208fd8bfd9f1985277ce7ebec0d27af786b8b922bd3c6bc79a48541a9ad784958d840c947f708373fbaccb925d94d8f3224c518b1d778cfd59fce39d5644dc981be0bcec2536b7f9ea444671d6a784b5779470d5773cde938d799acb86d41c56f4c5ec7694ffeb37c0ff167f0f6a455b7817c5b56ef27adc8c545791f11c3c7f699f957f0908bdd31cc07404544462ebf5fa330cd153a0463e004f9dab1ef82123f3247eaa2f2dbe230a2e3be8c529403b455096780f305f4815899467134ddc3db2f86e8b5a1ebce982256d96d76b048ab7e7715ce032613281619926f600214b02e7fd56d6d87e3e7dcf6251effebf8a97766f81834f56d635062203b6178c65d8b980e18b894c5010b52146133af4018db134b21b55c7c6dbfb1e103e5d47cb4dacc13061bf38ec82f6faf64c7d485c51fd2e29e6b6fc89fc9a718622e27eb072ee99c277efec5cacc3e939e6a7c6e34ef555969e09cfcc138cb2bad079e53b88265c1ce2f2002a4ae141346b9e27b075e9bd2a948807eff8524895943949dff6ec0eed0283e339302aaeadf5cc2fa47c5afa933c91508b1cc0cd86a3fd377e0a4d774c07c25218a70c7f54b79c1b0a7d8050110526b6dc277cc832a2e4b3147a48f3e0d4b36463420849cd11001ca157c4b90c3d6cb623a683af4ac7d1f7cf045324f653c01fa1f734d3a7cceee22d9a46b6e1382fe26c82a54153d3ec4bcbfad669ce3433a3ff43ba559322de3fe0ac58efbb365d0d5b4e042c4e921feae25bae2bb92ebddfc1bd57086c704ecc1fe700aab722870fd7789900828199f24e53acf4b73ef2df5a84dd00fc493ea891cb7a09ae126314c7d5af7cda804f70f83a145dbc9acaa079a0b1618999b5466913b42157354c7c2f1a661f9b2c766dbafd1fc5c8e5227a878b4dd88b383a4a5661d1d9abfbc04c5f3a31105e990a1801a479df7db039175bc4bf6b23215b71a16ca79eb176e1300aea585599b14bf67812c30bdf7833ff218d289b2af129934a3541e5193ff50972d8775a24613b2bc0861281572b9aadc2ac3fccfdf8baab395a5d7d5fbfe60f02a5fdc6771021cb4b2a6e484a9ed83a3954cc095279a6f8d4faeeb34b64c381fdf2aea4a1ae0840f3275407b7a0bd94b0162d12b5048b258ea59b450d0b3de6237522d627ae40bed30c359abdfbff465a39d4b590328a8838b53e214bff2bdc6c4444dd52e6dcc87de1f57e0a36b5d3467ec685cbf2c76e3a2767fadcfe067b3d57d3f19a3f33c69e546c0f76ffdc9bc0f31dd260daec78eb64d7ab13046e43b978e467316f60a49884e88b60ead357eb3f88f889b2a607b2b2056abb0aa39f10b25e66e0cac28db971a9f5947fde1faa56c602536d27b8b1f79cc5419f0cf4eeb17cedba6b7cad05e764b9bcc8a6040e862f95b463a3f01c59fb7da9cab43f6a604f2d576bf603c5b3c129ae24b637cf4b2b2d8d6ad37c7bb4b42336d9c84c16783a2c8d045a4b4764ddc91f9800c49274b8af13dd753a785c0d8bf98c1a711daf74bdf3c518ec8f44c9678ad457d6fe9be8479d34c9d5079c820ead88a1de80b589c795d16201dc5b090744def395df426095d0261596fa6b41a18022f783e20fdada995ab16d343c159b1d92f08cde56db2aa08c233a4b77acb1c959d97c7aa01818d62e3eff7df5db910d4acea08e35a55ca685bee4bc02dbdbbb5dab121a82cd668a1f8aa4b9373b2eb187f29aceea215fbc6438199a8c176c95eef24c5eb3d3fbba89d6b066b48ab2aeda2441c4a844b5e6f3e7c7a60160b1e9e5d71174f2da2fe513ac30391b105b423010fef13d17eff086a46a9734e81ff35dabd60f14e7a508f4544e96a28a288058c519299c38b852254a319fdd964317435de07a12cfc13eb65fc688e8a87a14d538825a59593638cd3d3ee7c7848206960614d80bc06e5a89216c1f1d907a8df4c2238871c0b7c772e0f200fb82af21485e570269b9354a8c2ebe67d3595577d49ed15877c6b1fee71e4ca59e906c0f674381a6700da43674e48a768a9146d5236cbe10cf580fc04ae24e530f685dab3f39da7a9ce5019728e1a430fcb872c166b184f08579cfedef9e849f1af129b93be52cb620ba58b69f89a7c583c3913d0b55bf3cd6bef3a703c88746a2cd50058191662838dfc9625b81cb0e7a99697ed62332706ed0be0a4059a054f5ccc9ddc3acc796f1f534c16237cb433a99183c114c825a20ee42062d4c70f40677780c5247a43d6d742e7f7b57d7b79d1330d160e47e2db36840295525b0b64a866ebd1698b499b32946ddb1579b9021ca9e326a64f9ba31255479bc67fb64666afefa3a7bf5da90b29d4bf4ec51a995796b0a33272e65341e76b2af1dadb119c49da911ec18a32a04cf92a493e913284a3f7fb4edbc8354e1c787b352cc56b4ec972d318e86f157e622c3850939eadc60c0723a5a313f2d0fb3bff4990876336b9024f827ec5e1d7317645bd34361064fbc40f9781b250cde57451b85c64ba781d5dc3d215e0a129c369ee3b78e501d9e2249bb31b8f6b9974b89cb323c87d2a082749a0b08dadba43c599341849f6e4ccfcea6d629749219a7cb72bb9dd90cee72ca85166df163a28e88a040e94a6e4825f5e39b40267854652934c1598e52c703ab8799387fde020120744661f424f878e65918d3680fcf1187edeb653e1339a7e2d68e9e444304ee6a922b032e19b34a6dd1c9a6371223c7b254609bf0c187462010124c785e3c9cbf658afdf34947518a8a8fe87f7c7e7d571e4e233961a774d120e9e7e63310f5c81c424b064c7454d1b92e35f8c621a99bf967f32946c470621df72273083d8be0faf67714606ed79671cd31c164e22885ec5f5a2615bcf88b7dc858570853f1320e8e74ed635f93383278b4503eab847c466f9ceb8ba2eb982409c0ca2a3460a83cb47b25d5e2c10abca17c69f2f97b89efd59f60043dbccdae0c2beca599adcbfd5806e4b78937b4c49b7b213c5ee4225452a9febb88b35bb8485814008d9229e52dd0cb18594e03e11ead38fbffd7ab8eaa28e5d330038a35d7d2f18df041e25529ec2e0d49a0854bc67f42021634f8b82895eaad19a8fe7ef76210f7f54e675509e90089c7916c2f94cd63fc71677281175a3cc3da1c51688a0eae2ca4e39f78ca05391e32ac88c66b64a772f190bc384ad35f513cdc509295a9ff185947ed53317696bd60c865e8941e2b370ceee68d9559a642beb3a92dc4f6462f43b5c9ab44b7f8dc1a09d29f3b8ba7424c999518515f668714e6cf8de5575cefe682fbe0cff3bdfb9ca559fbef26145fce98afa1dc9c8bada41d5e3f690e8e61999696b8e178d9591684819426246ad0c43c71d4174d50e4072f4fd0e60bf789092e46ad4c3a94da2da4ca38b6c3c78af3cdfba2e67931c28eabbd1b9752afd4b4f4986c286729787a59c8b65e3d766b368cbc32b3587013bb4313b6dae577866f6a4900500eb1610a56d3103b389458f9fc4496ecb6b2afc3f9d109b14ac44fe199bc68e3d20ad9dc84233e53d36621be896ef3817694d5132461d04298433a051f311bc4b0ba2a66b45a320e3e93779c6ee3f5671a3cd3c83f88d557184b4bac7d58eb1bd1e9a51111ca1ab7e0c25fbbf86697176344d33593815fdf77c45ff9c75465d36efd4eb86ffc9fff147406d91477580ffb8c7d61a0ce286b8781f8e394548219cb75232d684f817b6ef9fbc82d21fd5033c4bb86673894b28b17e8b64647efe331072ab43fa50f2193e978ed5a24c3decfbb599479a5a60d1ea41592a163d2670e1f3686a379e3cfa38172c3debacf44e4da69abdaaf5dfb9c1dd2ebb17a199f0f473a5033e3a9c434bcf456f5a4440dc03e4ea4b4242d9f2c1190070351e0cc5c1841b1db4b0b25d61e6ec9f24e054ceb5c0789d9d7c83d47f0278325786bf0d9b0a8bf00ea7d792c8057d0b12cb6153338f1191675e3c0dd4f35cf856289527cdc088378444d2e9948e83652a4e901c67a68455439363708bb83ad92abe8feae644078554b3f1273ef55594722bfae6007e9b68dbdf85446fbf3b633ce1743c737821081cf06daceb673a9b630723d8ad317448e3debec4ff8bcd58fc36f33895adf2f2663229ed7e7652d26ac78d2e01cd2808604397975916dc923442026bcd5b6aeebecdd5bb71182fd7a6fd818764bcc2b6ee9c39b90129fa828a5f137cf11a9d9fe77ec700f0d64c947114eba3b1383bd3605dd928426da574a8c15c58d5228f2ed0a772101c6b5506b8cb7eb7fa641dfcd0c0ac64e69ca12aef38cf4e34484ac528f8c18c60e26eafeedf6df790bc8f1eff83940a7fb7eea48383f2ab3d05ab04c1496ded8fc90ff94622a50e6e6ac85cae5b62e31ba2042862cdb92b6a0ac1bccf5d166ad926a8ec73ca8fab34deee26e5436633bfa38523e69009c771270feabb2df83f9b712a716cfa61ec06f45821438a4d410ee5da4fc56fc7b528061dd9c7b156d8dc2d7f11633d903a32db9942b79a81cd912dc9a96a99ba58a17fc31dfc4af41bc111325f05793708ceb9b7a3fd871f9084abee611482e576facfae6cd64ea7e547ef9902bf238343033994ca95a04cc6280680fdbdae00f903e7cdd3809ba8828a16f95d226d49fc2f6b75412ab241d8704d3f34b7afc10011fec6b8f0d2c2827327029dd0bb20605873e90c617fd1515ef5640823772e1afea19d9dafb690a04025956f057cc42628e3534d718c4035b3ee6f9857955cec7ca81e28842fd3d7050c1b86cdb98311ba3df250d88ffd896877cf7bf688d6626ada13706e936b7e2805203cd9c90348342db7eabf47aaac65f8f65c0e212fbdf665cfe284d8ecff06f7a7b54b261b04c47cd0f02bdaa3409de474355b08525d4f2d74f6b3361c136ebb47a0d33488de52c62d9cfd700558ed958e5f0f7c03f523ef8b6f5b3be2ed00d98ed17d97bbae3b12c937577ab8d17c5577160265ce9cd3594b479cecd996083c5d8472ff9a781ab6828f8f05083084a9864d2c7b284a303c73edcaeefeaabf360ce13642ec92a1cf137d3909f810705ad0e2d0b23b9ecb24b3cb0ecf4efbe18b279d31b3d1918dfc8929b47f3f62d06da11b5e9aff264a8a50b814f9cd6a74127184edc9395fc7a6dbf7158e6f3c8f6f7e6032d19e05da3f4fcece118b5be4303fc98eb9abec1edcb7a44735dafa90681584ecdc8f3c338b7d38f5d8c037e1ca00a46bc111954831731e3e482030603c306fb61aae532fa6a48bd393a1cebc5553ce24083ae7f09f387e0c62fefe8387ce5889e1e301abbed890be7b6c08effc220c4c0e88cdcf254d14459751333057bbdc515c293f1a5bfb6ad017e94fd0d6c572a082c43cb3a4f9c10f053372b3432a754bdab917d13ca649e84299ff0b26117aa0dbb68811c6c258f1e30c39e92969584eb1cf123561b1b9a47fbcfeddfe8aaed66c81526efa0df780a4247925204ca76c9593f50eb1bd8e951897280e5e9edc2262566feb8f0f1e5100ef581255d5e9573864ae8c41d8cfdcb24bd012144d279fed9fda94bb8b51027f122322e0abb5942f2a4d09cca9ad6990249f16a01de92bcc67e6ffb453be8a0eec90f2c0f2363f52f258184e25d42e84390ddc7caabbd8298149943cfc3724738072182e627ddbab669a922f0a0e436bc74dc1dee67274d58eae0e90e4700e95ca085fb6f31364d8d4de550142412a9b9b993f0ba5419a5acb3ed72d6ef2ccda479876fd6fb25a1ad75810bcf1d7ba72386d165fa46e798aa23dff9a59ab3b6e61c3ba7f21604de64c94fe5b7b5a51cfbe8b414cd282e53bb74eabced5c105d0b37b0265c656e9af0f48b716c8838d6a6506661477762ebace206f1f4faf287881db2bb0a9f3f97ffe4cf55972715d57d106067bbc5ae497e2faaae81708136584318f425d0d572894d18f6d6954296b0e146b4bd372238d7209509466b6a7c53c4a6dd61d8a80ea626d9ec76cda79e095f525598082ffb66fdafa5c6d0c7fe63de7eb112ea30a1aa2a22c1633a96d3fc8b45f90d04790b9515f75ae733d13ab2731f4df30f1daed757d38c7c3c246a2449f728050ef4ca083877ba8a615e00d289f59a74a1d95e5073e1846aaa577799e06ad8f19a524f8e3b5a86b9d639caf1ca7c4c4ed066f5e882aa95724b206b8d7d78d29933c49bc778036f974743de3c05f087f3207f5397515a85495c220029af05c11ef603e4ea4b3ca7021a625ff3ab896b80e90dd0f5ef5e4ecf0d516afceccdd30e15b7b2879670211ce947bb36535488afd6f5d4552eeda3ba0459ce3b0bbb479b561b0dee3efb871e03031e2c368bb6686727ff542695d2a9d8da4b9ac9cd50fa9205ee5bab8242daeb60d5e1b38ae5846f07bbc4b3e66ec2977524d5449628683630b2864c651ae5d720ce57cc50c974962597f9f225840540a1c9e7823c4331c5d94f6f1136500c32f69426a88d8792d742c744c80f35c24a59db70eb18edad53b373b1ef4834e01a5b85830f44c60803a8916f5bae001d1c976107cbdcd21d0c1fdda6604f9440e33210ccf1f9bd633dcca08d963d1d76929cca1ed24a36e0abbe93c9e00490a9923c4455df7831137d4b7360aa1f9b957f0e789f937fd19b0352d0b871eba6a0625502f8901e6d5e0d2d64d388e141e825dfe48808caaa877ae5b2251818110a438b87b55ebdaf8d0f7a05cd674f59c4be08aec90c145b2c895020b65d0fb55e56ff36dbc60c9f56f423c7af4499d0a1f71708f9e0da88a33d04619dcfb14cb37298cd3feca9a9eb063b24695d87756d70866bc4a7b79991b9295b437e388b41a285698f14a87868df74d0dbb5be2da33de581b3e977dcd3b21082d47f4156aca5900f1f2ab1d2b7f002a0c6941564a768d79d5b14c6ad23f1edd4fd6cec63886d40986b3bb69279fbb8230704f81bec6cdc473bf66159bdd5e98669e106d0caa0cb2e3bfa2f94e19a83333c55dc5ccabbeaa93d44e9b96fce7e2aca00497a9f1adbcb533cefb602b62ee15999c7e79c3adae3541730d957e3277a63eea13fd1691c56dc7c7d5c7b107c8db529e26895c09e92b16a2913f22d5f18095494605e50a452c9580d0103bb89196e1e1c97f99a5214dd9d5f7d185aece07cdea2a60b44214f2912fbd1ea9f886ed9d8e1f3f10a115363f5fc94fc2b83a5efbfcc1ac9f2dd6fc010dd633df8c2d5cd7be01cd8c3a020c819647528f6b16f282c1a5f83b793cb255c24eacbd6f2da326ddae1fe6fee6c3b9758191a2420a27fdc38fd8c260d7f80244d92219be8b242da8b90422957e1de5db76819b41c936c8f283c238698a2153b9fe4078081604a309f284c03c3aa7640bf1480b77c7add84f74eb2524a8b13967e4c010fee1184a3b16956828f7c0e78e984c8d2c9ccf2018fee64b70f026ad14b2ae1aa4870dcaa67ed6f1d4a16732252565fbfc05d00ce8fb03b19915a0aa8d41073037cdb108091bfa9c13ad65fbdb02f61cc1fe4e7ba3ead608ee4939d7e5410f5a279cc99a45f265e50be22e7ade0eb245ac5b8f9158146ac6110ed99d904310363c0d74159a78cbf1d71b4ebbcf95cb05f1eeb8934e92158915d97a4e33d7f74f5320a1f2737c02b71290b7894f829fce5e40451f1d2a7ad4f7a7057eb264b945c3e9ae1583bf6530ac2976046f78839882e178129909915846ceed5e2f67d146f5027107cb2472697b464e4c6f5d722fccaea303ca5904e4854a717dfc426105a9e01d7e5f197893d8447ef6c3256be85f26d608f1579b86cfe1e729be8a0771e3b34852c3a176915f8e437741280e07e44b9620dc05893f146c4b6f6eaf5490c7e4bafaa5bd340ab2512b37f9acf29d31546eba2cbf5c78f4b482d70065d114eb91135006b0859814b5cbd1077e331860f3055bf232e512f6eb9cdb037fa325e40bc14908523228a7bc1ea10954e102d1d9cfcd90412a571270c6101de3ec48dbe420b01a6a9afaf15e4a34b4a927972ab9eabea98138d27a9fb99d0eb8dc27eddda7ebe8874244d124397f10d053592363b2defa3ddb4d8506ccb30a705bca83bd21578a0f7feb51c418c15c1a7c6df2857126840a3d73eeab99c56fa9032d7b240a9a01a7f98ab7d045531872bc63e09ba95daabfadbc8c19bff1c54a244abee4cf8eef39ad6e8d57973a12eaa82aaf974555023955e198788b682183a711753fd9ddd1c280298a803852329d340af854b49d6cf652487ffc3ed29a11a9fe332cf502caea9f3ddb5a0af5d148f97efc0f7f6fdc209ad9d8880c4d6cd4be92b568e605e2de4484a25931a57d25b2da1b563665b0383a6996abe26f5245015dfd026bc3be1ec236880d2f3fd31e35e0d2332c107a99853effad2031384ad0141296b864d361f08347dd2a29e278e25118c30da210a6e0ec48ec1c06360abcd69ce08666433aac59f986890709516eb9f958c1248f722f1733dd0c03b9768f5c4de244a13fea7080d81da5a09a459d53cb5e63322088f560c73020294ceaa1f913e5b52306ff10cd7505f64e7aaec7b832f9cdfd8f76955d05263168d6509e563fe19e657b71f2cc9989d69eaa836e7a066e22497117390b6e94936e7f4556aa21193cc68136e77a43af034d54c102fd6788da810ad69b6ea3001c38a5f64420955be4305916d539d137c92d08d2c0dfa2e9c8af8bae8d448e07d54007394ad29e525aa15e3bc17dc766060e9d3cb3df53ae3de89617731e5100c58f517bff7e5c4764c44463609fe778cf58eb07b9745c6711fffa860b487af1e59bd126967f4d30f4d807545a1940503211427e5587c5089781c38241128bfaa48ce535deb631509697dae57f9a2d397b23485eb49c0dd422fa52092c0776bb87a41d8107610466d9332c6975d21166fd588cb9df34a753bade3bbe39056eb139d551a72c030c39ffc5a4d687e7d0bca8312a6bc60e7efe1ec239763085d33489dec110600c9c02821f5c44d741757ea02b6f4d68e7a16401b3670761756784d4f0dc574f639d061e8c4e23acec42b16a5bbe33ef678105945f241179fbf8195a96068e7a2adb6bcb0d0e5702e4c4607074f9459556a2277d0af6018214bcc5f96b50fdac7ab939e64e3fa658eecb74e9d00550b3a6ebc024ecaa6bb87617c7fad7dee426a895709580db1631ed48077ad436a429ab7d8bcf425b173529b20fda44f283169f3898dbc217ecfd9aafaf1bdf066e22a09aa76aaa52be09d49443eea6531454f69603eee35534f550613121fc414920a3e9aa012b32d576decd19ca01cbfa489be24a2b926413c00f455aa6eddec2dc4c3984a87bca68d54bb45983578484c429aebd8ff112d150f75c80ddf4220e1a21bb96f3bb1da302021b00ad69d16e035f99a73cbdb94e388fd928299593027d05b21a70688ee82643c880b5357e55effab40b5fdab63c47df67b6fcb6f1738baac489483b9844ce9604e6a709313587ed2b838396ad606413c32cecc3401a1cfcd85b03bc169b5c292feea29e463872e66a434944a70939da067ac553c930bb38fed4794a250d6860e53ae37ff4cf881b4afd7e93cff8c5f66bbc5e77fc575a85781c311619f1578c2d66ffd3fda1291db28775778b05d3ac6748068d94a5c239fad9a4f874ba1acd0c7831b221c34be4ee6898ff934a2066e0dd7668b89990b741886bca1c6973310cabe0bc928b43c557da931fef5a48ffe8bf8afb823fb84f9bfcdc3c0fab1f88b69b18f7cac7bc58bc813448b595bac8aaea9a4e90fd0c3e468cc8dfa5680af016de138240a8b2561be6640e616172c1ce5b0388a41f1befc5c02ebe7eb8c5f64f8eafed00b15bd9a9790e21a9b5d33301cd7a46ae1065ea179514e2498f4425b7fb3aa30230f78cd452e0e777d97013757479b3f160c19448e3cf4d4444989c08718d91d3a5943e739d2ab10048abc141b573d205d53dcea0a44761a32cf38223cc47a0f13df83a67aed543bb8cccaa2a8012909728d6aadfeb912a05367d844ce1102a539559f1e0b5502150bad7a2b07a0b35ac894643b66a7e213bdb8c49ca92be32183733838a19db6fc09322ff2dea7ce5893ff9455ca9c10a41e6b13d575cda5e91ecf5a2eacc74a13233211ab237b22ab5eba1b512e88e4885184af4de9538b0837b67411abf518c1f9ac4efe8e92a2df362d9c994eea2179f43aea986b04b7aa20afa743d3263e324bdb4607eb8c3de361ad9e2a2f05c73418d013fb87b6d9a31c7daafd063091d4e2a69dd92ced528ca73dec16faddb4f9fb41d0f22f499de9a7cc31ca1b6d3adc9e7c15f65e9147132adfc9022e2c290edae9c3aacd8d6f165ce9960a71e40cbf84aa3ad85b2ce993cef49ea91c903c3cc456e4872693d7e75f1f3e71d6d65eb9e5bb5f6c584c508c357bc057b9aac85090f731a00d7a33bf67aa1085c2f52449938526923eb897634317d16eb7f0329bd465df3ee63d982546543823d9b980b323d4d59ed72d0fdf383d64e4f8529a73f463f361f61a90410c252dfac193c77d46ee07dac6ca86ae5bf258c34c16cfb03ce0423b897c9f63bac60f0e4ff145f2285dd20254aaefde2eebaea97d744d30108f38b2e19ccb7c807e3d37f350a6a2eaac63c60582d5643b086f46b0e02c5324ee3fe2fc40d9a48edd049f88ddf1e02117384b0c47249868739ee01109977fcdc2d199243a9d92758b6448674c9543704972ab7b2644b7e4df19d1d6efe29d5b6510da5197832717ccc784cbf7b12527ba9970565ad5609daea40889b5e19a866b9a5b5fb065e326a3c67b8c6f4827fc3cfc1d68a53c1848f96d84a6ee43e7ea83ba060c75e26e3ab709a822fa76abcdc934a5ef7fc0c6fac5cbdd8f9e3ffa9594637bcda9af116a56945cfebf1b5877a7e5f2193f2e3754e5f2bbc631c8cba26e788d72fcc53a813ee13a2e9ba79ab66c885f4cc1dd9b343d005c7c11bb58e6a8b6c1951addfcbe0a71b955c5d3201f89523e3f6971060682be1270aa5c21ac733cd2df33688c2669a3a6f3dd395c1f489154e871dff094a031c31d0163ac8277092f1a9226c55f79f69ff802830615808c5835e528b6785a17683c2d940218110b7b113167a527f71198adf5ff9a715ff37796bfa1160cd73a8e8c56b030267c3c0d488051ce5012565aea8501d5ee0913d3580ad5b7f52aafd8f60cf8c86fbf236e78b8e006541600ff9bbd353cd3f79affd0f6de9f4018c37afa15215dcc6b7cdde76ec2b7704359789b86a39546497f8555448ebb0489c6c06b09f50a458031ce8850f4705d7bce07d4c49b6bb67f9d60097bbd5a9f868885825726ec4005e066ed2ed06288a341c5b91e1c8e4a4f248276c51bb27bafcabc389221a3c130fc010ac930e3e7de5f1d10eeac91697a634ff8720c5a6b6dc5ebf813c2d5af255badc7f97cee5a014a4f0013b94b4de99420f66c5bce8e6e3eab9c5ddaab37a053ac6636b2a299de66ee7fa3bb56a76f64c2ed2844efcd22a129bbc571533521e0f6b086fabfb556c96207b1649355f277f44de1af80e03fffa84753727cb16a2db4fc60f52c2b422e67989e03a426dffded201f9faae68dddfd7fdd42dd6d5fc8c6fe139e73809f388d14c11a71ae2854d386de6979917e37122bcb72ae81972e3f6da8a54377a43cb6e343fda42a382e4400d8e05cdb503263270b2e2c063cdde3716429eecc949c9aea8bbd2d77873cd188b861a950c38e844920a91ad3fb19b0ec5aeae88a807446d1927fbe12d53c7e81529f16e89606998a5a163b4b2b9a5286b08d4ee74b149899f4774f332486446b1136416b6469dc8697fed93861dcad9280f85cb5a291c585a97d9beafa9790713a242a6f867a1a4c240941ba228157c38b8df665b98dd44ea619a25a90f0c1c4c3e4b4dc194836789a57453e702f1aaa1aee2098fd2421abdc6c25fadc71adc4cc3a270957f1a5a7d29bb890459a770bbee2863918e1db24561a2de9bee91cfb00b3b15ec598f13346f7f5f65e0c1e01d73bf536e36f854063a08555a4e54ce66f7fe0940b8d37427a84595cd57778ec10cde0bc34d479f318313bef6dd66c93a39929795e89a8214e24c58e63671d393722dc07a2bf606a9627236d7bad210900066547d04c0bfa46ca1d8b66575eef102f24c59bc6bb1fcdac542114880382b5aa4f9202ae39ca320ccbd9eaecbc162c071503862a5e42c5fba99d9db22177cf6b07bcfcbea0b6bc2a3cfb58396ee8c29db3b1ad07ef6b98fa41685249797a2a2591fe288bc1a7cc2162e492b9e0d124a923b33dcf6ba79c8affbf6a0081e15aaa5704ba77b338cc90308b94072e474d082f7c9517a2c779070a71e929a13fce2ba310fb549df52a742183e945ad659274c544a3f7b60d23ea4eb97ee3b6b0b13f26b84cbd3feabbbab2a98924636695dc78c82354a01fef69fa3c337391f366547429d0746a80c6bae4e2eefe8ae0de80fe96e9bfab4e4843000bcd3d765492818505031b83aee46bbbe575fb11dcd0cdb0f48dd2f72daf8099b1206e9e670b8436c8cda505760814c1185c441bb4d8f90d4b2d23f7780940be62c058b5933a30c6c0c6cbfcb142c7837eec6a7dc90aa237291167f34977f3b028bbe35c6cadcd5a9aabb98a3525b313b2adc5440592fa74d4698e87a0288a702a5ebea8dceb9d7974532d653adaaf84f3a2222d2b9dabae68791b2238ed4247002a01a121d847908b9b56571f4bfa7244f2762cfcb31ef488d25a88ae78a740186ee2c49423006f7110adbd76f04b7794814de6289ed3f79f8c9551fa9454a92a3c888246a20ec9d5553f096525beb7fd5c963614464f8db08fd06831bdc096bea5a4bf6887bfb8de8edf404f18085511f820ac65415a32b6592e8c9fda275ebe75755389d4e6381149f67dd9b2affb339c7b096ce98f8295df92d22c1e201c1cd8f48c9c85dba8c9abb36c3513b7570a962ac44f2db2c09c6bbb211430212f86a184abd743374c44eeccd0c0702998f5f329881588d153949b96b4e92f74ba2b2922625ba9b6cd3556f2fdf385abf59fe774b370c41b435fe04a9cf13e982591e63c5cae5f4db7f340b05ea0d9dc26991cded965780fc02b21043cc68e249a74459eb507a3e0f92cf97bf07e81e8e90296dc40bd62e59c336453cb4e2e9689c2616d7e69f42169325b43c50e381d7e4de5106145143ccc0c9fa5d5050f45249a9d3d3b8b04b33ecf89f483c3dfc52c04cc2e6de58e75ea891019bb8ffcf0e36143436494211394b13519e283cdc4baa99aa7c67fc4c1b62ac6c22ea180f402eca53de7a6b1d5eb6239cf327874a5f6a4dee981a41f84911c2f48b835ed30feaa7062a4398448a8d08b1ed2284df1323d314beb13809d00cbde8a331607341cf6199af04204009364cbeabaec1196c2a639b9011155272f00e1064faf0aede25da9a9133395b658ade384f2a7aa147f1f341ff683bf576b95416e113805849c10447acabea48b06978f2f19da6101471b9005b1ece599a7b3a9fe4e28bc8bd84e318bc5793bf69333babfdffc3c177a8077514c25e460f8a3340834ff9cd3eaef48bf2a4116ae551300e08b3292c17ff4503296f400df1930de1ec2596f9469f6fd22107692658a2dd364754435883d388f3b92701396e8922c7a4b59c38fb03477fc0508f76dd158038d8df1d53ba2945f61f63042a9e7f8f718f85956ff5e016873894fe446af53ad00d38c69192057090fad5186f3bc90715942f9e7ded6fb9a6291b8f4a80c5dd2dedcebb95c2b5e10d656459c29b12bccd77b6c3930c0e06071053dd8d2b35c1a3054a573e3bcb56c45c2774cb9346e289046273156866e556741af74bfd59b82a3769f4d93d143695079c035ab5890a6721bd25c89d8592cc998d4d472860dfb8f96ee232fea84fb4c36a5838753aebfd2c5904bb529527831cc4ff0ba3f2734d4980320288e58a6f9676c3a21fea8485de3987ac32f169e84985b96985b491f54752a48c1c4dd392baeff02375896c08a6a18ce7567986513265505049ab7e381bd58108d9a5f02bb629ea00b03c2ccd5216cd0778e33dbfb9916f9e7a0214a96104cc6e5a251380e0f607a73c63d045823a6313b29ef729c85213afd77bbe0daa4f8f282705cc317f186e25709026dd161644189fed39fb97299f7a709e659aec2470bb71d7aa2057da2055c5c9e4c891ef75c709950510a5c0e7617a767e7b949260a32c7228be3d981ab22f0792ee971d78b59c8ad083c3617ddaf853494242ae3cd5e0f89915c5ff633af1ca4412dc66dc49f1f93782362bc2d353bc7de6feadd529299e6eb6d1e66797fd93470075d2893f7759be0ba3ab2ab454509160f2385254d8fd8224d592e8347473dd29ccae11d5365f996c38b63437184cb3ab903a5a5c01fcc40fc06d631ab26485ed77397bdb3ce2b725c15ccb6a2bcce67ebcb05f5a2f87ad2064f104fed5e6af99f1570a88754c204a30c239b9e145c0210515aab1dd177caaa158fab4e77e34837401266c7dd31dd819a557113996d35457e7ea94ce3ca994068a534c3214ee78dd64a080414b93b225a516315ba6679cbd105a653699e0f0e10ba2bb6ae60df2e900362fce954f013df1e70e7cd5c2abbdb194d8a27fbd1cf078ae9d1933d8e097989b61c0ec552222bcc10a389ef6ebec1a80da0e094812dfd3ad722eb534f2a71239b817ca086f93827a0a68ab1f132bde3ede1dfa6dafcd33d8778f4cf933babf804b4ec797d3373480fb2d1202f459d0d01fe6fe064b02001189b8f9b477b2f47bd595f1469203bedb040412f5a4d439958b96361848eaf734ac6c4eea5aa59eab58297a78fa267a225fe235b19f423988a0e8fc6fd55ff56f6de3c70df605831f8109b34b4c1bdbeb22ba3579e6bc87827144c463fc5c261c602c5e53313b00528f637ea842e7c2d8e6a5045da574f15b9dbf8dc00125e326665d2746117b861d5a67999c1cad308e9a6a07ad0d6ea11253374890dbf6f118ce9f8728bdf1d171631363a0c6f6704c3ce6adc49785e8b8cd3a49947664384888773c6fab05de521d9837f9593d1a7d0966ac66ada85150312f3063afd5caf455e9fd659214de0eafb3c7c17351e59fecec083ed0ecd09b797cce2c8d2f288e03e7e0c4eb89426ec3c8057d04b4d375aaf715703e3b825650022d3d2046fdb459e39e3902d2de8a8b132116f6ed4c46e15e5141ea25a79317fe99ed574f1177d1b9788eadde9bc95d27a5624c71c6258e1674824f2dea53fd39acae752cbf88a34870db03beb4a66764266495500981f81a8efd1f5016ca4e6b344992e04f415579c26a7312a3df901ba054f92523222403dc247f8958a8a6b35228d1dd67d36358c7c71a9f2895c44d584e6f4572a951f1c0a0b8300eda4ac246bdeef301b1d2ab18ce1a86309a42bcad29679466169d4230cf8e5ba29488f0bf7a19a354478858819dbc3f15b8c9c93a22f4ccc2a1d069100e9e6d350c576755917f9bb16c4c7342ba1f2e220243c719fcce98aa744118b9b13856a0ae05175de5ac5cdd8ed34fb9fb336d37cf5e766b15a9796efd25f98255f7a37057a9c1f97aa312a4bf5a5c84c9eda34f069eeb951d1d4f60c6ee02624356b02b5cd880c7690dbc3cb752da8ad5b83c77d62d676ef918928c6574503928e832961d2d4da7b83ea404841f64d262dbc25213eaaeb639f029d38b57ae4a73f961e83a309aaf3d10b7c0077be8ed20a203995c8472d779f7f39433f33e134c4b1e7740504a1bfe6a50519c074e5e60cc6c26acb2b9174d8f7d5c0a581f7860e849d6f61ae67b53b0d9dfd9bc8efa07020d56e3b8d33cf9cf4bd922f3485e31ad5edd9d2c5ab0a8bbdbb93e6f1f0c71a105a613c6172d6d6da337b610fcf8f49d69ae61f5d9cd1119a0b3d028f4cbdb9888afb740e727aa6c1eac7b5137f69c102f982b7917185f5ffeaa94f3c0f1de9c377466d2b982043008bccaeb11f0f5dd373950e90e3a61e06eb7bdd7266eef8ab8441f1a0128893f1332dce2e9f98e496068cf695ae681a9e54b8794f24643ad9e9d8b1ee4dbe2d08f3f83a85d5df9555c53ac71a146cd569a10a8f1e3e2df436302c9e1d7d23b1314f9420b7f8583dc5530a76a6598d410200d777ec4f556027bb24af20187facb4c07310e6257b61f96c76f42948c0381bd41c31e515b555c83c5c1f025b24822832a6f07ddbb842a95ddc6fdcacfc2e6933299701f2f27ecc3a534940c426fce2862f416a7e08b29b87994cbad83a856d6819965b6c13413793f7a52a969e823b1133c29bd7fcff06e079f6c320808f56712df885ad99b32bf47aa5e458fd25501ceec41b926d86797176e0035efb9ff081eabb6f03384ce963795cf15672ef8c6282b189beaf8e2dd9c34d346e31a93d800d1cae61102003b66f3fd01e5f291295908b26515e9681c830be96b23949a9864db383dd84dcd81982664005dc913ad8a27f81a2b234ac85d417308e0a50f763ee659e3098d95a8c921335a04ca9ee4667c36fed6c22a9690de825edd512fb1b23fb5039dee72c63a774634b048f266aba5239d34c26cb62cf556aba751ede624090007afcbb4dc4a1dc96a5fa1df8c58153cbd719e85471429f3d68e81a3d24bf4f9999ba19832c44eca512903bd34c562a50e13becf82cb13c4d6bddf1a9000f1621e1a220edeb8865f838628a1ae7fe89c4bfe339273dc70262bd518a32c738eee9be325c4da348a30a8230f31af3ba07009fc36d9de01fde9f5a3cab6302519079459b79c0cd30ef40a0d0b580c15cfe2e97573bc469240253c44ad7df6aba4bf6a911c5e3340a30aa0533f4cc1534caea1ebe9e03bc3aa5277fad330a6e3807cdc7afd0a1774faa066877e9faa7c8a7f49020415dd343a47296f4cb75b0d1404f9be4e62b8b13cd8444113f78a564bf16adabd14a3c6aa7e214fcc4c18882373bc9742af83738452b57fc31d0306f27d61a1855149840a28a8ce8504b80bfa87b150b86f697f4d0d426017aedda96e76fdec2ad901e30c9b7923376a6d6c7cbefeb58cd909aab8df7780925a32ecc4612e755de881537566e40c8fb1efdc774b7d8c6b1748412ba2ed784d840280cee628300c9c45df7b6b9b22f0c9b1ccc89520eb56c986da726a312dd2a53b9faa929e73f6d1ee714fe358acd61198d8233dee44b4165bf77cefbbefa0655454dd566ac37e6d638e4b1596c2c0ead5fce73dd54dc3775bb365275b0d94b032405b6334c9aabceeb685210835a2e49d5290c467cd0b4dbf5a34f1c842d1a38247a578aa921d7302ca43027fb551ee7e5d8ddfd1e2ccd82d441cfeb1a1682af0535ab79879770973d3371e78589fd91baeb2d901f9d7ac1135c42be13e482dbfc99fa590d6e02adf43d54f9d65896aa331177a50b1c11f1435ecb9458ed8ef93163f34c49e8f8d53bb51c5627bdfa466139b3b3901c9872477acab5947105f040ebd8b3a9d28d7a8fe1309fb7f167e90d084770fe4de946d69ee888b58e5416bc47a44d82cb422082414eaa443043ea2f17f9c7ceebb616fcbd1ae46f45b5eea8331d5e8c1b2ca03ab8ea73967d41d978099f84189edb49996357b2b98db304fcc93596631e8e6def0b1df2f5850ccac96c6a531bea9597e09bf8794d7bf49726103375083b8f0e70b4a673ad89f6f8ba6cff91b932914bda29044e2c72ec7686506c815f6c009f9ff9f84f91598f741455bf23f848de55e10dd422186b7771f044ab06af597e73f0614fae75632c8157e4f093828c3d1e255cd953194885172e1177ff25ff6cb9ef703913b3ceb53b356eb0f4259c5cfd2a637365f9384c0c7c0dd46d50e68f3eb7b52d35a7a84a9915ced13d5496e930482215b3c82fb3120a6142afa6ee40617b29005233218e4191291e6aa5e99d192a1033c75865e61b7e6c0547fea5878800de55e03cd7afe9f882973451b5a2945031b38fc1ed470d6f04fe301531936921fc6f012b3581c6cd1bac22b8b9f04b8d7ed97edf5c2ea7eb7ba10e09c458ce4a12e8886cdd296446a7837bdad88deb899beaedcf7a23f2f27ed766e0aa5887feb237c9f2264db2cd4cd0e7de26ca433fddfec3760895dcf8bcc8c002e2cbb052632595b1a9b25b837acdb88f8ade8280a0bf78fa93293f6763e898bb3ebc796fd9700f23dbc390b8875640daed8eda6ff00808465d2b521a68c20ebb27c929138b58f3c4ba704474273f3751feb118047da9508a4abbbae4a358b195dd9a38118cd2f9968e4d7bf2aa57eaab7dc0f389e1baa5b9ecbdba07bd18ff435346e8ccbc92e09eaffbb41f14c7c94122ce0e1f3b4c0f0a94b4babde1b405a09623fac048386165662fa81ff8a40193078e023a0f59d47874f458b352690dedc8d8c3fa743c1877d7e481c931fdf5c1ae440b2182466d42daed6589bd78624da79b209bbe7cf4e7ff95982664a7d7138fc9556fdd60eb6b6e75b95367e91e48a3eb7858497a49cac1830cf64c9c0a7236fa47956613373b8b41991b27cd4d4c8b4661fd9da807b700641e2ae7b2c1ecf9408b5585d443bca00d7d5c309ee027c3991ee5b865ef4ece061e6b5def0634361112483a007e3ea9155989ad7ab68e0c72b5846875ccaea6c4b292cccd621f207bcb5ae51a44d3addb3b842a49649adf2b07defb19e9fcbcc7e72ed632782825393a495c3632d35a69f827cf0f844653a6e60d4124f1e35782d60103ec35ecdbd8ae295173635aa47fcabf690811f2953afcbed3946e0f3ee40183f5e9b1af84e176e208d992c57a665e8b493bbde3f5f9c6b4bba8e2b13221755e6c9e5dd6191aedb061fd31398f4e6a41e9aa3cc0797407445b6b3ffe811a81f01428066bf050f9a32937211c2877138a695a3917098a66d8ad272fb09dbd83cc2d36fe9de846381dc554b29a058aad81dbb802d62408396cd6522883c9e97c39f9cde23c2ea0b6562511042156dffe395b3b12d22f16d714fec9c5b9d7ae54b1ff5ba74189fce3db0ec191e5ba9b1834fa2f2e4347b21c74827d3c97f5f69e6422bbf8443e99250bb0f2075d5cdafdec79134b46abc04e4674e310e687ed58b37d1b92047f1e3a9987f90bc40e55c94dd6f34a5ab76fced865cb5f7217b594b51bf3d1e36a7a720d9ba0a999863cbd39a0a961e36af2ba8b896d278e4d3e48b57662b8679feb9d43fd7c871f9d32ad0328f233ab81d3df5520cffe8f00527cb1cbb67bbb589d69eaa49ad7dea4987ef477b4be5c1c63144038d6924c5020e9dd3cf40a2b2277de05937d3720cea30fca1efbb207f9007fe9baa611a5d2e6a51ec49129ac16029a6e49b157759be9b134ca6c3aa03b450502cc2266f9cf7ff190f5d04a51b185594e9160502f66568957b14d077fc64990d077a8c6ae8fec4101925bf6d72dbe70a791affde7f2bf8c4ccf532ae02296b2adb166a3acb42cb973e9104b1a79cecfca35d29bf61586999fedd76764f96fab87a2e371e2a9f4d59e0d2d289eb14f4ee1e31820fd67f307d27e2dad8e1a92b95f478e2cc01e15b76bd433a00738a00b66836d795d0a5072d8482bd647178c2dcc5f759ebc93a5907aaa499ca14fd4d6aad3faf7c07bd8ec9adf2e2a5b694cb275ac7dbccdf643029d35e401d94eeaba4f12f8988e7c0ac175ef24d7fa2b97d09a7db15ef34ffbd55bc7fc3fc15f24fecef1d2d87903fadaba57603837646bc88c9183a5ad7da34f6e9f2ee490d0a91df3df31fbf241a07b2d154ae68cd9987931431f388101cc9c45fe6abb20f4d1905c9c886db1b8566f87fc3363557e083b8108205278dc832583072d0d6f92ba69fd1e3906315cf0fbf544d8b85068792e0b414a489e7b90767e7457b31ee90b3b9ece7a6731c922f696e24f812359b4d471541fc586b4e53dc016262cf53b0c055b0782ba348931f200b189a5d6d67764e720677fc5d1705a8a2add465df5644e23f1a14c02670d50bb6d8c9255d49eacb8b9089673af88504c7920b7e4563bf73ba6625525d359f7c4820dc6efe94318c175cc0dade7948c2e4c1caebdb97ec7b8a9c5516d580af40ca1265ada1de3c1f44de126150fbebd8b2eea933d5f8e3308ae32ee6c208ac8f72a34a7ac3cbc60d78b3f615001b93a7080db1e5fd198546aabc66dfb879f36de6e262ae3c1f74102c84210873dffd222d9935141d3569c7146ffdab3c37d748db1cf9c5f50506da538154fe75206a795391d5ca5e1035e8586fc127594b63fc508f994e3ba428bff4fc600215d48a2f50e60f77143b6e061dcd2e9e30bfe03bb05480e979c0621e8aa2648ea902ffa2105a9952faa78519678a73be460f257972f3ffa8981d13c484fc38c7cd836a52ba926ae8e62c79507f290bb24d45966f74de350279f26bb9d3a9ab31480d115d9db887cdf1453b16b3c76b691a488766983cf23c61c35e47a4062ab72f2eba427142cc998152b2ccfee65cddf13e1aaf437c2e41fec863e023980cd5c851700c3717ab1d545b1a811da1fe0057b9f3890d3ec67cc31821a717f03de1b56b39f909f6cf3d283f9a266efcafdbe6e64d0a9489029cf3d7f8894c043a6fb35a4c1248654f272ab7625d476113709f42eaba06eb0050c1475214b19fa3ed0a66da549ce5b86bb261719b9951d9aaac4b8b34a2ce8dfcb847e480e85ef1f6ce7bdce7f3360ab580e33b942253076a7a366906f72a8b037d5b951659c9af42a10bfe379a6174f16daed9df5d8cb41cbc13ee64c066c0ce1f744496fa34606663fbf549c3fa6d167a1edc128b5ae2326329709111caa803d5558eb5ce89dc502d6015b83011047e2eb89e287d5132c8a6e6e7564c191112324c2bcf371b19ee3c3cb047d6b302bc011d49b9283f7c010ec9fcedd17d8ba505244190dfed7cb72e4fcf40ddc2105612c1bac1c9d579d91bf66ec885f2315e3fc967b1850fe4c1c6bc83469c33e6ac596cddccd4aaa082e52e05b66d1dc28e078113994cf4282d25f5ef6fca526ec14fed928a1060599c9258118ec497306b0eb3b699976f6298a2b986fe5ee70c01702f2da68f2ade789a9adbf82c358f39f73cbb750105dabbc34fa037692f75bbc655088447e111d5a1ee95905a144da5de0ca6d76c7c6a8e58b9ccdb580e3ce95cfe4fe2d5f561e6b8b4cf045c884347efbf114dc04ddcc08a8cc34dee7e469591a077870d50974aec23789888c67d1fa606db6e99d3ca828337aaede464118623514a32c439eb65b756ed3056908b8ade798dc1653b73dcee6c0e9da4fa1e52e959665d412b66a6d5813bb6a8b09dfd8996e2d719ed68c897821ada69ac859df33dc80b0ed74bc23593360572e049d9e3daca91bd279036a963b644f27a1afbb211ca75b42517681e1d26ae1c3b983886df15aca86a94084f31ae792b04470aca58dec461b20e5cc2caf8410f04fcf02415881485afafb7264695462ceeda5c0ed12eb3445551812ac25d5605321b2db844a1ff1fd1836d10b18f066e8faadaa4f5961bcc5b112a07af3b74425ed8cc983951839955910c19651d878f86cac3f199190cba1da0c8ae2be8b9e9e6f92e35efef7246464ca6889c70a271d8cc6fbf01e1e1ff491e026f19c614934ad6c25f6d8d4cccd596e6f9baca436fc9b957ac6e8c8cc538c10911b058b90a17a39ef6063c0dfa207a58324ee7f1bf5c9b5ff5220d4bb8a1ca628f3e47fb3c20b9b86f6e7981fc00905d5987e508123bff0f029ada3269c2f4b10eac55a693f93a892e6e71ee0fceff09b15c32cc6318046575998a81e1b539cb027373559053d429f61d0ae8d69c3c18dfd75fc7dfb8402639a1b6605d1bfab326ded02a0920dab9c51fc0f23de848c68a590a1eeff311ee511360a14b4a213050a4b264e1070525ea20460b3d87e4a6e100ecf91a41d6dd4a0e8ced62d1ec35de041dde83c7d661e1aa13fe5ab848de4bc98699747cb72c9c47b1babb1bde6b279c1d1e826451ff7c19b0c70fd7832938075fe6122c64c8308712746a355ea3d2b238d38b7a5b269de9822a394b5b548753d5506fc45cf650402b3308d89977e4a464abb0b7887d977b5d2df782ee655f53861d2abc7cdf3ee4e4343a772c1b9714ea6c5a11e8e0e733e001b606ab50da86ddfc16b438c59bd9e322fad20c373d435fc9d50be9bb63902175f6daacb4b9eb1be08fbea80e4d352cff804ef53faa3a5cb0214ac44dff2fe5d493fdb58ec7e55078d9099e1864670b67635e8d9db83c667a3a6d9f97e4b6f7f8b4e295450c337586a9d5ff356a9e090d3acb16f6a0efb4ea6ec4a1cebce777d6a9c52fddd0353376e5fd4b50e71cc5c11d7b9483d69b6f8222ede5063cff63041b10ffc7c5419649b43174f8be3b0768284a2fbb1e9e09aa421f19d85de38defcea2deb1ab12e12a918ab90519813a84ea0218064c97c64aec6acc3d6d593619e1b3576297a5473821195080589f59bf036d25ab9ea7cef331002cf3d6f2af4bb77b2711c01983fc4976955f1b1673bb9ccaf8657adff371d5fa11e876d5a8ffed4827fee2416fb42ee30df8342e39e4c043629aad25f3bc8cc76f0e4549783a538b6e19cfee59083a34daacd5d477320955e036172cd7d8445751568b3b3ed00b6e617a69dfc7a42d4ceb4076b9eeb0292f7deeefe60191673ba988bf9da53f59c1cc492d41b17ec3dd83109f60c9b90ff0bed095adaa5377755feaae2bdd97ff8991d69151ef925210a02d21f8cdcbeba58272df97d3c14763a3d836db3292a5d27e9385e310343f412fb049f51c055af074c1a1ac217ccf02cbfbd64d0669907bc879135d3ccd1e5d4eb645007d16f5e2bf85107bed2f5efaa805e77373a014b9b2d07b79f690e2fc9cd5f573465fdb4b4ba9b6c8d1818fe22e253e12354ab4f41bdb6af45f16c368b3f80ce799b4132ddb683bb19a830f2fedaeba370877521b912b8ca31421182b537fb078592545e3f92d79ffbec2f8a4d065c4e9efc520e36c5b0cdc258597a7ada265ed9e7a19017f82b8466127ad514703d5699d7c6540a8644e5183d52fca27ffa93d30a680667f055265061686f97210a00c3116a22554d9b01d2f93ebcdc4196c5887b3c49f3a6f23b46efc689162aa4d7fe376ee20176e3424546157882e3bce8629f85e55b03e026a41f56c7da48ceb8c63212dcf2c3cd750f8f00fa477053dd9c6fff70c0a0c2b91a3d4be68d6cb167873c2c2171100fa002d0dfea73c6e30d00e839e1b048800cbc7dce3a2ff359b9c7b0a5cc3b02e19cca42c474911d1ca085c618326212c460fd177df92cf5354cdc6cfc90d0c781c9d83d76b5c3b34f35bdde515c61ac7d1661abe6516ced9bd02911c196c743afc168bfef002506f33a1bfef29b3e2460b0ba2311594abf41e137901bab5c7c312cb008def3db5b22cb9020d906ebb462e83e6af3c3df72c56b161f43be7f6850978b5d2656d0f6191d0f7f3b73c2c13fd4d1abfc7111ff27a9d7c807b206665a653a755dcd29f0bb85d8d31fae7b29f71945b88ac737204aa6209d4878dabd1ec9fb684e5f02a20f08c10ae10179894b34780273d86689765c12b07c81399c73d29ef577c4f682be75c075af0c33df44c78c158062a1b5d9590de82c7b69ccb18a57b58c8a7e83961f78880fe7b732ad04a1e7ec97803d01fa11e4775e725835216b8ff206365009168e0f579a789a9420c24ee9b91add632ebf1b8ecebce6e22785fb45198457aef9ffd234e7def5cdd508011b833245d8d631b836c7ad509bdfc7ded2bd4514bf5bcf94245c65316ffa11f51b62f9691baa468b499b9976ca94e9b81e7069ea9aaea967fcbb2bc4542445acf583ee404cae00f93543a8c6dc0bf18f5d5da4d541c66d6b62b4415c9d5d9becaaaea4c66f41169d7bfb8445c3fd6289fb66ab45d9e8b01753e4215c2de9d7b3afeb78b4eeb1e6f43133f32873a4f26717f599c62e6fa55e9f3217e1291765ba6c02848513675a5c81caa23d655707350399707f2ef5041d687486877e2947b0bfc250498f996f3a28a1dd621638a935c67fd5236a1ae07da76356075b48d77a761f214af69b7550f774e85f89eea6ea9ab480b198d27533a7e5ef0fb286381ed818ccbb41b338bd56103138c17506446e33fe34134674a888a19080bcf6b8a91d777e7f4a7fa06bbcc8a3ec48f8126064cbafdc59424597bde6e93672ce9072f154e88753f38252384dc249acbc992654d138d48c3a48c78ee34533149cb124f579a19080dead21ebc455fbfc2128d148a2bdbdf2d71b0a972badbea68a97b53e85ceaa580469b68c3dd69f4fac5cf9fef5a021761a59f5478ec6946eb64e11d4e7509848c5f2f7e269e1fe3d219d166901a9d82409623f623d6bd078911e6e34af0b0e2057b98a4eb05ad764b4c36c3069b647b0f225188043297f12588d8fcf1f07bd938d521f3e890ef3e443954d2e04773ff03dfd508c4a2e4d5095ade5891f7d71bb327400bb2df34ee3a41237f06dbe755dcafea845b254b089b7e7613b7018ae94ef6ee9dc9560300b00f7c83a696ed997e0701dc0f64c56d8b4b10e31baf0e87dc4ed1c50c7d9f0073ef5c382121975deb237b0e4661d5f9cbfe6f3ac660df7ba11fc29d97341230984864574e2c62f996d63b0ec2a721af80ab195604a7942f42f94cbcbe7937647c05b3b6c808a7ed3d73f33e99a7793549f3768f81f311eb79838d5914880637946c499ff64cc04b98387163ee53834da3e82e68ed276eaf29e592d29237b435d7c9b38724c78341d8f9c962205bd2ef9594597339853c37bb7896ca34a24e934968f5a4179763af6f3e83d21095c27e613882e6ddc52228951c2d6a5061a334263d3ca9ac61ae7de68c8b49ba906d116575627b3fabf33383f3b0aea91935b512a8d2ddf8a73cd93d19dc3442f7ac9c6f9f0a4f3c8f09e08ae96edbee85cf82f969497284aa287763ff6aaa2a99a0873f22d270df266285eaf631d84e70db82bb251c2bf38a8e90f82dbcf93756da3629cfaa15620d2e30251395730b996ea9cf6ce38a28d50ef8747b89cc4136e78190c32d416f83ca3a045ec26ce957bcc5a6550ab04a5225d2ccd6f613a4f3798e473394e0ae2eeef471da168f9d0595d43b6012fccf2855ff01f8befcdb3bae251981475e2a5cc20ed0dbd0864389043b7938d8f14c581eeebec9da9a854805eb454e4f2c4d44111e84bf7c855c224e01de7af00abd6c25fd5e144bfcbe2a844a42f0172c703fd9276f20cb8258c413f5c35156e94b1d05b8d9d5f598913ac997cff233479d608f4367cd977c128e302620b196085e19c5e9430688c0a570d70db9e4e135ad90bdaadcbd5632c3f92bea17a35ec69f6d506922d0a01cade809f24b166be5f2cb942c3c33f28fb78427c8da53570de56a8bad0495b6518f43ad0ff76b48f70fdbbb043706c60d7c0b04b1f5c7de7b8e2d693bd8d57e3989e6337d6b2b72fead2a76b5ebe0661ffd0dace55c8f7817c6555186a15830bc29ae846599d04531da6b74028c53c69c3116aafb02a7fc4e6d8ceff0d61d3dc61a027e0fae91a46cecd65611a9e1d140ac82bcab4a6cfe26377c129823542d514f92a4e72db68a6b7b826d48fcda909cd93f0a66b1f5733979e1af3c50cac40d06672693a68ec9b0378d06a473b589d993e6d84e3237495d9817c4d7916dc4d5c5a82cc136f2423d2197959e6ba113e11daf6d8abebf562b2ac975564c8e7cbd379b3ed7ffb437bbbd3d5577624e5596a2e34aa5a63ef0d625542a9f0fe3bd42677fc47459ad99845fd64aee1fcf59dee781e8708492fdc5b7f3769acadfd0f9836b079f1bd5c916cdaecaf78ae67497a7aeb40d358ef49c4eda7a5ca1fa0b940456a6551335350233619e76f5147a0ffd5bab85c898c7df7e506f09ec8d2f58a9fa3eef9a5008bcccc41af5e04b66904bf203947585ade03c2bcef25a428a1b6c7a9e0c034d2d3c186a306c1d4d5ca22f8e7dbbf26e3502a53a9227b271b3a582cf2eb4116f8e2cf82962779373bbe8721a56defa527d36414a06564c14a21d6de036b5b4ddfa5b6f08add4a11e5dd7457f5198a53af33db2c487cc4ea1e04ed9971cdb26d7d6baabbd6995b4ffd33bf2082ec43067c666d420a9327c727e82d189e1c2b70a08fd5049e03b90fe44c52ad24dc568a328b2e1cf6a03a55e3f778ba440b7497b45bd36d601dd35947d3aa64c2a6fe4d0a10e314caa24f2fc8e3877e00667964b26f3433f521ff66499558c4bc3dfc1d567d875d86e3dedbb76465454b88f09a26eafd1600eaf47af7ef848e0ee417f2b75dca0f36eafead3a2ff269afaecf5586b2271b29024e4c93446548706896c951678d10d82ff035277161a7259cd02b41bd1f0ca1990efd7049ca081e17c897858267c29173954197c5ba4fd90df46343dd44dbbc1717a36bbe949618187c40168b2388955ef26c4c6559d2cce5fd39daba6dc9b176b260324b39d7fbef89a4bbb04017672cb342e63b6d9218caf2322cd46061bc780b0df013438dc4c248be7b2c6f9a6737a6ec8b747a0dbe339f81b7ff5c423c6cb16c0fdfcf661fc914ce5a9819a4d39c3c74eb02d751498d127b88c590ba8e7a1afa1d7d0f5c1b22d1963497d7f09ca60b93b0a731c5ba0e1f1b1a08a131b26c1d3e0f82c85d59cc63aad0a2f8f9c94c4e13d5f1063b7379dd9812e22863fc6776b86245d553131b32c683c6fe5745eb63c06cc82b5b1cb4270b4e5232696d644558ee8ca26b946cac3ff0c421f5463c4ce7ce1a4758c78b1d1de94bbe9669058f844f3cb1b6fc40c380b7a7ca0fc1c22f673f6f55f6bb8ca0d23d044322e40cbcb444e119196a116b942e00190d7b13d651792a26e1b1fad95c04e527a044d8179971fb0288e0701ec36730601bc4d6301cbd94dd526e6db2fe095d494d913551d34f6c2a988b11583f23eca6a4e7efce5e44d3710a007c5b0130e67829a866ff3e4e92635c33421ae278c12691cdf267263d472e92132ec7b8a4fbf9e1905717ca60301294fe586bf74ce1cd5970f354d4ae0534d619a02c988867058c31e477dd8269b229dc49d21ae28fbfea12c6148fea65f70a3b6487567143482ff0885e55eec54f012e58de231a9ed25560faca4269f6bde34faf16237d4364200b01f109096e0c178f6c9e22380c1155df36ba31398223195b4edce9e7676c2bb224b7e74a859dedce18296c357f3a9d631dcf145d33993f3db620a854fd698e2ad01e989ed5cd4c7cbd18b7fb814100678c18ccbe48e1ed9c3ceed03ce8d8c387bcf4b9fec9fa4f9995074816a4cb5f74e4479f974894333dae94c137942a1dc3f9eea9510d94c452a6172eac67b46f0ef4310e70129d5f3c0426460c84432d3c2924dbc500a1d8d4b16f8fd5988948bc4f503119b6bb16f5eefc2dbe6bfc02ecae93bf30a33c199755b6059e180fca252744aa417f03ba7b4a2ff009f2a233d5ce9a40327112c1b96d9b0a465ebacd691b4d2488375c1df2551f6000b374b57a137dc71e3ce3564eb04e7495f136291836d3642d3b028e2bb4ef71b4e1288a6769e6d5f1f3079ca534040251e9e28079e3dfebc79e04295bd1070f2df3663911135f2ecbe6e6cafc4376f8240950ea0ebe9f249b4ec681c64692fa0c114c1b9ff7d88cb8067f25246262ce9672c75e730ce62574c28098b63afec5a2aee87260d627efcd5ce2d3f8f509e9cffcc1fd080ea9022e9bbc2021887ed5527fc158fb97836dceeeb612cc5ab068532f6d1f1c9683aa3f41dc17c4ae8b748d394f759e4d09a3e702b4b1116d1f05f18b2e376600325af56591abad295ffac2057e8bbf6d38a641db29e2efd525fa864c1a861a3d179c371b65706acf78fe073530d33085749165b978558a72110465f97b9de68dbad8011033e8534c1b9d5bdfb82397e2869bbc3796ab22353a6c624552b190904477ea61a958b99ccfcbc1555fee8ab91ac0a9cd8c3168c77925f96ccfba1d5f048e5e5ecdf1dca9debda3e8e208eed93ff8ffa0f9ef9006aaf8f7edda6aa9e0eb2273807544ffd1d758285ff247405feb02d79d8e09162b2fc19dcd8eec59e10ffc6df2cd08f3300fb976a0b3e9fcaa75bd002c90692d0f6cb92fcbb877192b2c0068dcd534f3f31bc6147c3dd04a94190ca1bf59c25a904cd75ccd5cb3ffa31e27abd3e162b5e057d7d3321e8c89f67852aaf0d6dbb179bb38009131e8946a68faadd01788f27c6268fd5dd73685af157c40bb0a32a4bd29a0142020035903b0a5ce2bf4fadff0a0ddb1165a1d5badd99444098ae373070d01ebdfc86738ee40972f43c8c952d4b758abcb933a59e38ff18aab00f0a5d5601cf615fd92d86587b0579635618793129dd607dc8162dc69e87d77a9c60d601e2dbd9a458de69d96067856b482c7fd6104748886f9a12879192f54d6244c85c9a4c7125a2dd190a43279a4108b68dd1783b26557a59613394719a06f44dbdce5f7936daa2a99508b3232f8e808dc2debbcf6bc0741c2e604bf147f6b5a02d47d7aeb9adaffa0d948d96a7631a3cca157294040c2ca847c9a51404e59654a6d9d4c255f3f9da6997ecf8b4db24f81b01c485e4aa9ddee0da0dbe0b1da4b4ed3d5562aef63eb5038f4231e8b87d9004fbce5e79b50dc94f51d5e796f244b0071bd564c85861dfbb1126c7c3e9fb7afceff6f26b142ebac98ffa69d8c81b5beaa6bfb41701ad4a780687457273e3362186f5a67b578bb01c7948941f9ce00d2a4fe11da35460f46622881f0a07bde1ca0db926574e62484a911a78662fc242ebd17c2374365f1b146a71e2bf5c35afdfad805bd4eef9085c60fc74aa9934a917f5bdaa94c84511b13e081d8c4546f05f63b339a22eb34e5b908c0f8dd2ac959e4f22c6fefe71dbc33f078debcc36fc269a0d2174ea81cbafedeef5d91f075ad9b0bfcf95f848e08689beb1b6277f20acce687fc6ce604448f554486c02642ccff567684ef8627c3d0c6eb2c582bd74caeb1a7ca37da052d2f94d3e09fa329a80a884308fccb8b9e0afb557029709d25ece70b54f6ac0d66eba9042f104f711c54db20eb6d671f570be801f140280b3f4b34f8434cf92a28ba02480096a8355eaf8b8df23e1f7a6e8ae08214790d23aab2860e51c251a7e42cf45fbc42404980775a8750a6efa9f2f95878315c286b32ed91530953baf12dd537397e1c3c6324fc0c57db6d8545fb766b6e6ff65a83631f4b436a6e2b59e35467534138d79db3c8ec6cc2a4c34ec6a4574e92e2d60b3bb771f5b65b1297291185b923e8dd44b8b1464d525c41e59e18fe5e930da5dbc0a001523e9dcf29fa26758abe1530f432adeb8c995bc8c8f7d766d529872187fff7be5503fe367f538f7e11d71e4706057cb0dd00bfd8ab636d39f5a4977a361746b066793d11ef5358cd6eea50aa52ca13ee2a28492356554446c1b93d34cc6494a1b8c6e19c85c977f1cf556e773cbc8904c69ac21097d14efe3b2629a75bf745a0c13e95079e000360381d7ee01078f8d0a22c2639087d73f8374c417eefcdb482be0698f84cdb86f1d99285abb5c27b3aa41280ed9fafe56db825c938a097055c47163b069b7bd261fd60b1b1a7a3e6aaf80756ae137854bce1343caafaef810af4050e3f8f0ac6ce7ab8e15f4971498d861227b40ab9d0e77951127cb6510eac105e65a309669e3fc2a5833881a8b6d368b8116920ca6515f975b2a7bc39fe23625723a66dbfc51974ce1f50a8a9f6747bb8f7ebb646a30e2aeefefd7d349e98fa3f39b21c7568ab89190dd2c8b403e6f797868e99f4c1c25a890fa199cf48f61df62ba3c0b0eca40d04447c2cbf38a9219764576feca0c21cb2e08ad3a9c06b362b02496694f29764a4651e3bfc16bb02df6181fd6d93c5958df8055b1bd7ebd86067e96700b27e259ea71fa933672438faf5fc390adb256084453061301cadd9d17abd273c623482887cf8f53e456cceea81e92d0db55ffbd6c380ffef71ac70b9b56f9a0bd7fa3f897eb63a4855202e6fc71fa8511597cc1fca3c6e29e2276eaa90a2a1fba5f33f26587c48a4fb511848c6aabfe88312afe436ff8a4103217fe8136936eff5a7978f25ff1662f114b391b846263f16b9149a1b3aafba8de281f54ace6337928439e40e0883e9f803a68137e84aaa279cb6b9ebafd6708174eac4a74d11c0f9582d33aaf65c43c7ba461caa364c87486dd5cea8f715c45e263747be569d1bead8583b0a39c7e46f5bd5b2ff0abb55d6010a9afefe602b850d753f65ce4777551dd82b3dd8e417c20bc93c60f32c78d2d3a5aec481a1e9bd7ed494358e6cc2c922e6e664ecd75b10722cdeecb39e84d881a32be5971ddf56964d9295e38d920e47558980231056cb3f0a2de3c2a64e884d27723a252eb1f6b254c6f90c2f8e26d19738753ee92e33a2e90776c97715539d8ffb8f6845c280f79e74c9c07e68f0675879e293997323d05a0f7728b37f5e7b15b1947045b4f4cbf6cb60a5d1b75a97a290d48b70d7a68393147c720b4f1b15fd66379f266f325a2052ffdaec307d4f9bb17525656e91771b0f84ecb6f5403e565d982deee5f012ce370d6e2011f164c9f97471f43ad9ee886b3f11b2771aeb4ce874a7f4130327a58606e631c1710eee7df8165029c62d4dc50710d24174b809a2b46c7cec3b10525b87bb16fb256a6de995cedcd15bfd905d434ca7e0056122c8892f9836df08ad9d68d97efcf0f74672fbe4009d902b21e168db2fc1e5499aa66878618f928eeee898222f90d2199e23a9bd4222ff086b4f18085a6570f67232f4237f0c2fe5f39561e92bf8ab9ad2c0584cb7dcbfe98814c6d256b60f92cc35e0dffb38a99493191478b302b72429c3789bc08e02a85ea8456abc5e7a6c96afb3af6c6682e80129b08a08d49bc803346d491cefcf21c64419b978565675cf48486d678cdb6d4556efd10c2f597aaa651f07427915b6cb84ccd8930aa5c45601de4249304f79bb164771ab462292a3ccc31a127bbeccfafec795c2ca02124c769c304d0f2da1ac65de831362ec4c9c7ba3dcd3b9264cba2cfcf5c8668e3f00646588f2848f42f1742893c2dda549e103253b8448a423b9307aacac2541cb7545ee74b3e707f9a51dd2235f53f70c67bc463974693fc26a4e91a7b7809a5e7133316e0b564c2bf002f42663f2fe7e62b13a6a3795f09e932450834a4cc6401a6567c7cdad667ed29807859653ad6b71beda79e200e6b1e5fc58137f38cfe12af4e921cca7615e1181f19e59514a03539539a3993d63f7acc2d0dd9ad30253b1c687f5cc51567927f08fc0bfae2f5a30b3ab3a6fcbc6f28b72d09d517867cb5fc0190ed26009434a0ca0b07997f6e77486412392b92227481529c2d7d107236d9d600caf15fd737e78792690422c093747d259ccad9e9fa6977aecc4dfe25b051982e3efadf984bc7d1351ee41217d3044656851ed8a6758119674c6a382144a41cd590f9b93352584034fbc488ee946a6fdf2dc40a510f74ce4e421e39613e31ac88bfdf9230a46cea8b689729855a4ec8a2e21ce912f11a3117a07a674c41ef6ea68f604fdc86da8850e485536ad5fa306648540f3de467a90aab45e06b5a217d835a87961fd107eda5515681640b516967234dbde3976fe9b9966e28b16ac965007aa2deb753e87f379d688ee44cf04dd4a7b4ea2e2f7e044a31c9e8440e092a14c424b9ac623003ee1a1aa8b4f1e908c71776af27eeb33bad52ab1412bad23c7437df0bbc6a11c26e5f9f60fdf41f49f821509a33694b270739cd7e8f639a30c7f932a340ff129f351d7eb8cd480bfa6a95fc9f937c5080374664e2462e7368468a0877c82569053e683408ef1d192f2291c7686325eeba5016a1b575760b749b9a8b8d5c1e7a60ed9449b395e74aa7cd6bb312dddd713d3fa4663f05f3a5a2703f1fdbfb35f8b5e53d5615e2918b1dd9c98c1b7"]}} \ No newline at end of file From f28054f42d5af4b203e9bec07873176a0b0c05da Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 30 Apr 2024 15:32:29 +0400 Subject: [PATCH 37/70] Update DataColumnSidecar kzg verification + proof verification (#27) * added structure checks in kzg verification from the spec * added proof verification * fixed kzg_commitment_proof -> kzg_commitments_proof * logging fixes * small changes to naming to distinguish with blobs --- .../SingleMerkleProofTestExecutor.java | 2 +- .../versions/eip7594/DataColumnSidecar.java | 19 +++++---- .../eip7594/DataColumnSidecarSchema.java | 22 +++++----- .../logic/common/helpers/MiscHelpers.java | 14 ++++--- .../deneb/helpers/MiscHelpersDeneb.java | 4 +- .../eip7594/helpers/MiscHelpersEip7594.java | 42 +++++++++++++++++++ .../deneb/helpers/MiscHelpersDenebTest.java | 5 ++- .../teku/spec/generator/ChainBuilder.java | 2 +- .../teku/spec/util/DataStructureUtil.java | 12 +++--- .../infrastructure/logging/LogFormatter.java | 12 ++++++ .../DataColumnSidecarsByRootValidator.java | 4 +- 11 files changed, 99 insertions(+), 39 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java index 82671409527..faf230165bd 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java @@ -130,7 +130,7 @@ private void runBlobKzgCommitmentMerkleProofTest( assertThat(miscHelpersDeneb.getBlobSidecarKzgCommitmentGeneralizedIndex(kzgCommitmentIndex)) .isEqualTo(data.leafIndex); assertThat( - miscHelpersDeneb.computeKzgCommitmentInclusionProof( + miscHelpersDeneb.computeBlobKzgCommitmentInclusionProof( kzgCommitmentIndex, beaconBlockBody)) .isEqualTo(data.branch.stream().map(Bytes32::fromHexString).toList()); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java index 214c7b6d794..3caef1d2cd3 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java @@ -52,7 +52,7 @@ public DataColumnSidecar( final List kzgCommitments, final List kzgProofs, final SignedBeaconBlockHeader signedBeaconBlockHeader, - final List kzgCommitmentInclusionProof) { + final List kzgCommitmentsInclusionProof) { super( schema, SszUInt64.of(index), @@ -65,8 +65,9 @@ public DataColumnSidecar( .createFromElements(kzgProofs.stream().map(SszKZGProof::new).toList()), signedBeaconBlockHeader, schema - .getKzgCommitmentInclusionProofSchema() - .createFromElements(kzgCommitmentInclusionProof.stream().map(SszBytes32::of).toList())); + .getKzgCommitmentsInclusionProofSchema() + .createFromElements( + kzgCommitmentsInclusionProof.stream().map(SszBytes32::of).toList())); } // public DataColumnSidecar( @@ -76,7 +77,7 @@ public DataColumnSidecar( // final KZGCommitment kzgCommitment, // final KZGProof kzgProof, // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentInclusionProof) { + // final List kzgCommitmentsInclusionProof) { // this( // schema, // index, @@ -84,7 +85,7 @@ public DataColumnSidecar( // new SszKZGCommitment(kzgCommitment), // new SszKZGProof(kzgProof), // signedBeaconBlockHeader, - // kzgCommitmentInclusionProof); + // kzgCommitmentsInclusionProof); // } public UInt64 getIndex() { @@ -107,7 +108,7 @@ public SignedBeaconBlockHeader getSignedBeaconBlockHeader() { return getField4(); } - public SszBytes32Vector getKzgCommitmentInclusionProof() { + public SszBytes32Vector getKzgCommitmentsInclusionProof() { return getField5(); } @@ -128,12 +129,12 @@ public SlotAndBlockRoot getSlotAndBlockRoot() { } public String toLogString() { - return LogFormatter.formatBlobSidecar( + return LogFormatter.formatDataColumnSidecar( getSlot(), getBlockRoot(), getIndex(), getDataColumn().toBriefString(), - "" + getSszKZGCommitments().size(), - "" + getSszKZGProofs().size()); + getSszKZGCommitments().size(), + getSszKZGProofs().size()); } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java index f40c10bf230..a8ccaa5ca78 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java @@ -49,8 +49,8 @@ public class DataColumnSidecarSchema static final SszFieldName FIELD_KZG_COMMITMENTS = () -> "kzg_commitments"; static final SszFieldName FIELD_KZG_PROOFS = () -> "kzg_proofs"; static final SszFieldName FIELD_SIGNED_BLOCK_HEADER = () -> "signed_block_header"; - static final SszFieldName FIELD_KZG_COMMITMENT_INCLUSION_PROOF = - () -> "kzg_commitment_inclusion_proof"; + static final SszFieldName FIELD_KZG_COMMITMENTS_INCLUSION_PROOF = + () -> "kzg_commitments_inclusion_proof"; DataColumnSidecarSchema( final SignedBeaconBlockHeaderSchema signedBeaconBlockHeaderSchema, @@ -70,7 +70,7 @@ public class DataColumnSidecarSchema SszKZGProofSchema.INSTANCE, specConfig.getMaxBlobCommitmentsPerBlock())), namedSchema(FIELD_SIGNED_BLOCK_HEADER, signedBeaconBlockHeaderSchema), namedSchema( - FIELD_KZG_COMMITMENT_INCLUSION_PROOF, + FIELD_KZG_COMMITMENTS_INCLUSION_PROOF, SszBytes32VectorSchema.create( specConfig.getKzgCommitmentsInclusionProofDepth().intValue()))); } @@ -84,9 +84,9 @@ public SignedBeaconBlockHeaderSchema getSignedBlockHeaderSchema() { return (SignedBeaconBlockHeaderSchema) getFieldSchema4(); } - public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { + public SszBytes32VectorSchema getKzgCommitmentsInclusionProofSchema() { return (SszBytes32VectorSchema) - getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENT_INCLUSION_PROOF)); + getChildSchema(getFieldIndex(FIELD_KZG_COMMITMENTS_INCLUSION_PROOF)); } @SuppressWarnings("unchecked") @@ -106,7 +106,7 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { // final SszKZGCommitment sszKzgCommitment, // final SszKZGProof sszKzgProof, // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentInclusionProof) { + // final List kzgCommitmentsInclusionProof) { // return new DataColumnSidecar( // this, // index, @@ -114,7 +114,7 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { // sszKzgCommitment, // sszKzgProof, // signedBeaconBlockHeader, - // kzgCommitmentInclusionProof); + // kzgCommitmentsInclusionProof); // } // // public DataColumnSidecar create( @@ -123,14 +123,14 @@ public SszBytes32VectorSchema getKzgCommitmentInclusionProofSchema() { // final Bytes48 kzgCommitment, // final Bytes48 kzgProof, // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentInclusionProof) { + // final List kzgCommitmentsInclusionProof) { // return create( // index, // new Blob(getBlobSchema(), blob), // KZGCommitment.fromBytesCompressed(kzgCommitment), // KZGProof.fromBytesCompressed(kzgProof), // signedBeaconBlockHeader, - // kzgCommitmentInclusionProof); + // kzgCommitmentsInclusionProof); // } public DataColumnSidecar create( @@ -139,7 +139,7 @@ public DataColumnSidecar create( final List kzgCommitments, final List kzgProofs, final SignedBeaconBlockHeader signedBeaconBlockHeader, - final List kzgCommitmentInclusionProof) { + final List kzgCommitmentsInclusionProof) { return new DataColumnSidecar( this, index, @@ -147,7 +147,7 @@ public DataColumnSidecar create( kzgCommitments, kzgProofs, signedBeaconBlockHeader, - kzgCommitmentInclusionProof); + kzgCommitmentsInclusionProof); } public static DataColumnSidecarSchema create( diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java index 611f4749938..e5d07fbff38 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MiscHelpers.java @@ -350,11 +350,6 @@ public boolean verifyBlobKzgProof(final KZG kzg, final BlobSidecar blobSidecar) return false; } - public boolean verifyDataColumnSidecarKzgProof( - final KZG kzg, final DataColumnSidecar dataColumnSidecar) { - return false; - } - public boolean verifyBlobKzgProofBatch(final KZG kzg, final List blobSidecars) { return false; } @@ -381,6 +376,15 @@ public UInt64 getMaxRequestBlocks() { return UInt64.valueOf(specConfig.getNetworkingConfig().getMaxRequestBlocks()); } + public boolean verifyDataColumnSidecarKzgProof( + final KZG kzg, final DataColumnSidecar dataColumnSidecar) { + return false; + } + + public boolean verifyDataColumnSidecarInclusionProof(final DataColumnSidecar dataColumnSidecar) { + return false; + } + public Optional toVersionDeneb() { return Optional.empty(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java index 0c49d887a1c..c1e76cec904 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java @@ -231,7 +231,7 @@ public int getBlobSidecarKzgCommitmentGeneralizedIndex(final UInt64 blobSidecarI GIndexUtil.gIdxCompose(blobKzgCommitmentsGeneralizedIndex, commitmentGeneralizedIndex); } - public List computeKzgCommitmentInclusionProof( + public List computeBlobKzgCommitmentInclusionProof( final UInt64 blobSidecarIndex, final BeaconBlockBody beaconBlockBody) { return MerkleUtil.constructMerkleProof( beaconBlockBody.getBackingNode(), @@ -256,7 +256,7 @@ public BlobSidecar constructBlobSidecar( index, commitmentsCount)); } final List kzgCommitmentInclusionProof = - computeKzgCommitmentInclusionProof(index, beaconBlockBody); + computeBlobKzgCommitmentInclusionProof(index, beaconBlockBody); return blobSidecarSchema.create( index, blob, commitment, proof, signedBeaconBlock.asHeader(), kzgCommitmentInclusionProof); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index e62c2621eac..8ebae831b7d 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -20,13 +20,17 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.ssz.tree.MerkleUtil; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCell; import tech.pegasys.teku.kzg.KZGCellWithID; import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; @@ -45,13 +49,17 @@ public static MiscHelpersEip7594 required(final MiscHelpers miscHelpers) { } private final SpecConfigEip7594 specConfigEip7594; + private final Predicates predicates; + private final SchemaDefinitionsEip7594 schemaDefinitions; public MiscHelpersEip7594( final SpecConfigEip7594 specConfig, final Predicates predicates, final SchemaDefinitionsEip7594 schemaDefinitions) { super(specConfig, predicates, schemaDefinitions); + this.predicates = predicates; this.specConfigEip7594 = specConfig; + this.schemaDefinitions = schemaDefinitions; } public UInt64 computeSubnetForDataColumnSidecar(UInt64 columnIndex) { @@ -79,6 +87,18 @@ public List computeDataColumnSidecarBackboneSubnets( @Override public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataColumnSidecar) { + final UInt64 dataColumns = specConfigEip7594.getNumberOfColumns(); + if (dataColumnSidecar.getIndex().isGreaterThanOrEqualTo(dataColumns)) { + return false; + } + + // Number of rows is the same for cells, commitments, proofs + if (dataColumnSidecar.getDataColumn().size() != dataColumnSidecar.getSszKZGCommitments().size() + || dataColumnSidecar.getSszKZGCommitments().size() + != dataColumnSidecar.getSszKZGProofs().size()) { + return false; + } + return IntStream.range(0, dataColumnSidecar.getSszKZGProofs().size()) .mapToObj( index -> @@ -93,6 +113,28 @@ public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataCo .orElse(true); } + @Override + public boolean verifyDataColumnSidecarInclusionProof(final DataColumnSidecar dataColumnSidecar) { + return predicates.isValidMerkleBranch( + dataColumnSidecar.getSszKZGCommitments().hashTreeRoot(), + dataColumnSidecar.getKzgCommitmentsInclusionProof(), + specConfigEip7594.getKzgCommitmentsInclusionProofDepth().intValue(), + getBlockBodyKzgCommitmentsGeneralizedIndex(), + dataColumnSidecar.getBlockBodyRoot()); + } + + private int getBlockBodyKzgCommitmentsGeneralizedIndex() { + return (int) + BeaconBlockBodySchemaEip7594.required(schemaDefinitions.getBeaconBlockBodySchema()) + .getBlobKzgCommitmentsGeneralizedIndex(); + } + + public List computeDataColumnKzgCommitmentsInclusionProof( + final BeaconBlockBody beaconBlockBody) { + return MerkleUtil.constructMerkleProof( + beaconBlockBody.getBackingNode(), getBlockBodyKzgCommitmentsGeneralizedIndex()); + } + @Override public Optional toVersionEip7594() { return Optional.of(this); diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java index d6712b89470..aca0e05a4ad 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDenebTest.java @@ -247,7 +247,8 @@ void verifyBlobSidecarMerkleProofShouldValidate() { for (int i = 0; i < numberOfCommitments; ++i) { final UInt64 blobSidecarIndex = UInt64.valueOf(i); final List merkleProof = - miscHelpersDeneb.computeKzgCommitmentInclusionProof(blobSidecarIndex, beaconBlockBody); + miscHelpersDeneb.computeBlobKzgCommitmentInclusionProof( + blobSidecarIndex, beaconBlockBody); assertThat(merkleProof.size()) .isEqualTo( SpecConfigDeneb.required(spec.getGenesisSpecConfig()) @@ -276,7 +277,7 @@ void verifyBlobSidecarMerkleProofShouldValidate() { final UInt64 wrongIndex = UInt64.valueOf(j); final List merkleProofWrong = - miscHelpersDeneb.computeKzgCommitmentInclusionProof(wrongIndex, beaconBlockBody); + miscHelpersDeneb.computeBlobKzgCommitmentInclusionProof(wrongIndex, beaconBlockBody); assertThat(merkleProofWrong.size()) .isEqualTo( SpecConfigDeneb.required(spec.getGenesisSpecConfig()) diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/ChainBuilder.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/ChainBuilder.java index cc1e4bea88a..582ddce809d 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/ChainBuilder.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/generator/ChainBuilder.java @@ -739,7 +739,7 @@ private SignedBlockAndState generateBlockWithBlobSidecars( final Blob blob = blobs.get(index); final KZGCommitment kzgCommitment = kzgCommitments.get(index); final List merkleProof = - miscHelpersDeneb.computeKzgCommitmentInclusionProof( + miscHelpersDeneb.computeBlobKzgCommitmentInclusionProof( blobSidecarIndex, nextBlockAndState.getBlock().getMessage().getBody()); return new BlobSidecar( blobSidecarSchema, diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 9f844912a20..0eb12ce90ee 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -2378,7 +2378,7 @@ public class RandomSidecarBuilder { private Optional> kzgCommitments = Optional.empty(); private Optional> kzgProofs = Optional.empty(); private Optional signedBeaconBlockHeader = Optional.empty(); - private Optional> kzgCommitmentInclusionProof = Optional.empty(); + private Optional> kzgCommitmentsInclusionProof = Optional.empty(); public RandomSidecarBuilder index(final UInt64 index) { this.index = Optional.of(index); @@ -2406,9 +2406,9 @@ public RandomSidecarBuilder signedBeaconBlockHeader( return this; } - public RandomSidecarBuilder kzgCommitmentInclusionProof( - final List kzgCommitmentInclusionProof) { - this.kzgCommitmentInclusionProof = Optional.of(kzgCommitmentInclusionProof); + public RandomSidecarBuilder kzgCommitmentsInclusionProof( + final List kzgCommitmentsInclusionProof) { + this.kzgCommitmentsInclusionProof = Optional.of(kzgCommitmentsInclusionProof); return this; } @@ -2435,12 +2435,12 @@ public DataColumnSidecar build() { kzgProofs.orElseGet( () -> IntStream.range(0, numberOfProofs).mapToObj(__ -> randomKZGProof()).toList()), signedBlockHeader, - kzgCommitmentInclusionProof.orElseGet( + kzgCommitmentsInclusionProof.orElseGet( () -> IntStream.range( 0, dataColumnSidecarSchema - .getKzgCommitmentInclusionProofSchema() + .getKzgCommitmentsInclusionProofSchema() .getLength()) .mapToObj(__ -> randomBytes32()) .toList())); diff --git a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/LogFormatter.java b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/LogFormatter.java index dbcf448b89a..870b56aa26b 100644 --- a/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/LogFormatter.java +++ b/infrastructure/logging/src/main/java/tech/pegasys/teku/infrastructure/logging/LogFormatter.java @@ -41,4 +41,16 @@ public static String formatBlobSidecar( "block %s (%s), index %s, blob %s, commitment %s, proof %s", formatAbbreviatedHashRoot(blockRoot), slot, index, blob, kzgCommitment, kzgProof); } + + public static String formatDataColumnSidecar( + final UInt64 slot, + final Bytes32 blockRoot, + final UInt64 index, + final String blob, + final int kzgCommitmentsSize, + final int kzgProofsSize) { + return String.format( + "block %s (%s), index %s, 1st cell %s, commitments %s, proofs %s", + formatAbbreviatedHashRoot(blockRoot), slot, index, blob, kzgCommitmentsSize, kzgProofsSize); + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java index dffcc983981..693caf5b1e3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java @@ -56,10 +56,10 @@ public void validate(final DataColumnSidecar dataColumnSidecar) { peer, InvalidResponseType.DATA_COLUMN_SIDECAR_UNEXPECTED_IDENTIFIER); } - verifyKzg(dataColumnSidecar); + verifyKzgProof(dataColumnSidecar); } - private void verifyKzg(final DataColumnSidecar dataColumnSidecar) { + private void verifyKzgProof(final DataColumnSidecar dataColumnSidecar) { if (!verifyDataColumnSidecarKzgProof(dataColumnSidecar)) { throw new DataColumnSidecarsResponseInvalidResponseException( peer, InvalidResponseType.DATA_COLUMN_SIDECAR_KZG_VERIFICATION_FAILED); From 29eb79f5e6a8f5452e04e874c006be5e4c80435b Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 1 May 2024 12:47:33 +0400 Subject: [PATCH 38/70] Merge pull request #28 * Fix el methods for EIP7594 --- .../MilestoneBasedEngineJsonRpcMethodsResolver.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java index b4dc559a640..8f30f8a6570 100644 --- a/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java +++ b/ethereum/executionlayer/src/main/java/tech/pegasys/teku/ethereum/executionlayer/MilestoneBasedEngineJsonRpcMethodsResolver.java @@ -72,6 +72,8 @@ public MilestoneBasedEngineJsonRpcMethodsResolver( methodsByMilestone.put(milestone, denebSupportedMethods()); break; case EIP7594: + // not changed + methodsByMilestone.put(milestone, denebSupportedMethods()); break; } }); From b3e80795ba874109a930783b0659fd4bae67c932 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 1 May 2024 12:50:27 +0400 Subject: [PATCH 39/70] Merge pull request #29 * constructDataColumnSidecars miscHelpers method --- .../versions/eip7594/DataColumnSidecar.java | 37 +++++------ .../eip7594/DataColumnSidecarSchema.java | 34 +++++------ .../eip7594/helpers/MiscHelpersEip7594.java | 61 +++++++++++++++++++ 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java index 3caef1d2cd3..601974ee3df 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecar.java @@ -70,23 +70,26 @@ public DataColumnSidecar( kzgCommitmentsInclusionProof.stream().map(SszBytes32::of).toList())); } - // public DataColumnSidecar( - // final DataColumnSidecarSchema schema, - // final UInt64 index, - // final Blob blob, - // final KZGCommitment kzgCommitment, - // final KZGProof kzgProof, - // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentsInclusionProof) { - // this( - // schema, - // index, - // blob, - // new SszKZGCommitment(kzgCommitment), - // new SszKZGProof(kzgProof), - // signedBeaconBlockHeader, - // kzgCommitmentsInclusionProof); - // } + public DataColumnSidecar( + final DataColumnSidecarSchema schema, + final UInt64 index, + final DataColumn dataColumn, + final SszList sszKzgCommitments, + final SszList sszKkzgProofs, + final SignedBeaconBlockHeader signedBeaconBlockHeader, + final List kzgCommitmentsInclusionProof) { + super( + schema, + SszUInt64.of(index), + dataColumn, + sszKzgCommitments, + sszKkzgProofs, + signedBeaconBlockHeader, + schema + .getKzgCommitmentsInclusionProofSchema() + .createFromElements( + kzgCommitmentsInclusionProof.stream().map(SszBytes32::of).toList())); + } public UInt64 getIndex() { return getField0().get(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java index a8ccaa5ca78..4f72d91b63b 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/blobs/versions/eip7594/DataColumnSidecarSchema.java @@ -100,23 +100,23 @@ public SszBytes32VectorSchema getKzgCommitmentsInclusionProofSchema() { return (SszListSchema) getChildSchema(getFieldIndex(FIELD_KZG_PROOFS)); } - // public DataColumnSidecar create( - // final UInt64 index, - // final Blob blob, - // final SszKZGCommitment sszKzgCommitment, - // final SszKZGProof sszKzgProof, - // final SignedBeaconBlockHeader signedBeaconBlockHeader, - // final List kzgCommitmentsInclusionProof) { - // return new DataColumnSidecar( - // this, - // index, - // blob, - // sszKzgCommitment, - // sszKzgProof, - // signedBeaconBlockHeader, - // kzgCommitmentsInclusionProof); - // } - // + public DataColumnSidecar create( + final UInt64 index, + final DataColumn dataColumn, + final SszList sszKzgCommitments, + final SszList sszKkzgProofs, + final SignedBeaconBlockHeader signedBeaconBlockHeader, + final List kzgCommitmentsInclusionProof) { + return new DataColumnSidecar( + this, + index, + dataColumn, + sszKzgCommitments, + sszKkzgProofs, + signedBeaconBlockHeader, + kzgCommitmentsInclusionProof); + } + // public DataColumnSidecar create( // final UInt64 index, // final Bytes blob, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 8ebae831b7d..57c98364298 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.spec.logic.versions.eip7594.helpers; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; @@ -22,15 +24,26 @@ import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.tree.MerkleUtil; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCell; import tech.pegasys.teku.kzg.KZGCellWithID; import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.Cell; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.CellSchema; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumn; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSchema; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecarSchema; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyEip7594; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594; +import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; +import tech.pegasys.teku.spec.datastructures.type.SszKZGProof; import tech.pegasys.teku.spec.logic.common.helpers.MiscHelpers; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; @@ -135,6 +148,54 @@ public List computeDataColumnKzgCommitmentsInclusionProof( beaconBlockBody.getBackingNode(), getBlockBodyKzgCommitmentsGeneralizedIndex()); } + public List constructDataColumnSidecars( + final SignedBeaconBlock signedBeaconBlock, + final List blobs, + final SszList sszKZGProofs, + final KZG kzg) { + if (blobs.isEmpty()) { + return Collections.emptyList(); + } + final BeaconBlockBodyEip7594 beaconBlockBody = + BeaconBlockBodyEip7594.required(signedBeaconBlock.getMessage().getBody()); + final SszList sszKZGCommitments = beaconBlockBody.getBlobKzgCommitments(); + final List kzgCommitmentsInclusionProof = + computeDataColumnKzgCommitmentsInclusionProof(beaconBlockBody); + + final List> extendedMatrix = computeExtendedMatrix(blobs, kzg); + final DataColumnSchema dataColumnSchema = schemaDefinitions.getDataColumnSchema(); + final DataColumnSidecarSchema dataColumnSidecarSchema = + schemaDefinitions.getDataColumnSidecarSchema(); + + final List dataColumnSidecars = new ArrayList<>(); + for (int i = 0; i < extendedMatrix.get(0).size(); ++i) { + final int cellID = i; + final DataColumn dataColumn = + dataColumnSchema.create(extendedMatrix.stream().map(row -> row.get(cellID)).toList()); + final DataColumnSidecar dataColumnSidecar = + dataColumnSidecarSchema.create( + UInt64.valueOf(cellID), + dataColumn, + sszKZGCommitments, + sszKZGProofs, + signedBeaconBlock.asHeader(), + kzgCommitmentsInclusionProof); + dataColumnSidecars.add(dataColumnSidecar); + } + + return dataColumnSidecars; + } + + private List> computeExtendedMatrix(final List blobs, final KZG kzg) { + final CellSchema cellSchema = schemaDefinitions.getCellSchema(); + return blobs.stream() + .map(blob -> kzg.computeCells(blob.getBytes())) + .map( + kzgCellsList -> + kzgCellsList.stream().map(kzgCell -> cellSchema.create(kzgCell.bytes())).toList()) + .toList(); + } + @Override public Optional toVersionEip7594() { return Optional.of(this); From 2a12f22655b3ce872f60881fe32d4d0129b5a574 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 1 May 2024 17:55:33 +0400 Subject: [PATCH 40/70] Create DataColumnSidecars on block production and publish it (#31) --- .../validator/coordinator/BlockFactory.java | 3 + .../coordinator/BlockFactoryEip7594.java | 43 +++++++++++ .../coordinator/BlockFactoryPhase0.java | 7 ++ .../MilestoneBasedBlockFactory.java | 17 ++++- .../coordinator/ValidatorApiHandler.java | 3 + .../publisher/BlockPublisherEip7594.java | 76 +++++++++++++++++++ .../MilestoneBasedBlockPublisher.java | 18 ++++- .../coordinator/ValidatorApiHandlerTest.java | 6 ++ .../publisher/AbstractBlockPublisherTest.java | 2 +- .../eip7594/helpers/MiscHelpersEip7594.java | 33 ++++---- .../beaconchain/BeaconChainController.java | 11 ++- 11 files changed, 196 insertions(+), 23 deletions(-) create mode 100644 beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java create mode 100644 beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java index 766ce6051b9..38904515f61 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java @@ -22,6 +22,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; @@ -43,4 +44,6 @@ SafeFuture unblindSignedBlockIfBlinded( List createBlobSidecars( SignedBlockContainer blockContainer, BlockPublishingPerformance blockPublishingPerformance); + + List createDataColumnSidecars(SignedBlockContainer blockContainer); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java new file mode 100644 index 00000000000..5bfc516cbe3 --- /dev/null +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java @@ -0,0 +1,43 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.coordinator; + +import java.util.List; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; + +public class BlockFactoryEip7594 extends BlockFactoryDeneb { + private final KZG kzg; + + public BlockFactoryEip7594( + final Spec spec, final BlockOperationSelectorFactory operationSelector, final KZG kzg) { + super(spec, operationSelector); + this.kzg = kzg; + } + + @Override + public List createDataColumnSidecars( + final SignedBlockContainer blockContainer) { + final MiscHelpersEip7594 miscHelpersEip7594 = + MiscHelpersEip7594.required(spec.atSlot(blockContainer.getSlot()).miscHelpers()); + return miscHelpersEip7594.constructDataColumnSidecars( + blockContainer.getSignedBlock(), + blockContainer.getBlobs().orElseThrow().asList(), + blockContainer.getKzgProofs().orElseThrow(), + kzg); + } +} diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java index cc05e2885fa..86ab74d39f4 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; @@ -111,4 +112,10 @@ public List createBlobSidecars( final BlockPublishingPerformance blockPublishingPerformance) { return Collections.emptyList(); } + + @Override + public List createDataColumnSidecars( + final SignedBlockContainer blockContainer) { + return Collections.emptyList(); + } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java index 637e34bcf5c..592ad3fcb79 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java @@ -25,9 +25,11 @@ import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.datastructures.metadata.BlockContainerAndMetaData; @@ -40,20 +42,24 @@ public class MilestoneBasedBlockFactory implements BlockFactory { private final Spec spec; public MilestoneBasedBlockFactory( - final Spec spec, final BlockOperationSelectorFactory operationSelector) { + final Spec spec, final BlockOperationSelectorFactory operationSelector, final KZG kzg) { this.spec = spec; final BlockFactoryPhase0 blockFactoryPhase0 = new BlockFactoryPhase0(spec, operationSelector); // Not needed for all milestones final Supplier blockFactoryDenebSupplier = Suppliers.memoize(() -> new BlockFactoryDeneb(spec, operationSelector)); + final Supplier blockFactoryEip7594Supplier = + Suppliers.memoize(() -> new BlockFactoryEip7594(spec, operationSelector, kzg)); // Populate forks factories spec.getEnabledMilestones() .forEach( forkAndSpecMilestone -> { final SpecMilestone milestone = forkAndSpecMilestone.getSpecMilestone(); - if (milestone.isGreaterThanOrEqualTo(SpecMilestone.DENEB)) { + if (milestone.isGreaterThanOrEqualTo(SpecMilestone.EIP7594)) { + registeredFactories.put(milestone, blockFactoryEip7594Supplier.get()); + } else if (milestone.equals(SpecMilestone.DENEB)) { registeredFactories.put(milestone, blockFactoryDenebSupplier.get()); } else { registeredFactories.put(milestone, blockFactoryPhase0); @@ -103,6 +109,13 @@ public List createBlobSidecars( .createBlobSidecars(blockContainer, blockPublishingPerformance); } + @Override + public List createDataColumnSidecars( + final SignedBlockContainer blockContainer) { + final SpecMilestone milestone = getMilestone(blockContainer.getSlot()); + return registeredFactories.get(milestone).createDataColumnSidecars(blockContainer); + } + private SpecMilestone getMilestone(final UInt64 slot) { return spec.atSlot(slot).getMilestone(); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java index f8d3f183d86..506d9d49eda 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandler.java @@ -61,6 +61,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; import tech.pegasys.teku.spec.Spec; @@ -149,6 +150,7 @@ public ValidatorApiHandler( final BlockGossipChannel blockGossipChannel, final BlockBlobSidecarsTrackersPool blockBlobSidecarsTrackersPool, final BlobSidecarGossipChannel blobSidecarGossipChannel, + final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel, final AggregatingAttestationPool attestationPool, final AttestationManager attestationManager, final AttestationTopicSubscriber attestationTopicSubscriber, @@ -190,6 +192,7 @@ public ValidatorApiHandler( blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, performanceTracker, dutyMetrics); this.attesterDutiesGenerator = new AttesterDutiesGenerator(spec); diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java new file mode 100644 index 00000000000..3bd1e88989f --- /dev/null +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java @@ -0,0 +1,76 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.validator.coordinator.publisher; + +import java.util.List; +import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; +import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; +import tech.pegasys.teku.statetransition.blobs.BlockBlobSidecarsTrackersPool; +import tech.pegasys.teku.statetransition.block.BlockImportChannel; +import tech.pegasys.teku.statetransition.block.BlockImportChannel.BlockImportAndBroadcastValidationResults; +import tech.pegasys.teku.validator.coordinator.BlockFactory; +import tech.pegasys.teku.validator.coordinator.DutyMetrics; +import tech.pegasys.teku.validator.coordinator.performance.PerformanceTracker; + +public class BlockPublisherEip7594 extends AbstractBlockPublisher { + + private final BlockBlobSidecarsTrackersPool blockBlobSidecarsTrackersPool; + private final BlockGossipChannel blockGossipChannel; + private final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel; + + public BlockPublisherEip7594( + final BlockFactory blockFactory, + final BlockImportChannel blockImportChannel, + final BlockGossipChannel blockGossipChannel, + final BlockBlobSidecarsTrackersPool blockBlobSidecarsTrackersPool, + final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel, + final PerformanceTracker performanceTracker, + final DutyMetrics dutyMetrics) { + super(blockFactory, blockImportChannel, performanceTracker, dutyMetrics); + this.blockBlobSidecarsTrackersPool = blockBlobSidecarsTrackersPool; + this.blockGossipChannel = blockGossipChannel; + this.dataColumnSidecarGossipChannel = dataColumnSidecarGossipChannel; + } + + @Override + SafeFuture importBlockAndBlobSidecars( + final SignedBeaconBlock block, + final List blobSidecars, + final BroadcastValidationLevel broadcastValidationLevel, + final BlockPublishingPerformance blockPublishingPerformance) { + // TODO: DataColumnSidecars pool fill up + // provide blobs for the block before importing it + blockBlobSidecarsTrackersPool.onCompletedBlockAndBlobSidecars(block, blobSidecars); + return blockImportChannel + .importBlock(block, broadcastValidationLevel) + .thenPeek(__ -> blockPublishingPerformance.blockImportCompleted()); + } + + @Override + void publishBlockAndBlobSidecars( + final SignedBeaconBlock block, + final List blobSidecars, + BlockPublishingPerformance blockPublishingPerformance) { + blockGossipChannel.publishBlock(block); + final List dataColumnSidecars = blockFactory.createDataColumnSidecars(block); + dataColumnSidecarGossipChannel.publishDataColumnSidecars(dataColumnSidecars); + blockPublishingPerformance.blockAndBlobSidecarsPublishingInitiated(); + } +} diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java index 2dabbf3ebac..435d94b20f6 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/MilestoneBasedBlockPublisher.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; @@ -44,6 +45,7 @@ public MilestoneBasedBlockPublisher( final BlockGossipChannel blockGossipChannel, final BlockBlobSidecarsTrackersPool blockBlobSidecarsTrackersPool, final BlobSidecarGossipChannel blobSidecarGossipChannel, + final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel, final PerformanceTracker performanceTracker, final DutyMetrics dutyMetrics) { this.spec = spec; @@ -63,13 +65,27 @@ public MilestoneBasedBlockPublisher( blobSidecarGossipChannel, performanceTracker, dutyMetrics)); + final Supplier blockAndDataColumnSidecarsPublisherSupplier = + Suppliers.memoize( + () -> + new BlockPublisherEip7594( + blockFactory, + blockImportChannel, + blockGossipChannel, + blockBlobSidecarsTrackersPool, + dataColumnSidecarGossipChannel, + performanceTracker, + dutyMetrics)); // Populate forks publishers spec.getEnabledMilestones() .forEach( forkAndSpecMilestone -> { final SpecMilestone milestone = forkAndSpecMilestone.getSpecMilestone(); - if (milestone.isGreaterThanOrEqualTo(SpecMilestone.DENEB)) { + if (milestone.equals(SpecMilestone.EIP7594)) { + registeredPublishers.put( + milestone, blockAndDataColumnSidecarsPublisherSupplier.get()); + } else if (milestone.equals(SpecMilestone.DENEB)) { registeredPublishers.put(milestone, blockAndBlobSidecarsPublisherSupplier.get()); } else { registeredPublishers.put(milestone, blockPublisherPhase0); diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java index 40eb7c723fa..4b0064117e9 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerTest.java @@ -79,6 +79,7 @@ import tech.pegasys.teku.kzg.KZGProof; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; import tech.pegasys.teku.spec.Spec; @@ -150,6 +151,8 @@ class ValidatorApiHandlerTest { mock(BlockBlobSidecarsTrackersPool.class); private final BlobSidecarGossipChannel blobSidecarGossipChannel = mock(BlobSidecarGossipChannel.class); + private final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel = + mock(DataColumnSidecarGossipChannel.class); private final DefaultPerformanceTracker performanceTracker = mock(DefaultPerformanceTracker.class); private final ChainDataProvider chainDataProvider = mock(ChainDataProvider.class); @@ -202,6 +205,7 @@ public void setUp() { blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, attestationPool, attestationManager, attestationTopicSubscriptions, @@ -456,6 +460,7 @@ void getSyncCommitteeDuties_shouldNotUseEpochPriorToFork() { blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, attestationPool, attestationManager, attestationTopicSubscriptions, @@ -1302,6 +1307,7 @@ private void setupDeneb() { blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, attestationPool, attestationManager, attestationTopicSubscriptions, diff --git a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java index 7fd4c543dad..525a494599b 100644 --- a/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java +++ b/beacon/validator/src/test/java/tech/pegasys/teku/validator/coordinator/publisher/AbstractBlockPublisherTest.java @@ -42,7 +42,7 @@ import tech.pegasys.teku.validator.coordinator.performance.PerformanceTracker; public class AbstractBlockPublisherTest { - private final Spec spec = TestSpecFactory.createMinimalDeneb(); + private final Spec spec = TestSpecFactory.createMinimalEip7594(); private final DataStructureUtil dataStructureUtil = new DataStructureUtil(spec); private final BlockFactory blockFactory = mock(BlockFactory.class); private final BlockImportChannel blockImportChannel = mock(BlockImportChannel.class); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 57c98364298..a15aae0a561 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -13,7 +13,6 @@ package tech.pegasys.teku.spec.logic.versions.eip7594.helpers; -import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; @@ -167,23 +166,21 @@ public List constructDataColumnSidecars( final DataColumnSidecarSchema dataColumnSidecarSchema = schemaDefinitions.getDataColumnSidecarSchema(); - final List dataColumnSidecars = new ArrayList<>(); - for (int i = 0; i < extendedMatrix.get(0).size(); ++i) { - final int cellID = i; - final DataColumn dataColumn = - dataColumnSchema.create(extendedMatrix.stream().map(row -> row.get(cellID)).toList()); - final DataColumnSidecar dataColumnSidecar = - dataColumnSidecarSchema.create( - UInt64.valueOf(cellID), - dataColumn, - sszKZGCommitments, - sszKZGProofs, - signedBeaconBlock.asHeader(), - kzgCommitmentsInclusionProof); - dataColumnSidecars.add(dataColumnSidecar); - } - - return dataColumnSidecars; + return IntStream.range(0, extendedMatrix.get(0).size()) + .mapToObj( + cellID -> { + final DataColumn dataColumn = + dataColumnSchema.create( + extendedMatrix.stream().map(row -> row.get(cellID)).toList()); + return dataColumnSidecarSchema.create( + UInt64.valueOf(cellID), + dataColumn, + sszKZGCommitments, + sszKZGProofs, + signedBeaconBlock.asHeader(), + kzgCommitmentsInclusionProof); + }) + .toList(); } private List> computeExtendedMatrix(final List blobs, final KZG kzg) { diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index a68007d9322..732ac2226b1 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -78,6 +78,7 @@ import tech.pegasys.teku.networking.eth2.P2PConfig; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.subnets.AllSubnetsSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.AllSyncCommitteeSubscriptions; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber; @@ -946,7 +947,7 @@ public void initValidatorApiHandler() { graffitiBuilder, forkChoiceNotifier, executionLayerBlockProductionManager); - final BlockFactory blockFactory = new MilestoneBasedBlockFactory(spec, operationSelector); + final BlockFactory blockFactory = new MilestoneBasedBlockFactory(spec, operationSelector, kzg); SyncCommitteeSubscriptionManager syncCommitteeSubscriptionManager = beaconConfig.p2pConfig().isSubscribeAllSubnetsEnabled() ? new AllSyncCommitteeSubscriptions(p2pNetwork, spec) @@ -961,6 +962,13 @@ public void initValidatorApiHandler() { } else { blobSidecarGossipChannel = BlobSidecarGossipChannel.NOOP; } + final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel; + if (spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + dataColumnSidecarGossipChannel = + eventChannels.getPublisher(DataColumnSidecarGossipChannel.class); + } else { + dataColumnSidecarGossipChannel = DataColumnSidecarGossipChannel.NOOP; + } final BlockProductionAndPublishingPerformanceFactory blockProductionPerformanceFactory = new BlockProductionAndPublishingPerformanceFactory( @@ -981,6 +989,7 @@ public void initValidatorApiHandler() { blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, attestationPool, attestationManager, attestationTopicSubscriber, From 3d6e1fe8cc1a0abcae59aeac7832fcd49826fbdd Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 3 May 2024 11:49:57 +0400 Subject: [PATCH 41/70] Fix the DataColumnSidecars construction (#32) * Adjust DataColumnSidecar creation * Fix the test compilation --- .../ValidatorApiHandlerIntegrationTest.java | 4 ++ .../validator/coordinator/BlockFactory.java | 4 +- .../coordinator/BlockFactoryEip7594.java | 8 ++- .../coordinator/BlockFactoryPhase0.java | 3 +- .../MilestoneBasedBlockFactory.java | 5 +- .../publisher/BlockPublisherEip7594.java | 5 +- .../eip7594/helpers/MiscHelpersEip7594.java | 49 +++++++++++-------- 7 files changed, 48 insertions(+), 30 deletions(-) diff --git a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java index 7ed4010551c..82ce6764121 100644 --- a/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java +++ b/beacon/validator/src/integrationTest/java/tech/pegasys/teku/validator/coordinator/ValidatorApiHandlerIntegrationTest.java @@ -37,6 +37,7 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.gossip.BlobSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; +import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.subnets.AttestationTopicSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; import tech.pegasys.teku.spec.Spec; @@ -84,6 +85,8 @@ public class ValidatorApiHandlerIntegrationTest { mock(BlockBlobSidecarsTrackersPool.class); private final BlobSidecarGossipChannel blobSidecarGossipChannel = mock(BlobSidecarGossipChannel.class); + private final DataColumnSidecarGossipChannel dataColumnSidecarGossipChannel = + mock(DataColumnSidecarGossipChannel.class); private final ChainDataProvider chainDataProvider = mock(ChainDataProvider.class); private final NodeDataProvider nodeDataProvider = mock(NodeDataProvider.class); private final ForkChoiceTrigger forkChoiceTrigger = mock(ForkChoiceTrigger.class); @@ -109,6 +112,7 @@ public class ValidatorApiHandlerIntegrationTest { blockGossipChannel, blockBlobSidecarsTrackersPool, blobSidecarGossipChannel, + dataColumnSidecarGossipChannel, attestationPool, attestationManager, attestationTopicSubscriber, diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java index 38904515f61..208ee6c30b1 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactory.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.ethereum.performance.trackers.BlockPublishingPerformance; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -45,5 +46,6 @@ SafeFuture unblindSignedBlockIfBlinded( List createBlobSidecars( SignedBlockContainer blockContainer, BlockPublishingPerformance blockPublishingPerformance); - List createDataColumnSidecars(SignedBlockContainer blockContainer); + List createDataColumnSidecars( + SignedBlockContainer blockContainer, List blobs); } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java index 5bfc516cbe3..cd4dc713218 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryEip7594.java @@ -16,6 +16,7 @@ import java.util.List; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBlockContainer; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; @@ -31,13 +32,10 @@ public BlockFactoryEip7594( @Override public List createDataColumnSidecars( - final SignedBlockContainer blockContainer) { + final SignedBlockContainer blockContainer, List blobs) { final MiscHelpersEip7594 miscHelpersEip7594 = MiscHelpersEip7594.required(spec.atSlot(blockContainer.getSlot()).miscHelpers()); return miscHelpersEip7594.constructDataColumnSidecars( - blockContainer.getSignedBlock(), - blockContainer.getBlobs().orElseThrow().asList(), - blockContainer.getKzgProofs().orElseThrow(), - kzg); + blockContainer.getSignedBlock(), blobs, kzg); } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java index 86ab74d39f4..df85bdc5580 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/BlockFactoryPhase0.java @@ -26,6 +26,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlockAndState; @@ -115,7 +116,7 @@ public List createBlobSidecars( @Override public List createDataColumnSidecars( - final SignedBlockContainer blockContainer) { + final SignedBlockContainer blockContainer, List blobs) { return Collections.emptyList(); } } diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java index 592ad3fcb79..4f2e1cb4be1 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/MilestoneBasedBlockFactory.java @@ -28,6 +28,7 @@ import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -111,9 +112,9 @@ public List createBlobSidecars( @Override public List createDataColumnSidecars( - final SignedBlockContainer blockContainer) { + final SignedBlockContainer blockContainer, List blobs) { final SpecMilestone milestone = getMilestone(blockContainer.getSlot()); - return registeredFactories.get(milestone).createDataColumnSidecars(blockContainer); + return registeredFactories.get(milestone).createDataColumnSidecars(blockContainer, blobs); } private SpecMilestone getMilestone(final UInt64 slot) { diff --git a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java index 3bd1e88989f..ae4156170f5 100644 --- a/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java +++ b/beacon/validator/src/main/java/tech/pegasys/teku/validator/coordinator/publisher/BlockPublisherEip7594.java @@ -18,6 +18,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.networking.eth2.gossip.BlockGossipChannel; import tech.pegasys.teku.networking.eth2.gossip.DataColumnSidecarGossipChannel; +import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -69,7 +70,9 @@ void publishBlockAndBlobSidecars( final List blobSidecars, BlockPublishingPerformance blockPublishingPerformance) { blockGossipChannel.publishBlock(block); - final List dataColumnSidecars = blockFactory.createDataColumnSidecars(block); + List blobs = blobSidecars.stream().map(BlobSidecar::getBlob).toList(); + final List dataColumnSidecars = + blockFactory.createDataColumnSidecars(block, blobs); dataColumnSidecarGossipChannel.publishDataColumnSidecars(dataColumnSidecars); blockPublishingPerformance.blockAndBlobSidecarsPublishingInitiated(); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index a15aae0a561..5e17d23e58c 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -24,10 +24,12 @@ import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.ssz.SszList; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.MerkleUtil; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellAndProof; import tech.pegasys.teku.kzg.KZGCellWithID; import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; @@ -148,10 +150,7 @@ public List computeDataColumnKzgCommitmentsInclusionProof( } public List constructDataColumnSidecars( - final SignedBeaconBlock signedBeaconBlock, - final List blobs, - final SszList sszKZGProofs, - final KZG kzg) { + final SignedBeaconBlock signedBeaconBlock, final List blobs, final KZG kzg) { if (blobs.isEmpty()) { return Collections.emptyList(); } @@ -161,38 +160,48 @@ public List constructDataColumnSidecars( final List kzgCommitmentsInclusionProof = computeDataColumnKzgCommitmentsInclusionProof(beaconBlockBody); - final List> extendedMatrix = computeExtendedMatrix(blobs, kzg); + final CellSchema cellSchema = schemaDefinitions.getCellSchema(); final DataColumnSchema dataColumnSchema = schemaDefinitions.getDataColumnSchema(); final DataColumnSidecarSchema dataColumnSidecarSchema = schemaDefinitions.getDataColumnSidecarSchema(); + final SszListSchema kzgProofsSchema = + dataColumnSidecarSchema.getKzgProofsSchema(); + + List> blobsCellsAndProofs = + blobs.stream().map(blob -> kzg.computeCellsAndProofs(blob.getBytes())).toList(); - return IntStream.range(0, extendedMatrix.get(0).size()) + int columnCount = blobsCellsAndProofs.get(0).size(); + + return IntStream.range(0, columnCount) .mapToObj( cellID -> { - final DataColumn dataColumn = - dataColumnSchema.create( - extendedMatrix.stream().map(row -> row.get(cellID)).toList()); + List columnData = + blobsCellsAndProofs.stream().map(row -> row.get(cellID)).toList(); + List columnCells = + columnData.stream() + .map(KZGCellAndProof::cell) + .map(KZGCell::bytes) + .map(cellSchema::create) + .toList(); + + SszList columnProofs = + columnData.stream() + .map(KZGCellAndProof::proof) + .map(SszKZGProof::new) + .collect(kzgProofsSchema.collector()); + final DataColumn dataColumn = dataColumnSchema.create(columnCells); + return dataColumnSidecarSchema.create( UInt64.valueOf(cellID), dataColumn, sszKZGCommitments, - sszKZGProofs, + columnProofs, signedBeaconBlock.asHeader(), kzgCommitmentsInclusionProof); }) .toList(); } - private List> computeExtendedMatrix(final List blobs, final KZG kzg) { - final CellSchema cellSchema = schemaDefinitions.getCellSchema(); - return blobs.stream() - .map(blob -> kzg.computeCells(blob.getBytes())) - .map( - kzgCellsList -> - kzgCellsList.stream().map(kzgCell -> cellSchema.create(kzgCell.bytes())).toList()) - .toList(); - } - @Override public Optional toVersionEip7594() { return Optional.of(this); From 2cf41a1bedd9a05273c650487f505aaebd2d66c5 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 3 May 2024 15:00:12 +0400 Subject: [PATCH 42/70] Speedup block production (via Stream.parallel()) (#33) --- .../teku/spec/datastructures/util/BlobsUtil.java | 10 ++++++++-- .../versions/eip7594/helpers/MiscHelpersEip7594.java | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/BlobsUtil.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/BlobsUtil.java index daa0b05188d..dc7817248bd 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/BlobsUtil.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/util/BlobsUtil.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Random; import java.util.stream.IntStream; +import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.crypto.Hash; @@ -75,7 +76,7 @@ public Bytes generateRawBlobTransactionFromKzgCommitments( } public List blobsToKzgCommitments(final List blobs) { - return blobs.stream().map(Blob::getBytes).map(kzg::blobToKzgCommitment).toList(); + return blobs.stream().parallel().map(Blob::getBytes).map(kzg::blobToKzgCommitment).toList(); } public KZGProof computeKzgProof(final Blob blob, final KZGCommitment kzgCommitment) { @@ -84,7 +85,12 @@ public KZGProof computeKzgProof(final Blob blob, final KZGCommitment kzgCommitme public List computeKzgProofs( final List blobs, final List kzgCommitments) { - return Streams.zip(blobs.stream(), kzgCommitments.stream(), this::computeKzgProof).toList(); + return Streams.zip(blobs.stream(), kzgCommitments.stream(), Pair::of) + .parallel() + .map( + blobAndCommitment -> + computeKzgProof(blobAndCommitment.getLeft(), blobAndCommitment.getRight())) + .toList(); } public List generateBlobs(final UInt64 slot, final int count) { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 5e17d23e58c..21633c99d1f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -168,7 +168,7 @@ public List constructDataColumnSidecars( dataColumnSidecarSchema.getKzgProofsSchema(); List> blobsCellsAndProofs = - blobs.stream().map(blob -> kzg.computeCellsAndProofs(blob.getBytes())).toList(); + blobs.stream().parallel().map(blob -> kzg.computeCellsAndProofs(blob.getBytes())).toList(); int columnCount = blobsCellsAndProofs.get(0).size(); From 7097eab2b386176f54507c32440b180c36350333 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 3 May 2024 19:05:44 +0400 Subject: [PATCH 43/70] Disable BlobSidecars availability check in the post Deneb milestones (#34) * Disable BlobSidecars availability check in the post Deneb milestones * More accurate logging --- .../main/java/tech/pegasys/teku/spec/Spec.java | 16 ++++++++-------- .../versions/deneb/helpers/MiscHelpersDeneb.java | 9 +++++++++ .../eip7594/helpers/MiscHelpersEip7594.java | 5 +++++ .../gossip/DataColumnSidecarGossipManager.java | 7 +++++-- .../rpc/core/Eth2IncomingRequestHandler.java | 2 +- 5 files changed, 28 insertions(+), 11 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index 1642035525a..cb50bc64396 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -932,14 +932,14 @@ public boolean isAvailabilityOfBlobSidecarsRequiredAtSlot( public boolean isAvailabilityOfBlobSidecarsRequiredAtEpoch( final ReadOnlyStore store, final UInt64 epoch) { - if (!forkSchedule.getSpecMilestoneAtEpoch(epoch).isGreaterThanOrEqualTo(DENEB)) { - return false; - } - final SpecConfig config = atEpoch(epoch).getConfig(); - final SpecConfigDeneb specConfigDeneb = SpecConfigDeneb.required(config); - return getCurrentEpoch(store) - .minusMinZero(epoch) - .isLessThanOrEqualTo(specConfigDeneb.getMinEpochsForBlobSidecarsRequests()); + return atEpoch(epoch) + .miscHelpers() + .toVersionDeneb() + .map( + denebMiscHelpers -> + denebMiscHelpers.isAvailabilityOfBlobSidecarsRequiredAtEpoch( + getCurrentEpoch(store), epoch)) + .orElse(false); } public Optional getMaxBlobsPerBlock() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java index c1e76cec904..4b1404f6c2f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/deneb/helpers/MiscHelpersDeneb.java @@ -51,6 +51,7 @@ public class MiscHelpersDeneb extends MiscHelpersCapella { private final Predicates predicates; private final BeaconBlockBodySchemaDeneb beaconBlockBodySchema; private final BlobSidecarSchema blobSidecarSchema; + private final SpecConfigDeneb specConfigDeneb; public static MiscHelpersDeneb required(final MiscHelpers miscHelpers) { return miscHelpers @@ -67,6 +68,7 @@ public MiscHelpersDeneb( final Predicates predicates, final SchemaDefinitionsDeneb schemaDefinitions) { super(specConfig); + this.specConfigDeneb = specConfig; this.predicates = predicates; this.beaconBlockBodySchema = (BeaconBlockBodySchemaDeneb) schemaDefinitions.getBeaconBlockBodySchema(); @@ -269,4 +271,11 @@ public boolean verifyBlobSidecarMerkleProof(final BlobSidecar blobSidecar) { getBlobSidecarKzgCommitmentGeneralizedIndex(blobSidecar.getIndex()), blobSidecar.getBlockBodyRoot()); } + + public boolean isAvailabilityOfBlobSidecarsRequiredAtEpoch( + final UInt64 currentEpoch, final UInt64 epoch) { + return currentEpoch + .minusMinZero(epoch) + .isLessThanOrEqualTo(specConfigDeneb.getMinEpochsForBlobSidecarsRequests()); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 21633c99d1f..8a7e317d502 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -202,6 +202,11 @@ public List constructDataColumnSidecars( .toList(); } + @Override + public boolean isAvailabilityOfBlobSidecarsRequiredAtEpoch(UInt64 currentEpoch, UInt64 epoch) { + return false; + } + @Override public Optional toVersionEip7594() { return Optional.of(this); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java index 9cc2332e1d2..e8ef4f42b57 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/DataColumnSidecarGossipManager.java @@ -34,10 +34,13 @@ public void publish(final DataColumnSidecar dataColumnSidecar) { .finish( __ -> { LOG.debug( - "Successfully published data column sidecar for slot {}", dataColumnSidecar); + "Successfully published data column sidecar for slot {}", + dataColumnSidecar.toLogString()); }, error -> { - LOG.warn("Error publishing data column sidecar for slot {}", dataColumnSidecar); + LOG.warn( + "Error publishing data column sidecar for slot {}", + dataColumnSidecar.toLogString()); }); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/Eth2IncomingRequestHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/Eth2IncomingRequestHandler.java index cdee3e07f62..7e9587d8660 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/Eth2IncomingRequestHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/core/Eth2IncomingRequestHandler.java @@ -91,7 +91,7 @@ public void readComplete(NodeId nodeId, RpcStream rpcStream) { .ifPresent(request -> handleRequest(peer, request, createResponseCallback(rpcStream))); } catch (RpcException e) { createResponseCallback(rpcStream).completeWithErrorResponse(e); - LOG.debug("RPC Request stream closed prematurely", e); + LOG.debug("RPC Request stream closed prematurely {}", protocolId, e); } } From 3ac6cc567ee45ba7f2e6cd336720ed636e13e5b7 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 3 May 2024 23:12:08 +0400 Subject: [PATCH 44/70] Integrate DataColumnSidecarCustody for storing gossip inbound/outbound sidecars (#35) * Integrate DataColumnSidecarCustody for storing gossip inbound/outbound sidecars * Add suitable sidecars only in DataColumnSidecarCustodyImpl * Change initialization sequence * Rename to BlockRootResolver * Remove stinky comment :) * Skip initing DasCustody if milestone is not supported --- .../datacolumns/CustodySync.java | 5 +- .../datacolumns/DataColumnSidecarCustody.java | 5 -- .../DataColumnSidecarCustodyImpl.java | 47 ++++++++++------ .../datacolumns/DataColumnSidecarManager.java | 29 +++++++--- .../DataColumnSidecarManagerImpl.java | 56 +++++++++++++++++++ .../UpdatableDataColumnSidecarCustody.java | 24 ++++++++ .../beaconchain/BeaconChainController.java | 56 ++++++++++++++++--- 7 files changed, 184 insertions(+), 38 deletions(-) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManagerImpl.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/UpdatableDataColumnSidecarCustody.java diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java index e6cc978faf3..e97cf950a7a 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java @@ -25,7 +25,7 @@ public class CustodySync implements SlotEventsChannel { - private final DataColumnSidecarCustody custody; + private final UpdatableDataColumnSidecarCustody custody; private final DataColumnSidecarRetriever retriever; private final int maxPendingColumnRequests = 1024; private final int minPendingColumnRequests = 512; @@ -33,7 +33,8 @@ public class CustodySync implements SlotEventsChannel { private Map pendingRequests = new HashMap<>(); private boolean started = false; - public CustodySync(DataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { + public CustodySync( + UpdatableDataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { this.custody = custody; this.retriever = retriever; } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java index 1cf0253b6cf..a189542dd8e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustody.java @@ -14,17 +14,12 @@ package tech.pegasys.teku.statetransition.datacolumns; import java.util.Optional; -import java.util.stream.Stream; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public interface DataColumnSidecarCustody { - void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar); - SafeFuture> getCustodyDataColumnSidecar( DataColumnIdentifier columnId); - - Stream streamMissingColumns(); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 9cb1183ffee..df4fbcc279b 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -27,14 +27,15 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; -import tech.pegasys.teku.storage.client.CombinedChainDataClient; -public class DataColumnSidecarCustodyImpl implements DataColumnSidecarCustody, SlotEventsChannel { +import static com.google.common.base.Preconditions.checkNotNull; - public interface BlockChainAccessor { +public class DataColumnSidecarCustodyImpl + implements UpdatableDataColumnSidecarCustody, SlotEventsChannel { + + public interface BlockRootResolver { Optional getCanonicalBlockRootAtSlot(UInt64 slot); } @@ -71,7 +72,7 @@ public boolean isIncomplete() { private final Spec spec; private final DataColumnSidecarDB db; - private final BlockChainAccessor blockChainAccessor; + private final BlockRootResolver blockRootResolver; private final UInt256 nodeId; private final int totalCustodySubnetCount; @@ -81,20 +82,19 @@ public boolean isIncomplete() { public DataColumnSidecarCustodyImpl( Spec spec, - CombinedChainDataClient combinedChainDataClient, + BlockRootResolver blockRootResolver, DataColumnSidecarDB db, - BlockChainAccessor blockChainAccessor, UInt256 nodeId, int totalCustodySubnetCount) { + + checkNotNull(spec); + checkNotNull(blockRootResolver); + checkNotNull(db); + checkNotNull(nodeId); + this.spec = spec; this.db = db; - // FIXME: I stink! - this.blockChainAccessor = - slot -> - combinedChainDataClient - .getBlockAtSlotExact(slot) - .thenApply(maybeBlock -> maybeBlock.map(SignedBeaconBlock::getRoot)) - .join(); + this.blockRootResolver = blockRootResolver; this.nodeId = nodeId; this.totalCustodySubnetCount = totalCustodySubnetCount; this.eip7594StartEpoch = spec.getForkSchedule().getFork(SpecMilestone.EIP7594).getEpoch(); @@ -125,7 +125,22 @@ private Set getCustodyColumnsForEpoch(UInt64 epoch) { @Override public void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar) { - db.addSidecar(dataColumnSidecar); + if (isMyCustody(dataColumnSidecar.getSlot(), dataColumnSidecar.getIndex())) { + db.addSidecar(dataColumnSidecar); + } + } + + private boolean isMyCustody(UInt64 slot, UInt64 columnIndex) { + UInt64 epoch = spec.computeEpochAtSlot(slot); + return spec.atEpoch(epoch) + .miscHelpers() + .toVersionEip7594() + .map( + miscHelpersEip7594 -> + miscHelpersEip7594 + .computeCustodyColumnIndexes(nodeId, epoch, totalCustodySubnetCount) + .contains(columnIndex)) + .orElse(false); } @Override @@ -171,7 +186,7 @@ private Stream streamSlotCustodies() { .map( slot -> { Optional maybeCanonicalBlockRoot = - blockChainAccessor.getCanonicalBlockRootAtSlot(slot); + blockRootResolver.getCanonicalBlockRootAtSlot(slot); Set requiredColumns = getCustodyColumnsForSlot(slot); List existingColumns = db.streamColumnIdentifiers(slot).toList(); diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java index 5d243db4715..b4d3eaedb1b 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManager.java @@ -17,19 +17,34 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; -import tech.pegasys.teku.statetransition.validation.DataColumnSidecarGossipValidator; import tech.pegasys.teku.statetransition.validation.InternalValidationResult; public interface DataColumnSidecarManager { - public static DataColumnSidecarManager NOOP = - (sidecar, arrivalTimestamp) -> SafeFuture.completedFuture(InternalValidationResult.ACCEPT); - - public static DataColumnSidecarManager create(DataColumnSidecarGossipValidator validator) { - // TODO - return (sidecar, arrivalTimestamp) -> validator.validate(sidecar); + interface ValidDataColumnSidecarsListener { + void onNewValidSidecar(DataColumnSidecar sidecar); } + DataColumnSidecarManager NOOP = + new DataColumnSidecarManager() { + @Override + public SafeFuture onDataColumnSidecarGossip( + DataColumnSidecar sidecar, Optional arrivalTimestamp) { + return SafeFuture.completedFuture(InternalValidationResult.ACCEPT); + } + + @Override + public void onDataColumnSidecarPublish(DataColumnSidecar sidecar) {} + + @Override + public void subscribeToValidDataColumnSidecars( + ValidDataColumnSidecarsListener sidecarsListener) {} + }; + + void onDataColumnSidecarPublish(DataColumnSidecar sidecar); + SafeFuture onDataColumnSidecarGossip( DataColumnSidecar sidecar, Optional arrivalTimestamp); + + void subscribeToValidDataColumnSidecars(ValidDataColumnSidecarsListener sidecarsListener); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManagerImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManagerImpl.java new file mode 100644 index 00000000000..921632a6147 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarManagerImpl.java @@ -0,0 +1,56 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.subscribers.Subscribers; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.statetransition.validation.DataColumnSidecarGossipValidator; +import tech.pegasys.teku.statetransition.validation.InternalValidationResult; + +public class DataColumnSidecarManagerImpl implements DataColumnSidecarManager { + + private final DataColumnSidecarGossipValidator validator; + private final Subscribers validDataColumnSidecarsSubscribers = + Subscribers.create(true); + + public DataColumnSidecarManagerImpl(DataColumnSidecarGossipValidator validator) { + this.validator = validator; + } + + @Override + public SafeFuture onDataColumnSidecarGossip( + DataColumnSidecar sidecar, Optional arrivalTimestamp) { + return validator + .validate(sidecar) + .thenPeek( + res -> { + if (res.isAccept()) { + validDataColumnSidecarsSubscribers.forEach(l -> l.onNewValidSidecar(sidecar)); + } + }); + } + + @Override + public void onDataColumnSidecarPublish(DataColumnSidecar sidecar) { + validDataColumnSidecarsSubscribers.forEach(l -> l.onNewValidSidecar(sidecar)); + } + + @Override + public void subscribeToValidDataColumnSidecars(ValidDataColumnSidecarsListener sidecarsListener) { + validDataColumnSidecarsSubscribers.subscribe(sidecarsListener); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/UpdatableDataColumnSidecarCustody.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/UpdatableDataColumnSidecarCustody.java new file mode 100644 index 00000000000..00468e07ab4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/UpdatableDataColumnSidecarCustody.java @@ -0,0 +1,24 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.stream.Stream; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; + +public interface UpdatableDataColumnSidecarCustody extends DataColumnSidecarCustody { + + void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar); + + Stream streamMissingColumns(); +} diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 732ac2226b1..f80833cb538 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -34,6 +34,7 @@ import java.util.Optional; import java.util.function.Function; import java.util.function.IntSupplier; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; @@ -96,6 +97,7 @@ import tech.pegasys.teku.services.timer.TimerService; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -135,7 +137,11 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustodyImpl; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarDBImpl; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManager; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManagerImpl; import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifier; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifierImpl; @@ -281,6 +287,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile KZG kzg; protected volatile BlobSidecarManager blobSidecarManager; protected volatile DataColumnSidecarManager dataColumnSidecarManager; + protected volatile DataColumnSidecarCustody dataColumnSidecarCustody; protected volatile Optional terminalPowBlockMonitor = Optional.empty(); protected volatile ProposersDataManager proposersDataManager; protected volatile KeyValueStore keyValueStore; @@ -295,6 +302,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected PoolFactory poolFactory; protected SettableLabelledGauge futureItemsMetric; protected IntSupplier rejectedExecutionCountSupplier; + protected volatile UInt256 nodeId; public BeaconChainController( final ServiceConfig serviceConfig, final BeaconChainConfiguration beaconConfig) { @@ -516,6 +524,7 @@ public void initAll() { initBlockManager(); initSyncCommitteePools(); initP2PNetwork(); + initDasCustody(); initSyncService(); initSlotProcessor(); initMetrics(); @@ -593,12 +602,45 @@ protected void initDataColumnSidecarManager() { DataColumnSidecarValidator dataColumnSidecarValidator = DataColumnSidecarValidator.create(); DataColumnSidecarGossipValidator gossipValidator = DataColumnSidecarGossipValidator.create(dataColumnSidecarValidator); - dataColumnSidecarManager = DataColumnSidecarManager.create(gossipValidator); + dataColumnSidecarManager = new DataColumnSidecarManagerImpl(gossipValidator); + eventChannels.subscribe( + DataColumnSidecarGossipChannel.class, + dataColumnSidecarManager::onDataColumnSidecarPublish); } else { dataColumnSidecarManager = DataColumnSidecarManager.NOOP; } } + protected void initDasCustody() { + if (!spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + return; + } + DataColumnSidecarDBImpl sidecarDB = + new DataColumnSidecarDBImpl( + combinedChainDataClient, eventChannels.getPublisher(SidecarUpdateChannel.class)); + DataColumnSidecarCustodyImpl.BlockRootResolver blockRootResolver = + slot -> + combinedChainDataClient + .getBlockAtSlotExact(slot) + .thenApply(maybeBlock -> maybeBlock.map(SignedBeaconBlock::getRoot)) + .join(); + + int dasExtraCustodySubnetCount = beaconConfig.p2pConfig().getDasExtraCustodySubnetCount(); + SpecConfigEip7594 configEip7594 = + SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); + int custodyRequirement = configEip7594.getCustodyRequirement(); + int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); + int totalCustodySubnets = + Integer.min(maxSubnets, custodyRequirement + dasExtraCustodySubnetCount); + + DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = + new DataColumnSidecarCustodyImpl( + spec, blockRootResolver, sidecarDB, nodeId, totalCustodySubnets); + dataColumnSidecarManager.subscribeToValidDataColumnSidecars( + dataColumnSidecarCustodyImpl::onNewValidatedDataColumnSidecar); + this.dataColumnSidecarCustody = dataColumnSidecarCustodyImpl; + } + protected void initMergeMonitors() { if (spec.isMilestoneSupported(SpecMilestone.BELLATRIX)) { terminalPowBlockMonitor = @@ -897,13 +939,6 @@ protected void initSubnetSubscriber() { protected void initDataColumnSidecarSubnetBackboneSubscriber() { LOG.debug("BeaconChainController.initDataColumnSidecarSubnetBackboneSubscriber"); - UInt256 nodeId = - p2pNetwork - .getDiscoveryNodeId() - .orElseThrow( - () -> - new InvalidConfigurationException( - "NodeID is required for DataColumnSidecarSubnetBackboneSubscriber")); DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = new DataColumnSidecarSubnetBackboneSubscriber( spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); @@ -1167,6 +1202,11 @@ protected void initP2PNetwork() { new LocalOperationAcceptedFilter<>(p2pNetwork::publishVoluntaryExit)); blsToExecutionChangePool.subscribeOperationAdded( new LocalOperationAcceptedFilter<>(p2pNetwork::publishSignedBlsToExecutionChange)); + + nodeId = + p2pNetwork + .getDiscoveryNodeId() + .orElseThrow(() -> new InvalidConfigurationException("NodeID is required")); } protected Eth2P2PNetworkBuilder createEth2P2PNetworkBuilder() { From 3c38ebc99572848a9fe14dd7c8844bec1930f4fb Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 7 May 2024 17:53:20 +0400 Subject: [PATCH 45/70] Reference tests for EIP-7594 + utils implementation (#36) * eference tests 1.5.0.alpha.1 with EIP-7594 coverage activated * computeCustodyColumnIndexes implemented * verifyCellProofBatch implemented, it fails badly on some tests, so it's disabled. jc-kzg update should help I think --- build.gradle | 2 +- .../teku/reference/Eth2ReferenceTestCase.java | 8 ++ .../pegasys/teku/reference/KzgRetriever.java | 7 +- .../operations/OperationsTestExecutor.java | 15 +-- .../SingleMerkleProofTestExecutor.java | 51 +++++++- .../GetCustodyColumnsTestExecutor.java | 67 +++++++++++ .../eip7594/networking/NetworkingTests.java | 24 ++++ ...gComputeCellsAndKzgProofsTestExecutor.java | 75 ++++++++++++ .../kzg/KzgComputeCellsTestExecutor.java | 69 +++++++++++ .../kzg/KzgRecoverAllCellsTestExecutor.java | 87 ++++++++++++++ .../reference/phase0/kzg/KzgTestExecutor.java | 13 +++ .../teku/reference/phase0/kzg/KzgTests.java | 9 ++ ...zgVerifyCellKzgProofBatchTestExecutor.java | 110 ++++++++++++++++++ .../KzgVerifyCellKzgProofTestExecutor.java | 95 +++++++++++++++ .../phase0/ssz_static/SszTestExecutor.java | 12 ++ .../logic/common/helpers/MathHelpers.java | 9 ++ .../eip7594/helpers/MiscHelpersEip7594.java | 64 +++++++--- .../DataColumnSidecarCustodyImpl.java | 8 +- .../retriever/SimpleSidecarRetriever.java | 4 +- .../java/tech/pegasys/teku/kzg/CKZG4844.java | 29 ++++- .../main/java/tech/pegasys/teku/kzg/KZG.java | 28 +++-- .../java/tech/pegasys/teku/kzg/KZGCellID.java | 2 +- ...llWithID.java => KZGCellWithColumnId.java} | 6 +- .../tech/pegasys/teku/kzg/KZGCellWithIds.java | 30 +++++ .../tech/pegasys/teku/kzg/CKZG4844Test.java | 8 +- .../beaconchain/BeaconChainController.java | 1 - 26 files changed, 771 insertions(+), 62 deletions(-) create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/NetworkingTests.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsAndKzgProofsTestExecutor.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsTestExecutor.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgRecoverAllCellsTestExecutor.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofBatchTestExecutor.java create mode 100644 eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofTestExecutor.java rename infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/{KZGCellWithID.java => KZGCellWithColumnId.java} (72%) create mode 100644 infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithIds.java diff --git a/build.gradle b/build.gradle index 5124105b7f4..3f9010f670a 100644 --- a/build.gradle +++ b/build.gradle @@ -295,7 +295,7 @@ allprojects { } } -def refTestVersion = 'v1.4.0' // Arbitrary change to refresh cache number: 1 +def refTestVersion = 'v1.5.0-alpha.1' // Arbitrary change to refresh cache number: 1 def blsRefTestVersion = 'v0.1.2' def refTestBaseUrl = 'https://github.com/ethereum/consensus-spec-tests/releases/download' def blsRefTestBaseUrl = 'https://github.com/ethereum/bls12-381-tests/releases/download' diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java index 2be8f4414ea..4b21f61f7f3 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/Eth2ReferenceTestCase.java @@ -24,6 +24,7 @@ import tech.pegasys.teku.reference.common.epoch_processing.EpochProcessingTestExecutor; import tech.pegasys.teku.reference.common.operations.OperationsTestExecutor; import tech.pegasys.teku.reference.deneb.merkle_proof.MerkleProofTests; +import tech.pegasys.teku.reference.eip7594.networking.NetworkingTests; import tech.pegasys.teku.reference.phase0.bls.BlsTests; import tech.pegasys.teku.reference.phase0.forkchoice.ForkChoiceTestExecutor; import tech.pegasys.teku.reference.phase0.genesis.GenesisTests; @@ -87,6 +88,12 @@ public abstract class Eth2ReferenceTestCase { .putAll(MerkleProofTests.MERKLE_PROOF_TEST_TYPES) .build(); + private static final ImmutableMap EIP7594_TEST_TYPES = + ImmutableMap.builder() + .putAll(MerkleProofTests.MERKLE_PROOF_TEST_TYPES) + .putAll(NetworkingTests.NETWORKING_TEST_TYPES) + .build(); + protected void runReferenceTest(final TestDefinition testDefinition) throws Throwable { getExecutorFor(testDefinition).runTest(testDefinition); } @@ -100,6 +107,7 @@ private TestExecutor getExecutorFor(final TestDefinition testDefinition) { case TestFork.BELLATRIX -> BELLATRIX_TEST_TYPES.get(testDefinition.getTestType()); case TestFork.CAPELLA -> CAPELLA_TEST_TYPES.get(testDefinition.getTestType()); case TestFork.DENEB -> DENEB_TEST_TYPES.get(testDefinition.getTestType()); + case TestFork.EIP7594 -> EIP7594_TEST_TYPES.get(testDefinition.getTestType()); default -> null; }; diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/KzgRetriever.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/KzgRetriever.java index 73e5ee9fe66..b7dbf06e939 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/KzgRetriever.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/KzgRetriever.java @@ -25,10 +25,11 @@ public class KzgRetriever { private static final Map TRUSTED_SETUP_FILES_BY_NETWORK = Maps.newHashMap(); public static KZG getKzgWithLoadedTrustedSetup(final Spec spec, final String network) { - if (!spec.isMilestoneSupported(SpecMilestone.DENEB)) { - return KZG.NOOP; + if (spec.isMilestoneSupported(SpecMilestone.DENEB) + || spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + return getKzgWithLoadedTrustedSetup(network); } - return getKzgWithLoadedTrustedSetup(network); + return KZG.NOOP; } public static KZG getKzgWithLoadedTrustedSetup(final String network) { diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java index b19ce0bb0d6..61caa502ae2 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/common/operations/OperationsTestExecutor.java @@ -71,9 +71,7 @@ private enum Operation { SYNC_AGGREGATE, EXECUTION_PAYLOAD, BLS_TO_EXECUTION_CHANGE, - WITHDRAWAL, - DEPOSIT_RECEIPT, - EXECUTION_LAYER_EXIT + WITHDRAWAL } public static final ImmutableMap OPERATIONS_TEST_TYPES = @@ -114,13 +112,6 @@ private enum Operation { .put( "operations/withdrawals", new OperationsTestExecutor<>("execution_payload.ssz_snappy", Operation.WITHDRAWAL)) - .put( - "operations/deposit_receipt", - new OperationsTestExecutor<>("deposit_receipt.ssz_snappy", Operation.DEPOSIT_RECEIPT)) - .put( - "operations/execution_layer_exit", - new OperationsTestExecutor<>( - "execution_layer_exit.ssz_snappy", Operation.EXECUTION_LAYER_EXIT)) .build(); private final String dataFileName; @@ -396,9 +387,7 @@ public void checkBlockInclusionValidation( ATTESTATION, SYNC_AGGREGATE, EXECUTION_PAYLOAD, - WITHDRAWAL, - DEPOSIT_RECEIPT, - EXECUTION_LAYER_EXIT -> {} + WITHDRAWAL -> {} } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java index faf230165bd..f9a735dc87b 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/deneb/merkle_proof/SingleMerkleProofTestExecutor.java @@ -32,10 +32,12 @@ import tech.pegasys.teku.reference.TestDataUtils; import tech.pegasys.teku.reference.TestExecutor; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; import tech.pegasys.teku.spec.datastructures.type.SszKZGCommitment; import tech.pegasys.teku.spec.logic.common.helpers.Predicates; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; public class SingleMerkleProofTestExecutor implements TestExecutor { private static final Pattern TEST_NAME_PATTERN = Pattern.compile("(.+)/(.+)"); @@ -88,9 +90,12 @@ void runBeaconBlockBodyTest( testDefinition, OBJECT_SSZ_FILE, testDefinition.getSpec().getGenesisSchemaDefinitions().getBeaconBlockBodySchema()); - + // Deneb if (proofType.startsWith("blob_kzg_commitment_merkle_proof")) { runBlobKzgCommitmentMerkleProofTest(testDefinition, data, beaconBlockBody); + // EIP-7594 + } else if (proofType.startsWith("blob_kzg_commitments_merkle_proof")) { + runBlobKzgCommitmentsMerkleProofTest(testDefinition, data, beaconBlockBody); } else { throw new RuntimeException("Unknown proof type " + proofType); } @@ -135,6 +140,30 @@ private void runBlobKzgCommitmentMerkleProofTest( .isEqualTo(data.branch.stream().map(Bytes32::fromHexString).toList()); } + private void runBlobKzgCommitmentsMerkleProofTest( + final TestDefinition testDefinition, final Data data, final BeaconBlockBody beaconBlockBody) { + final Predicates predicates = new Predicates(testDefinition.getSpec().getGenesisSpecConfig()); + final Bytes32 kzgCommitmentsHash = Bytes32.fromHexString(data.leaf); + + // Forward check + assertThat( + predicates.isValidMerkleBranch( + kzgCommitmentsHash, + createKzgCommitmentsMerkleProofBranchFromData(testDefinition, data.branch), + getKzgCommitmentsInclusionProofDepth(testDefinition), + data.leafIndex, + beaconBlockBody.hashTreeRoot())) + .isTrue(); + + // Verify 2 MiscHelpersEip7594 helpers + final MiscHelpersEip7594 miscHelpersEip7594 = + MiscHelpersEip7594.required(testDefinition.getSpec().getGenesisSpec().miscHelpers()); + assertThat(miscHelpersEip7594.getBlockBodyKzgCommitmentsGeneralizedIndex()) + .isEqualTo(data.leafIndex); + assertThat(miscHelpersEip7594.computeDataColumnKzgCommitmentsInclusionProof(beaconBlockBody)) + .isEqualTo(data.branch.stream().map(Bytes32::fromHexString).toList()); + } + private SszBytes32Vector createKzgCommitmentMerkleProofBranchFromData( final TestDefinition testDefinition, final List branch) { final SszBytes32VectorSchema kzgCommitmentInclusionProofSchema = @@ -153,4 +182,24 @@ private int getKzgCommitmentInclusionProofDepth(final TestDefinition testDefinit return SpecConfigDeneb.required(testDefinition.getSpec().getGenesisSpecConfig()) .getKzgCommitmentInclusionProofDepth(); } + + private SszBytes32Vector createKzgCommitmentsMerkleProofBranchFromData( + final TestDefinition testDefinition, final List branch) { + final SszBytes32VectorSchema kzgCommitmentsInclusionProofSchema = + testDefinition + .getSpec() + .getGenesisSchemaDefinitions() + .toVersionEip7594() + .orElseThrow() + .getDataColumnSidecarSchema() + .getKzgCommitmentsInclusionProofSchema(); + return kzgCommitmentsInclusionProofSchema.createFromElements( + branch.stream().map(Bytes32::fromHexString).map(SszBytes32::of).toList()); + } + + private int getKzgCommitmentsInclusionProofDepth(final TestDefinition testDefinition) { + return SpecConfigEip7594.required(testDefinition.getSpec().getGenesisSpecConfig()) + .getKzgCommitmentsInclusionProofDepth() + .intValue(); + } } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java new file mode 100644 index 00000000000..201d46add2e --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java @@ -0,0 +1,67 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.eip7594.networking; + +import static org.assertj.core.api.Assertions.assertThat; +import static tech.pegasys.teku.reference.TestDataUtils.loadYaml; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigInteger; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.reference.TestExecutor; +import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; + +public class GetCustodyColumnsTestExecutor implements TestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition) throws Exception { + final GetCustodyColumnsMetaData metaData = + loadYaml(testDefinition, "meta.yaml", GetCustodyColumnsMetaData.class); + final SpecVersion spec = testDefinition.getSpec().getGenesisSpec(); + final Set actualResult = + MiscHelpersEip7594.required(spec.miscHelpers()) + .computeCustodyColumnIndexes(metaData.getNodeId(), metaData.getCustodySubnetCount()); + assertThat(actualResult).isEqualTo(metaData.getResult()); + } + + private static class GetCustodyColumnsMetaData { + + @JsonProperty(value = "node_id", required = true) + private String nodeId; + + @JsonProperty(value = "custody_subnet_count", required = true) + private int custodySubnetCount; + + @JsonProperty(value = "result", required = true) + private List result; + + public UInt256 getNodeId() { + return UInt256.valueOf(new BigInteger(nodeId)); + } + + public int getCustodySubnetCount() { + return custodySubnetCount; + } + + public Set getResult() { + return result.stream().map(UInt64::valueOf).collect(Collectors.toUnmodifiableSet()); + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/NetworkingTests.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/NetworkingTests.java new file mode 100644 index 00000000000..264d9426082 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/NetworkingTests.java @@ -0,0 +1,24 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.eip7594.networking; + +import com.google.common.collect.ImmutableMap; +import tech.pegasys.teku.reference.TestExecutor; + +public class NetworkingTests { + public static final ImmutableMap NETWORKING_TEST_TYPES = + ImmutableMap.builder() + .put("networking/get_custody_columns", new GetCustodyColumnsTestExecutor()) + .build(); +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsAndKzgProofsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsAndKzgProofsTestExecutor.java new file mode 100644 index 00000000000..98caa1ff012 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsAndKzgProofsTestExecutor.java @@ -0,0 +1,75 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.phase0.kzg; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Streams; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellAndProof; +import tech.pegasys.teku.kzg.KZGProof; + +public class KzgComputeCellsAndKzgProofsTestExecutor extends KzgTestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition, final KZG kzg) throws Throwable { + final Data data = loadDataFile(testDefinition, Data.class); + final List expectedKzgCellsAndProofs = data.getOutput(); + List actualKzgCellsAndProofs; + try { + final Bytes blob = data.getInput().getBlob(); + actualKzgCellsAndProofs = kzg.computeCellsAndProofs(blob); + } catch (final RuntimeException ex) { + actualKzgCellsAndProofs = null; + } + assertThat(actualKzgCellsAndProofs).isEqualTo(expectedKzgCellsAndProofs); + } + + private static class Data { + @JsonProperty(value = "input", required = true) + private Input input; + + @JsonProperty(value = "output", required = true) + private List[] output; + + public Input getInput() { + return input; + } + + public List getOutput() { + return output == null + ? null + : Streams.zip( + output[0].stream() + .map(cellString -> new KZGCell(Bytes.fromHexString(cellString))), + output[1].stream().map(KZGProof::fromHexString), + KZGCellAndProof::new) + .toList(); + } + + private static class Input { + @JsonProperty(value = "blob", required = true) + private String blob; + + public Bytes getBlob() { + return Bytes.fromHexString(blob); + } + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsTestExecutor.java new file mode 100644 index 00000000000..879ed90bd44 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgComputeCellsTestExecutor.java @@ -0,0 +1,69 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.phase0.kzg; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; + +public class KzgComputeCellsTestExecutor extends KzgTestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition, final KZG kzg) throws Throwable { + final Data data = loadDataFile(testDefinition, Data.class); + final List expectedKzgCells = data.getOutput(); + List actualKzgCells; + try { + final Bytes blob = data.getInput().getBlob(); + actualKzgCells = kzg.computeCells(blob); + } catch (final RuntimeException ex) { + actualKzgCells = null; + } + assertThat(actualKzgCells).isEqualTo(expectedKzgCells); + } + + private static class Data { + @JsonProperty(value = "input", required = true) + private Input input; + + @JsonProperty(value = "output", required = true) + private List output; + + public Input getInput() { + return input; + } + + public List getOutput() { + return output == null + ? null + : output.stream() + .map(cellString -> new KZGCell(Bytes.fromHexString(cellString))) + .toList(); + } + + private static class Input { + @JsonProperty(value = "blob", required = true) + private String blob; + + public Bytes getBlob() { + return Bytes.fromHexString(blob); + } + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgRecoverAllCellsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgRecoverAllCellsTestExecutor.java new file mode 100644 index 00000000000..dbc75d2e572 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgRecoverAllCellsTestExecutor.java @@ -0,0 +1,87 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.phase0.kzg; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.collect.Streams; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellWithColumnId; + +public class KzgRecoverAllCellsTestExecutor extends KzgTestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition, final KZG kzg) throws Throwable { + final Data data = loadDataFile(testDefinition, Data.class); + final List expectedKzgCells = data.getOutput(); + List actualKzgCells; + try { + final List cellIds = data.getInput().getCellIds(); + final List cells = data.getInput().getCells(); + if (cells.size() != cellIds.size()) { + throw new RuntimeException("Cells doesn't match ids"); + } + final List cellWithIds = + Streams.zip(cells.stream(), cellIds.stream(), KZGCellWithColumnId::fromCellAndColumn) + .toList(); + actualKzgCells = kzg.recoverCells(cellWithIds); + } catch (final RuntimeException ex) { + actualKzgCells = null; + } + assertThat(actualKzgCells).isEqualTo(expectedKzgCells); + } + + private static class Data { + @JsonProperty(value = "input", required = true) + private Input input; + + @JsonProperty(value = "output", required = true) + private List output; + + public Input getInput() { + return input; + } + + public List getOutput() { + return output == null + ? null + : output.stream() + .map(cellString -> new KZGCell(Bytes.fromHexString(cellString))) + .toList(); + } + + private static class Input { + @JsonProperty(value = "cell_ids", required = true) + private List cellIds; + + @JsonProperty(value = "cells", required = true) + private List cells; + + public List getCellIds() { + return cellIds; + } + + public List getCells() { + return cells.stream() + .map(cellString -> new KZGCell(Bytes.fromHexString(cellString))) + .toList(); + } + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java index a67a07d11bf..2c014a29094 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java @@ -16,6 +16,7 @@ import static tech.pegasys.teku.ethtests.finder.KzgTestFinder.KZG_DATA_FILE; import java.io.IOException; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import tech.pegasys.teku.ethtests.finder.TestDefinition; @@ -27,9 +28,21 @@ public abstract class KzgTestExecutor implements TestExecutor { private static final Pattern TEST_NAME_PATTERN = Pattern.compile("kzg-(.+)/.+"); + // TODO: update kzg and retest, should help + private static final List IGNORED_TESTS = + List.of( + "verify_cell_kzg_proof_batch_case_valid_21b209cb4f64d0ed", + "verify_cell_kzg_proof_batch_case_valid_7dc4b00d04efff0c", + "verify_cell_kzg_proof_batch_case_valid_fad5448f3ceb0978", + "verify_cell_kzg_proof_batch_case_valid_unused_row_commitments_bc80af6ef27f8129"); @Override public final void runTest(final TestDefinition testDefinition) throws Throwable { + final int lastSlash = testDefinition.getTestName().lastIndexOf("/"); + final String testName = testDefinition.getTestName().substring(lastSlash + 1); + if (IGNORED_TESTS.contains(testName)) { + return; + } final String network = extractNetwork(testDefinition.getTestName()); final KZG kzg = KzgRetriever.getKzgWithLoadedTrustedSetup(network); runTest(testDefinition, kzg); diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTests.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTests.java index dcded15e3f1..d444fde440a 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTests.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTests.java @@ -20,6 +20,8 @@ public class KzgTests { public static final ImmutableMap KZG_TEST_TYPES = ImmutableMap.builder() + + // BlobSidecar Deneb utils .put("kzg/blob_to_kzg_commitment", new KzgBlobToCommitmentTestExecutor()) .put("kzg/compute_blob_kzg_proof", new KzgComputeBlobProofTestExecutor()) // no KZG interface on CL side, EL responsibility @@ -29,5 +31,12 @@ public class KzgTests { .put("kzg/verify_blob_kzg_proof_batch", new KzgVerifyBlobProofBatchTestExecutor()) // no KZG interface on CL side, EL responsibility .put("kzg/verify_kzg_proof", TestExecutor.IGNORE_TESTS) + + // DataColumnSidecar EIP-7594 utils + .put("kzg/compute_cells", new KzgComputeCellsTestExecutor()) + .put("kzg/compute_cells_and_kzg_proofs", new KzgComputeCellsAndKzgProofsTestExecutor()) + .put("kzg/recover_all_cells", new KzgRecoverAllCellsTestExecutor()) + .put("kzg/verify_cell_kzg_proof", new KzgVerifyCellKzgProofTestExecutor()) + .put("kzg/verify_cell_kzg_proof_batch", new KzgVerifyCellKzgProofBatchTestExecutor()) .build(); } diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofBatchTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofBatchTestExecutor.java new file mode 100644 index 00000000000..32e476732ce --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofBatchTestExecutor.java @@ -0,0 +1,110 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.phase0.kzg; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import java.util.stream.IntStream; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellID; +import tech.pegasys.teku.kzg.KZGCellWithIds; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.kzg.KZGProof; + +public class KzgVerifyCellKzgProofBatchTestExecutor extends KzgTestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition, final KZG kzg) throws Throwable { + final Data data = loadDataFile(testDefinition, Data.class); + final Boolean expectedVerificationResult = data.getOutput(); + Boolean actualVerificationResult; + try { + actualVerificationResult = + kzg.verifyCellProofBatch( + data.getInput().getRowCommitments(), + IntStream.range(0, data.getInput().getCells().size()) + .mapToObj( + index -> + new KZGCellWithIds( + data.getInput().getCells().get(index), + KZGCellID.fromCellColumnIndex( + data.getInput().getRowIndices().get(index)), + KZGCellID.fromCellColumnIndex( + data.getInput().getColumnIndices().get(index)))) + .toList(), + data.getInput().getProofs()); + } catch (final RuntimeException ex) { + actualVerificationResult = null; + } + assertThat(actualVerificationResult).isEqualTo(expectedVerificationResult); + } + + private static class Data { + @JsonProperty(value = "input", required = true) + private Input input; + + @JsonProperty(value = "output", required = true) + private Boolean output; + + public Input getInput() { + return input; + } + + public Boolean getOutput() { + return output; + } + + private static class Input { + @JsonProperty(value = "row_commitments", required = true) + private List rowCommitments; + + @JsonProperty(value = "row_indices", required = true) + private List rowIndices; + + @JsonProperty(value = "column_indices", required = true) + private List columnIndices; + + @JsonProperty(value = "cells", required = true) + private List cells; + + @JsonProperty(value = "proofs", required = true) + private List proofs; + + public List getRowCommitments() { + return rowCommitments.stream().map(KZGCommitment::fromHexString).toList(); + } + + public List getRowIndices() { + return rowIndices; + } + + public List getColumnIndices() { + return columnIndices; + } + + public List getCells() { + return cells.stream().map(cell -> new KZGCell(Bytes.fromHexString(cell))).toList(); + } + + public List getProofs() { + return proofs.stream().map(KZGProof::fromHexString).toList(); + } + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofTestExecutor.java new file mode 100644 index 00000000000..2a9ddc24da4 --- /dev/null +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgVerifyCellKzgProofTestExecutor.java @@ -0,0 +1,95 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.reference.phase0.kzg; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.tuweni.bytes.Bytes; +import tech.pegasys.teku.ethtests.finder.TestDefinition; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.kzg.KZGCell; +import tech.pegasys.teku.kzg.KZGCellID; +import tech.pegasys.teku.kzg.KZGCellWithColumnId; +import tech.pegasys.teku.kzg.KZGCommitment; +import tech.pegasys.teku.kzg.KZGProof; + +public class KzgVerifyCellKzgProofTestExecutor extends KzgTestExecutor { + + @Override + public void runTest(final TestDefinition testDefinition, final KZG kzg) throws Throwable { + final Data data = loadDataFile(testDefinition, Data.class); + final Boolean expectedVerificationResult = data.getOutput(); + Boolean actualVerificationResult; + try { + actualVerificationResult = + kzg.verifyCellProof( + data.getInput().getCommitment(), + new KZGCellWithColumnId( + data.getInput().getCell(), + new KZGCellID(UInt64.valueOf(data.getInput().getCellId()))), + data.getInput().getProof()); + } catch (final RuntimeException ex) { + actualVerificationResult = null; + } + assertThat(actualVerificationResult).isEqualTo(expectedVerificationResult); + } + + private static class Data { + @JsonProperty(value = "input", required = true) + private Input input; + + @JsonProperty(value = "output", required = true) + private Boolean output; + + public Input getInput() { + return input; + } + + public Boolean getOutput() { + return output; + } + + private static class Input { + @JsonProperty(value = "commitment", required = true) + private String commitment; + + @JsonProperty(value = "cell_id", required = true) + private Integer cellId; + + @JsonProperty(value = "cell", required = true) + private String cell; + + @JsonProperty(value = "proof", required = true) + private String proof; + + public KZGCommitment getCommitment() { + return KZGCommitment.fromHexString(commitment); + } + + public Integer getCellId() { + return cellId; + } + + public KZGCell getCell() { + return new KZGCell(Bytes.fromHexString(cell)); + } + + public KZGProof getProof() { + return KZGProof.fromHexString(proof); + } + } + } +} diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/ssz_static/SszTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/ssz_static/SszTestExecutor.java index d0449a50145..9e175385014 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/ssz_static/SszTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/ssz_static/SszTestExecutor.java @@ -29,6 +29,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.altair.BeaconBlockBodySchemaAltair; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.operations.AttestationData; import tech.pegasys.teku.spec.datastructures.operations.Deposit; import tech.pegasys.teku.spec.datastructures.operations.DepositData; @@ -47,6 +48,7 @@ import tech.pegasys.teku.spec.schemas.SchemaDefinitionsBellatrix; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsCapella; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; +import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; public class SszTestExecutor implements TestExecutor { private final SchemaProvider sszType; @@ -184,6 +186,16 @@ public class SszTestExecutor implements TestExecutor { "ssz_static/BlobIdentifier", new SszTestExecutor<>(schemas -> BlobIdentifier.SSZ_SCHEMA)) + // EIP7594 types + .put( + "ssz_static/DataColumnIdentifier", + new SszTestExecutor<>(schemas -> DataColumnIdentifier.SSZ_SCHEMA)) + .put( + "ssz_static/DataColumnSidecar", + new SszTestExecutor<>( + schemas -> + SchemaDefinitionsEip7594.required(schemas).getDataColumnSidecarSchema())) + // Legacy Schemas (Not yet migrated to SchemaDefinitions) .put( "ssz_static/AttestationData", new SszTestExecutor<>(__ -> AttestationData.SSZ_SCHEMA)) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java index 0d47860fffa..0f6876e9166 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java @@ -18,6 +18,7 @@ import java.nio.ByteOrder; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.unsigned.UInt64; public class MathHelpers { @@ -134,4 +135,12 @@ static Bytes32 uintToBytes32(final UInt64 value) { public static UInt64 bytesToUInt64(final Bytes data) { return UInt64.fromLongBits(data.toLong(ByteOrder.LITTLE_ENDIAN)); } + + public static Bytes uint256ToBytes(final UInt256 number) { + final Bytes intBytes = + Bytes.wrap(number.toUnsignedBigInteger(ByteOrder.LITTLE_ENDIAN).toByteArray()) + .trimLeadingZeros(); + // We should keep 32 bytes + return Bytes32.leftPad(intBytes); + } } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 8a7e317d502..aeb0b132770 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -13,16 +13,20 @@ package tech.pegasys.teku.spec.logic.versions.eip7594.helpers; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.bytesToUInt64; +import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.uint256ToBytes; + +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; -import java.util.stream.Stream; +import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.crypto.Hash; import tech.pegasys.teku.infrastructure.ssz.SszList; import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; import tech.pegasys.teku.infrastructure.ssz.tree.MerkleUtil; @@ -30,7 +34,7 @@ import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.kzg.KZGCell; import tech.pegasys.teku.kzg.KZGCellAndProof; -import tech.pegasys.teku.kzg.KZGCellWithID; +import tech.pegasys.teku.kzg.KZGCellWithColumnId; import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.Blob; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.Cell; @@ -80,17 +84,49 @@ public UInt64 computeSubnetForDataColumnSidecar(UInt64 columnIndex) { return columnIndex.mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); } - public Set computeCustodyColumnIndexes( - final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { - // TODO: implement whatever formula is finalized - Set subnets = - new HashSet<>(computeDataColumnSidecarBackboneSubnets(nodeId, epoch, subnetCount)); - return Stream.iterate(UInt64.ZERO, UInt64::increment) - .limit(specConfigEip7594.getNumberOfColumns().intValue()) - .filter(columnIndex -> subnets.contains(computeSubnetForDataColumnSidecar(columnIndex))) - .collect(Collectors.toSet()); + public Set computeCustodyColumnIndexes(final UInt256 nodeId, final int subnetCount) { + // assert custody_subnet_count <= DATA_COLUMN_SIDECAR_SUBNET_COUNT + if (subnetCount > specConfigEip7594.getDataColumnSidecarSubnetCount()) { + throw new IllegalArgumentException( + String.format( + "Subnet count %s couldn't exceed number of subnet columns %s", + subnetCount, specConfigEip7594.getNumberOfColumns())); + } + + final List subnetIds = new ArrayList<>(); + UInt256 curId = nodeId; + while (subnetIds.size() < subnetCount) { + final UInt64 subnetId = + bytesToUInt64(Hash.sha256(uint256ToBytes(curId)).slice(0, 8)) + .mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); + if (!subnetIds.contains(subnetId)) { + subnetIds.add(subnetId); + } + if (curId.equals(UInt256.MAX_VALUE)) { + curId = UInt256.ZERO; + } + curId = curId.plus(1); + } + + final int columnsPerSubnet = + specConfigEip7594 + .getNumberOfColumns() + .dividedBy(specConfigEip7594.getDataColumnSidecarSubnetCount()) + .intValue(); + return subnetIds.stream() + .sorted() + .flatMap( + subnetId -> IntStream.range(0, columnsPerSubnet).mapToObj(i -> Pair.of(subnetId, i))) + .map( + // ColumnIndex(DATA_COLUMN_SIDECAR_SUBNET_COUNT * i + subnet_id) + pair -> + specConfigEip7594.getDataColumnSidecarSubnetCount() * pair.getRight() + + pair.getLeft().intValue()) + .map(UInt64::valueOf) + .collect(Collectors.toUnmodifiableSet()); } + // TODO public List computeDataColumnSidecarBackboneSubnets( final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { // TODO: implement whatever formula is finalized @@ -118,7 +154,7 @@ public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataCo index -> kzg.verifyCellProof( dataColumnSidecar.getSszKZGCommitments().get(index).getKZGCommitment(), - KZGCellWithID.fromCellAndColumn( + KZGCellWithColumnId.fromCellAndColumn( new KZGCell(dataColumnSidecar.getDataColumn().get(index).getBytes()), dataColumnSidecar.getIndex().intValue()), dataColumnSidecar.getSszKZGProofs().get(index).getKZGProof())) @@ -137,7 +173,7 @@ public boolean verifyDataColumnSidecarInclusionProof(final DataColumnSidecar dat dataColumnSidecar.getBlockBodyRoot()); } - private int getBlockBodyKzgCommitmentsGeneralizedIndex() { + public int getBlockBodyKzgCommitmentsGeneralizedIndex() { return (int) BeaconBlockBodySchemaEip7594.required(schemaDefinitions.getBeaconBlockBodySchema()) .getBlobKzgCommitmentsGeneralizedIndex(); diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index df4fbcc279b..82415c3c0a9 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -13,6 +13,8 @@ package tech.pegasys.teku.statetransition.datacolumns; +import static com.google.common.base.Preconditions.checkNotNull; + import java.util.Collection; import java.util.List; import java.util.Optional; @@ -30,8 +32,6 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; -import static com.google.common.base.Preconditions.checkNotNull; - public class DataColumnSidecarCustodyImpl implements UpdatableDataColumnSidecarCustody, SlotEventsChannel { @@ -120,7 +120,7 @@ private Set getCustodyColumnsForSlot(UInt64 slot) { private Set getCustodyColumnsForEpoch(UInt64 epoch) { return MiscHelpersEip7594.required(spec.atEpoch(epoch).miscHelpers()) - .computeCustodyColumnIndexes(nodeId, epoch, totalCustodySubnetCount); + .computeCustodyColumnIndexes(nodeId, totalCustodySubnetCount); } @Override @@ -138,7 +138,7 @@ private boolean isMyCustody(UInt64 slot, UInt64 columnIndex) { .map( miscHelpersEip7594 -> miscHelpersEip7594 - .computeCustodyColumnIndexes(nodeId, epoch, totalCustodySubnetCount) + .computeCustodyColumnIndexes(nodeId, totalCustodySubnetCount) .contains(columnIndex)) .orElse(false); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 1e40710c61c..2e6d2be3872 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -178,13 +178,11 @@ public ConnectedPeer(UInt256 nodeId, int extraCustodySubnetCount) { } private Set getNodeCustodyIndexes(UInt64 slot) { - UInt64 epoch = spec.computeEpochAtSlot(slot); SpecVersion specVersion = spec.atSlot(slot); int minCustodyRequirement = SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); return MiscHelpersEip7594.required(specVersion.miscHelpers()) - .computeCustodyColumnIndexes( - nodeId, epoch, minCustodyRequirement + extraCustodySubnetCount); + .computeCustodyColumnIndexes(nodeId, minCustodyRequirement + extraCustodySubnetCount); } public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index 08edbd4db6b..683c9be5447 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -171,17 +171,36 @@ public List computeCellsAndProofs(Bytes blob) { @Override public boolean verifyCellProof( - KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { + KZGCommitment commitment, KZGCellWithColumnId cellWithColumnId, KZGProof proof) { return CKZG4844JNI.verifyCellProof( commitment.toArrayUnsafe(), - cellWithID.id().id().longValue(), - cellWithID.cell().bytes().toArrayUnsafe(), + cellWithColumnId.columnId().id().longValue(), + cellWithColumnId.cell().bytes().toArrayUnsafe(), proof.toArrayUnsafe()); } @Override - public List recoverCells(List cells) { - long[] cellIds = cells.stream().mapToLong(c -> c.id().id().longValue()).toArray(); + public boolean verifyCellProofBatch( + List commitments, + List cellWithIdsList, + List proofs) { + return CKZG4844JNI.verifyCellProofBatch( + CKZG4844Utils.flattenCommitments(commitments), + cellWithIdsList.stream() + .mapToLong(cellWithIds -> cellWithIds.rowId().id().longValue()) + .toArray(), + cellWithIdsList.stream() + .mapToLong(cellWithIds -> cellWithIds.columnId().id().longValue()) + .toArray(), + CKZG4844Utils.flattenBytes( + cellWithIdsList.stream().map(cellWithIds -> cellWithIds.cell().bytes()).toList(), + cellWithIdsList.size() * BYTES_PER_CELL), + CKZG4844Utils.flattenProofs(proofs)); + } + + @Override + public List recoverCells(List cells) { + long[] cellIds = cells.stream().mapToLong(c -> c.columnId().id().longValue()).toArray(); byte[] cellBytes = CKZG4844Utils.flattenBytes( cells.stream().map(c -> c.cell().bytes()).toList(), cells.size() * BYTES_PER_CELL); diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index a765b3c5fe2..d253f31ab84 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -43,7 +43,7 @@ public void freeTrustedSetup() throws KZGException {} public boolean verifyBlobKzgProof( final Bytes blob, final KZGCommitment kzgCommitment, final KZGProof kzgProof) throws KZGException { - return true; + return false; } @Override @@ -52,7 +52,7 @@ public boolean verifyBlobKzgProofBatch( final List kzgCommitments, final List kzgProofs) throws KZGException { - return true; + return false; } @Override @@ -83,16 +83,24 @@ public List computeCellsAndProofs(Bytes blob) { @Override public boolean verifyCellProof( - KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof) { - return true; + KZGCommitment commitment, KZGCellWithColumnId cellWithID, KZGProof proof) { + return false; } @Override - public List recoverCells(List cells) { + public boolean verifyCellProofBatch( + List commitments, + List cellWithIDs, + List proofs) { + return false; + } + + @Override + public List recoverCells(List cells) { if (cells.size() < CELLS_PER_BLOB) { throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); } - return cells.stream().map(KZGCellWithID::cell).limit(CELLS_PER_BLOB).toList(); + return cells.stream().map(KZGCellWithColumnId::cell).limit(CELLS_PER_BLOB).toList(); } }; @@ -117,9 +125,11 @@ boolean verifyBlobKzgProofBatch( List computeCellsAndProofs(Bytes blob); - boolean verifyCellProof(KZGCommitment commitment, KZGCellWithID cellWithID, KZGProof proof); + boolean verifyCellProof(KZGCommitment commitment, KZGCellWithColumnId cellWithID, KZGProof proof); - // TODO veryCellProofBatch() + boolean verifyCellProofBatch( + List commitments, List cellWithIDs, List proofs); - List recoverCells(List cells); + /** Check {@link KzgRecoverAllCellsTestExecutor} for implementation requirements */ + List recoverCells(List cells); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java index 9ff417a8da9..766434c9a7b 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellID.java @@ -17,7 +17,7 @@ public record KZGCellID(UInt64 id) { - static KZGCellID fromCellColumnIndex(int idx) { + public static KZGCellID fromCellColumnIndex(int idx) { return new KZGCellID(UInt64.valueOf(idx)); } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithColumnId.java similarity index 72% rename from infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java rename to infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithColumnId.java index 3f1ccc5c841..af5f9ccb3a0 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithID.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithColumnId.java @@ -13,9 +13,9 @@ package tech.pegasys.teku.kzg; -public record KZGCellWithID(KZGCell cell, KZGCellID id) { +public record KZGCellWithColumnId(KZGCell cell, KZGCellID columnId) { - public static KZGCellWithID fromCellAndColumn(KZGCell cell, int index) { - return new KZGCellWithID(cell, KZGCellID.fromCellColumnIndex(index)); + public static KZGCellWithColumnId fromCellAndColumn(KZGCell cell, int columnIndex) { + return new KZGCellWithColumnId(cell, KZGCellID.fromCellColumnIndex(columnIndex)); } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithIds.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithIds.java new file mode 100644 index 00000000000..a3b6125085d --- /dev/null +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZGCellWithIds.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.kzg; + +public record KZGCellWithIds(KZGCell cell, KZGCellID rowId, KZGCellID columnId) { + + public static KZGCellWithIds fromCellAndIndices(KZGCell cell, int rowIndex, int columnIndex) { + return new KZGCellWithIds( + cell, KZGCellID.fromCellColumnIndex(rowIndex), KZGCellID.fromCellColumnIndex(columnIndex)); + } + + public static KZGCellWithIds fromKZGCellWithColumnIdAndRowId( + KZGCellWithColumnId cellWithColumnId, int rowIndex) { + return new KZGCellWithIds( + cellWithColumnId.cell(), + KZGCellID.fromCellColumnIndex(rowIndex), + cellWithColumnId.columnId()); + } +} diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index 18c449d0ed1..4ab0980d20b 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -292,9 +292,9 @@ public void testComputeRecoverCells() { List cells = CKZG.computeCells(blob); assertThat(cells).hasSize(CELLS_PER_EXT_BLOB); - List cellsToRecover = + List cellsToRecover = IntStream.range(CELLS_PER_ORIG_BLOB, CELLS_PER_EXT_BLOB) - .mapToObj(i -> new KZGCellWithID(cells.get(i), KZGCellID.fromCellColumnIndex(i))) + .mapToObj(i -> new KZGCellWithColumnId(cells.get(i), KZGCellID.fromCellColumnIndex(i))) .toList(); List recoveredCells = CKZG.recoverCells(cellsToRecover); @@ -315,14 +315,14 @@ public void testComputeAndVerifyCellProof() { assertThat( CKZG.verifyCellProof( kzgCommitment, - KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), + KZGCellWithColumnId.fromCellAndColumn(cellAndProofs.get(i).cell(), i), cellAndProofs.get(i).proof())) .isTrue(); var invalidProof = cellAndProofs.get((i + 1) % cellAndProofs.size()).proof(); assertThat( CKZG.verifyCellProof( kzgCommitment, - KZGCellWithID.fromCellAndColumn(cellAndProofs.get(i).cell(), i), + KZGCellWithColumnId.fromCellAndColumn(cellAndProofs.get(i).cell(), i), invalidProof)) .isFalse(); } diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index f80833cb538..6c70b7d9842 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -34,7 +34,6 @@ import java.util.Optional; import java.util.function.Function; import java.util.function.IntSupplier; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; From dedcd9677ce350ab07562147444b8fb61258452f Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 7 May 2024 22:51:02 +0400 Subject: [PATCH 46/70] Ref tests 1.5.0.alpha.2 + jc-kzg usage update (#37) * Bump reference tests to 1.5.0.alpha.2 + jc-kzg usage update * fix old import --- build.gradle | 2 +- .../teku/reference/phase0/kzg/KzgTestExecutor.java | 13 ------------- .../main/java/tech/pegasys/teku/kzg/CKZG4844.java | 8 ++++---- .../src/main/java/tech/pegasys/teku/kzg/KZG.java | 6 +++--- .../java/tech/pegasys/teku/kzg/CKZG4844Test.java | 3 +-- 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/build.gradle b/build.gradle index 3f9010f670a..23b726fe087 100644 --- a/build.gradle +++ b/build.gradle @@ -295,7 +295,7 @@ allprojects { } } -def refTestVersion = 'v1.5.0-alpha.1' // Arbitrary change to refresh cache number: 1 +def refTestVersion = 'v1.5.0-alpha.2' // Arbitrary change to refresh cache number: 1 def blsRefTestVersion = 'v0.1.2' def refTestBaseUrl = 'https://github.com/ethereum/consensus-spec-tests/releases/download' def blsRefTestBaseUrl = 'https://github.com/ethereum/bls12-381-tests/releases/download' diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java index 2c014a29094..a67a07d11bf 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/phase0/kzg/KzgTestExecutor.java @@ -16,7 +16,6 @@ import static tech.pegasys.teku.ethtests.finder.KzgTestFinder.KZG_DATA_FILE; import java.io.IOException; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import tech.pegasys.teku.ethtests.finder.TestDefinition; @@ -28,21 +27,9 @@ public abstract class KzgTestExecutor implements TestExecutor { private static final Pattern TEST_NAME_PATTERN = Pattern.compile("kzg-(.+)/.+"); - // TODO: update kzg and retest, should help - private static final List IGNORED_TESTS = - List.of( - "verify_cell_kzg_proof_batch_case_valid_21b209cb4f64d0ed", - "verify_cell_kzg_proof_batch_case_valid_7dc4b00d04efff0c", - "verify_cell_kzg_proof_batch_case_valid_fad5448f3ceb0978", - "verify_cell_kzg_proof_batch_case_valid_unused_row_commitments_bc80af6ef27f8129"); @Override public final void runTest(final TestDefinition testDefinition) throws Throwable { - final int lastSlash = testDefinition.getTestName().lastIndexOf("/"); - final String testName = testDefinition.getTestName().substring(lastSlash + 1); - if (IGNORED_TESTS.contains(testName)) { - return; - } final String network = extractNetwork(testDefinition.getTestName()); final KZG kzg = KzgRetriever.getKzgWithLoadedTrustedSetup(network); runTest(testDefinition, kzg); diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java index 683c9be5447..30f410edf06 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/CKZG4844.java @@ -158,7 +158,7 @@ public List computeCells(Bytes blob) { @Override public List computeCellsAndProofs(Bytes blob) { - CellsAndProofs cellsAndProofs = CKZG4844JNI.computeCellsAndProofs(blob.toArrayUnsafe()); + CellsAndProofs cellsAndProofs = CKZG4844JNI.computeCellsAndKzgProofs(blob.toArrayUnsafe()); List cells = KZGCell.splitBytes(Bytes.wrap(cellsAndProofs.getCells())); List proofs = KZGProof.splitBytes(Bytes.wrap(cellsAndProofs.getProofs())); if (cells.size() != proofs.size()) { @@ -172,7 +172,7 @@ public List computeCellsAndProofs(Bytes blob) { @Override public boolean verifyCellProof( KZGCommitment commitment, KZGCellWithColumnId cellWithColumnId, KZGProof proof) { - return CKZG4844JNI.verifyCellProof( + return CKZG4844JNI.verifyCellKzgProof( commitment.toArrayUnsafe(), cellWithColumnId.columnId().id().longValue(), cellWithColumnId.cell().bytes().toArrayUnsafe(), @@ -184,7 +184,7 @@ public boolean verifyCellProofBatch( List commitments, List cellWithIdsList, List proofs) { - return CKZG4844JNI.verifyCellProofBatch( + return CKZG4844JNI.verifyCellKzgProofBatch( CKZG4844Utils.flattenCommitments(commitments), cellWithIdsList.stream() .mapToLong(cellWithIds -> cellWithIds.rowId().id().longValue()) @@ -204,7 +204,7 @@ public List recoverCells(List cells) { byte[] cellBytes = CKZG4844Utils.flattenBytes( cells.stream().map(c -> c.cell().bytes()).toList(), cells.size() * BYTES_PER_CELL); - byte[] recovered = CKZG4844JNI.recoverCells(cellIds, cellBytes); + byte[] recovered = CKZG4844JNI.recoverAllCells(cellIds, cellBytes); return KZGCell.splitBytes(Bytes.wrap(recovered)); } } diff --git a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java index d253f31ab84..a5aaee0d4e2 100644 --- a/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java +++ b/infrastructure/kzg/src/main/java/tech/pegasys/teku/kzg/KZG.java @@ -13,7 +13,7 @@ package tech.pegasys.teku.kzg; -import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; +import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_EXT_BLOB; import java.util.List; import java.util.stream.Stream; @@ -97,10 +97,10 @@ public boolean verifyCellProofBatch( @Override public List recoverCells(List cells) { - if (cells.size() < CELLS_PER_BLOB) { + if (cells.size() < CELLS_PER_EXT_BLOB) { throw new IllegalArgumentException("Can't recover from " + cells.size() + " cells"); } - return cells.stream().map(KZGCellWithColumnId::cell).limit(CELLS_PER_BLOB).toList(); + return cells.stream().map(KZGCellWithColumnId::cell).limit(CELLS_PER_EXT_BLOB).toList(); } }; diff --git a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java index 4ab0980d20b..87975ccf33e 100644 --- a/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java +++ b/infrastructure/kzg/src/test/java/tech/pegasys/teku/kzg/CKZG4844Test.java @@ -15,7 +15,7 @@ import static ethereum.ckzg4844.CKZG4844JNI.BLS_MODULUS; import static ethereum.ckzg4844.CKZG4844JNI.BYTES_PER_BLOB; -import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_BLOB; +import static ethereum.ckzg4844.CKZG4844JNI.CELLS_PER_EXT_BLOB; import static ethereum.ckzg4844.CKZG4844JNI.FIELD_ELEMENTS_PER_BLOB; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; @@ -283,7 +283,6 @@ public void testInvalidLengthG2PointInNewTrustedSetup() { .hasMessage("Expected G2 point to be 96 bytes"); } - static final int CELLS_PER_EXT_BLOB = CELLS_PER_BLOB; static final int CELLS_PER_ORIG_BLOB = CELLS_PER_EXT_BLOB / 2; @Test From 8a76b2589717824fad835082723b533455a2a42c Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 16:12:57 +0400 Subject: [PATCH 47/70] Expose discovery NodeId to DiscoveryPeer and LibP2PPeer (#38) * Add DiscoveryPeer.getNodeId() * Use the correct NodeId (from Discovery) instead of wrong Libp2p PeerId when calculating DAS subnets * Add Eth2Peer.getDiscoveryNodeId() method. --- .../eth2/Eth2P2PNetworkBuilder.java | 20 ++++++++++++++++++- ...dToDataColumnSidecarSubnetsCalculator.java | 4 ++-- .../subnets/PeerSubnetSubscriptions.java | 4 +++- .../eth2/gossip/subnets/SubnetScorer.java | 5 +++-- .../eth2/peers/DefaultEth2Peer.java | 9 +++++++++ .../eth2/peers/DiscoveryNodeIdExtractor.java | 8 ++++++++ .../teku/networking/eth2/peers/Eth2Peer.java | 5 +++++ .../eth2/peers/Eth2PeerFactory.java | 7 ++++++- .../eth2/peers/Eth2PeerManager.java | 6 ++++-- .../p2p/discovery/DiscoveryPeer.java | 7 +++++++ .../p2p/discovery/discv5/DiscV5Service.java | 1 + .../discovery/discv5/NodeRecordConverter.java | 6 ++++++ .../networking/p2p/libp2p/LibP2PPeer.java | 7 +++++++ .../networking/p2p/libp2p/MultiaddrUtil.java | 2 +- 14 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 926a01c7e71..355d9ac9bea 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -23,7 +23,10 @@ import java.util.Collections; import java.util.List; import java.util.Optional; + +import io.libp2p.core.crypto.PubKey; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.events.EventChannels; @@ -48,6 +51,7 @@ import tech.pegasys.teku.networking.eth2.gossip.topics.Eth2GossipTopicFilter; import tech.pegasys.teku.networking.eth2.gossip.topics.OperationProcessor; import tech.pegasys.teku.networking.eth2.gossip.topics.ProcessedAttestationSubscriptionProvider; +import tech.pegasys.teku.networking.eth2.peers.DiscoveryNodeIdExtractor; import tech.pegasys.teku.networking.eth2.peers.Eth2PeerManager; import tech.pegasys.teku.networking.eth2.peers.Eth2PeerSelectionStrategy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.StatusMessageFactory; @@ -57,8 +61,10 @@ import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetwork; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryNetworkBuilder; +import tech.pegasys.teku.networking.p2p.discovery.discv5.DiscV5Service; import tech.pegasys.teku.networking.p2p.gossip.PreparedGossipMessageFactory; import tech.pegasys.teku.networking.p2p.libp2p.LibP2PNetworkBuilder; +import tech.pegasys.teku.networking.p2p.libp2p.LibP2PPeer; import tech.pegasys.teku.networking.p2p.libp2p.LibP2PPrivateKeyLoader; import tech.pegasys.teku.networking.p2p.libp2p.gossip.GossipTopicFilter; import tech.pegasys.teku.networking.p2p.network.P2PNetwork; @@ -143,6 +149,17 @@ public Eth2P2PNetwork build() { final SubnetSubscriptionService syncCommitteeSubnetService = new SubnetSubscriptionService(); final SubnetSubscriptionService dataColumnSidecarSubnetService = new SubnetSubscriptionService(); + + // TODO a bit hacky solution, subject to be refactored + DiscoveryNodeIdExtractor discoveryNodeIdExtractor = + peer -> { + LibP2PPeer libP2PPeer = (LibP2PPeer) peer; + PubKey libP2PPubKey = libP2PPeer.getPubKey(); + Bytes discoveryNodeIdBytes = DiscV5Service.DEFAULT_NODE_RECORD_CONVERTER.convertPublicKeyToNodeId( + Bytes.wrap(libP2PPubKey.raw())); + return UInt256.fromBytes(discoveryNodeIdBytes); + }; + final RpcEncoding rpcEncoding = RpcEncoding.createSszSnappyEncoding(spec.getNetworkingConfig().getMaxChunkSize()); if (statusMessageFactory == null) { @@ -165,7 +182,8 @@ public Eth2P2PNetwork build() { config.getPeerRateLimit(), config.getPeerRequestLimit(), spec, - kzg); + kzg, + discoveryNodeIdExtractor); final Collection> eth2RpcMethods = eth2PeerManager.getBeaconChainMethods().all(); rpcMethods.addAll(eth2RpcMethods); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java index ecc1503af7e..0f3fe75ea68 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java @@ -30,7 +30,7 @@ @FunctionalInterface public interface NodeIdToDataColumnSidecarSubnetsCalculator { - Optional calculateSubnets(NodeId nodeId, int extraSubnetCount); + Optional calculateSubnets(UInt256 nodeId, int extraSubnetCount); NodeIdToDataColumnSidecarSubnetsCalculator NOOP = (nodeId, extraSubnetCount) -> Optional.empty(); @@ -43,7 +43,7 @@ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( return (nodeId, extraSubnetCount) -> { List nodeSubnets = miscHelpers.computeDataColumnSidecarBackboneSubnets( - UInt256.fromBytes(nodeId.toBytes()), + nodeId, currentEpoch, config.getCustodyRequirement() + extraSubnetCount); return Optional.of( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index ce377bbc9ac..3af3fb1aded 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -26,6 +26,8 @@ import java.util.OptionalInt; import java.util.function.Consumer; import java.util.stream.IntStream; + +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; @@ -205,7 +207,7 @@ public SszBitvector getDataColumnSidecarSubnetSubscriptions(final NodeId peerId) } public SszBitvector getDataColumnSidecarSubnetSubscriptionsByNodeId( - final NodeId peerId, final int extraSubnetCount) { + final UInt256 peerId, final int extraSubnetCount) { return nodeIdToDataColumnSidecarSubnetsCalculator .calculateSubnets(peerId, extraSubnetCount) .orElse(dataColumnSidecarSubnetSubscriptions.getSubscriptionSchema().getDefault()); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java index 0d1329fe584..4ba75668bb1 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java @@ -14,10 +14,11 @@ package tech.pegasys.teku.networking.eth2.gossip.subnets; import java.util.function.IntUnaryOperator; + +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.networking.eth2.peers.PeerScorer; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryPeer; -import tech.pegasys.teku.networking.p2p.libp2p.MultiaddrUtil; import tech.pegasys.teku.networking.p2p.peer.NodeId; /** Scores peers higher if they are tracking subnets that are not tracked by other peers. */ @@ -54,7 +55,7 @@ public int scoreCandidatePeer(DiscoveryPeer candidate) { candidate.getPersistentAttestationSubnets(), candidate.getSyncCommitteeSubnets(), peerSubnetSubscriptions.getDataColumnSidecarSubnetSubscriptionsByNodeId( - MultiaddrUtil.getNodeId(candidate), candidate.getDasExtraCustodySubnetCount())); + UInt256.fromBytes(candidate.getNodeId()), candidate.getDasExtraCustodySubnetCount())); } // @Override diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index 95a760a6238..d3332ae4b0c 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -27,6 +27,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszData; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; @@ -78,6 +79,7 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private static final Logger LOG = LogManager.getLogger(); private final Spec spec; + private final UInt256 discoveryNodeId; private final BeaconChainMethods rpcMethods; private final StatusMessageFactory statusMessageFactory; private final MetadataMessagesFactory metadataMessagesFactory; @@ -104,6 +106,7 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { DefaultEth2Peer( final Spec spec, final Peer peer, + final UInt256 discoveryNodeId, final BeaconChainMethods rpcMethods, final StatusMessageFactory statusMessageFactory, final MetadataMessagesFactory metadataMessagesFactory, @@ -115,6 +118,7 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { final KZG kzg) { super(peer); this.spec = spec; + this.discoveryNodeId = discoveryNodeId; this.rpcMethods = rpcMethods; this.statusMessageFactory = statusMessageFactory; this.metadataMessagesFactory = metadataMessagesFactory; @@ -146,6 +150,11 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { this.maxBlobsPerBlock = Suppliers.memoize(() -> getSpecConfigDeneb().getMaxBlobsPerBlock()); } + @Override + public UInt256 getDiscoveryNodeId() { + return discoveryNodeId; + } + @Override public void updateStatus(final PeerStatus status) { peerChainValidator diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java new file mode 100644 index 00000000000..2f77d787199 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java @@ -0,0 +1,8 @@ +package tech.pegasys.teku.networking.eth2.peers; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.networking.p2p.peer.Peer; + +public interface DiscoveryNodeIdExtractor { + UInt256 calculateDiscoveryNodeId(Peer peer); +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java index b01c0207483..fe36d8a1a06 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Optional; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.ssz.SszData; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; @@ -43,6 +44,7 @@ public interface Eth2Peer extends Peer, SyncSource { static Eth2Peer create( final Spec spec, final Peer peer, + final UInt256 discoveryNodeId, final BeaconChainMethods rpcMethods, final StatusMessageFactory statusMessageFactory, final MetadataMessagesFactory metadataMessagesFactory, @@ -55,6 +57,7 @@ static Eth2Peer create( return new DefaultEth2Peer( spec, peer, + discoveryNodeId, rpcMethods, statusMessageFactory, metadataMessagesFactory, @@ -135,6 +138,8 @@ void adjustDataColumnSidecarsRequest( int getUnansweredPingCount(); + UInt256 getDiscoveryNodeId(); + interface PeerStatusSubscriber { void onPeerStatus(final PeerStatus initialStatus); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java index e28e2ed7181..1a308b3710a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.networking.eth2.peers; import java.util.Optional; + import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -39,6 +40,7 @@ public class Eth2PeerFactory { private final int peerRateLimit; private final int peerRequestLimit; private final KZG kzg; + private final DiscoveryNodeIdExtractor discoveryNodeIdExtractor; public Eth2PeerFactory( final Spec spec, @@ -50,7 +52,8 @@ public Eth2PeerFactory( final Optional requiredCheckpoint, final int peerRateLimit, final int peerRequestLimit, - final KZG kzg) { + final KZG kzg, + final DiscoveryNodeIdExtractor discoveryNodeIdExtractor) { this.spec = spec; this.metricsSystem = metricsSystem; this.chainDataClient = chainDataClient; @@ -61,12 +64,14 @@ public Eth2PeerFactory( this.peerRateLimit = peerRateLimit; this.peerRequestLimit = peerRequestLimit; this.kzg = kzg; + this.discoveryNodeIdExtractor = discoveryNodeIdExtractor; } public Eth2Peer create(final Peer peer, final BeaconChainMethods rpcMethods) { return Eth2Peer.create( spec, peer, + discoveryNodeIdExtractor.calculateDiscoveryNodeId(peer), rpcMethods, statusMessageFactory, metadataMessagesFactory, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java index 96b1bf98dde..8da048d2e2b 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java @@ -118,7 +118,8 @@ public static Eth2PeerManager create( final int peerRateLimit, final int peerRequestLimit, final Spec spec, - final KZG kzg) { + final KZG kzg, + final DiscoveryNodeIdExtractor discoveryNodeIdExtractor) { final MetadataMessagesFactory metadataMessagesFactory = new MetadataMessagesFactory(); attestationSubnetService.subscribeToUpdates( @@ -142,7 +143,8 @@ public static Eth2PeerManager create( requiredCheckpoint, peerRateLimit, peerRequestLimit, - kzg), + kzg, + discoveryNodeIdExtractor), statusMessageFactory, metadataMessagesFactory, rpcEncoding, diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java index 1b182847f9a..d011bdb5834 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java @@ -23,6 +23,7 @@ public class DiscoveryPeer { private final Bytes publicKey; + private final Bytes nodeId; private final InetSocketAddress nodeAddress; private final Optional enrForkId; private final SszBitvector persistentAttestationSubnets; @@ -31,12 +32,14 @@ public class DiscoveryPeer { public DiscoveryPeer( final Bytes publicKey, + final Bytes nodeId, final InetSocketAddress nodeAddress, final Optional enrForkId, final SszBitvector persistentAttestationSubnets, final SszBitvector syncCommitteeSubnets, final Optional dasExtraCustodySubnetCount) { this.publicKey = publicKey; + this.nodeId = nodeId; this.nodeAddress = nodeAddress; this.enrForkId = enrForkId; this.persistentAttestationSubnets = persistentAttestationSubnets; @@ -48,6 +51,10 @@ public Bytes getPublicKey() { return publicKey; } + public Bytes getNodeId() { + return nodeId; + } + public InetSocketAddress getNodeAddress() { return nodeAddress; } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java index 6e999a41ac0..e3e97da95b7 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/DiscV5Service.java @@ -213,6 +213,7 @@ public Optional getDiscoveryAddress() { final DiscoveryPeer discoveryPeer = new DiscoveryPeer( (Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1), + nodeRecord.getNodeId(), nodeRecord.getUdpAddress().get(), Optional.empty(), currentSchemaDefinitionsSupplier.getAttnetsENRFieldSchema().getDefault(), diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index 0947429c886..b5583576f37 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -26,6 +26,7 @@ import org.apache.tuweni.bytes.Bytes; import org.ethereum.beacon.discovery.schema.EnrField; import org.ethereum.beacon.discovery.schema.NodeRecord; +import org.ethereum.beacon.discovery.schema.NodeRecordBuilder; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; @@ -36,6 +37,10 @@ public class NodeRecordConverter { private static final Logger LOG = LogManager.getLogger(); + public Bytes convertPublicKeyToNodeId(Bytes publicKey) { + return new NodeRecordBuilder().publicKey(publicKey).build().getNodeId(); + } + public Optional convertToDiscoveryPeer( final NodeRecord nodeRecord, final SchemaDefinitions schemaDefinitions) { return nodeRecord @@ -71,6 +76,7 @@ private static DiscoveryPeer socketAddressToDiscoveryPeer( return new DiscoveryPeer( ((Bytes) nodeRecord.get(EnrField.PKEY_SECP256K1)), + nodeRecord.getNodeId(), address, enrForkId, persistentAttestationSubnets, diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java index 03f4824c19c..3bb92f926b7 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java @@ -16,6 +16,7 @@ import identify.pb.IdentifyOuterClass; import io.libp2p.core.Connection; import io.libp2p.core.PeerId; +import io.libp2p.core.crypto.PubKey; import io.libp2p.protocol.Identify; import java.util.List; import java.util.Map; @@ -51,6 +52,7 @@ public class LibP2PPeer implements Peer { private final AtomicBoolean connected = new AtomicBoolean(true); private final MultiaddrPeerAddress peerAddress; private final PeerId peerId; + private final PubKey pubKey; private volatile PeerClientType peerClientType = PeerClientType.UNKNOWN; private volatile Optional maybeAgentString = Optional.empty(); @@ -73,6 +75,7 @@ public LibP2PPeer( this.reputationManager = reputationManager; this.peerScoreFunction = peerScoreFunction; this.peerId = connection.secureSession().getRemoteId(); + this.pubKey = connection.secureSession().getRemotePubKey(); final NodeId nodeId = new LibP2PNodeId(peerId); peerAddress = new MultiaddrPeerAddress(nodeId, connection.remoteAddress()); @@ -110,6 +113,10 @@ public Optional getMaybeAgentString() { return maybeAgentString; } + public PubKey getPubKey() { + return pubKey; + } + @Override public PeerAddress getAddress() { return peerAddress; diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java index 6cce9b8fa42..37d39f0dde7 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtil.java @@ -58,7 +58,7 @@ private static Multiaddr addPeerId(final Multiaddr addr, final NodeId nodeId) { return addr.withP2P(PeerId.fromBase58(nodeId.toBase58())); } - public static NodeId getNodeId(final DiscoveryPeer peer) { + private static NodeId getNodeId(final DiscoveryPeer peer) { final PubKey pubKey = unmarshalSecp256k1PublicKey(peer.getPublicKey().toArrayUnsafe()); return new LibP2PNodeId(PeerId.fromPubKey(pubKey)); } From 9e71e7208750eacb154377a625b69e3da371884f Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 16:51:08 +0400 Subject: [PATCH 48/70] Add Eth2Peer.getAvailableDataColumnSidecarsRequestCount() (#39) --- .../teku/networking/eth2/peers/DefaultEth2Peer.java | 5 +++++ .../tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java | 2 ++ .../pegasys/teku/networking/eth2/peers/RateTracker.java | 7 +++++++ .../teku/networking/eth2/peers/RateTrackerImpl.java | 6 ++++++ 4 files changed, 20 insertions(+) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index d3332ae4b0c..ee5569a90dc 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -429,6 +429,11 @@ public void adjustBlobSidecarsRequest( blobSidecarsRequestTracker, blobSidecarsRequest, returnedBlobSidecarsCount); } + @Override + public long getAvailableDataColumnSidecarsRequestCount() { + return dataColumnSidecarsRequestTracker.getAvailableObjectCount(); + } + @Override public Optional approveDataColumnSidecarsRequest( final ResponseCallback callback, long dataColumnSidecarsCount) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java index fe36d8a1a06..a2e0c53eb00 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2Peer.java @@ -126,6 +126,8 @@ Optional approveBlobSidecarsRequest( void adjustBlobSidecarsRequest( RequestApproval blobSidecarsRequest, long returnedBlobSidecarsCount); + long getAvailableDataColumnSidecarsRequestCount(); + Optional approveDataColumnSidecarsRequest( ResponseCallback callback, long dataColumnSidecarsCount); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java index 8678dadb069..f245d1cf62c 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java @@ -28,6 +28,11 @@ public Optional approveObjectsRequest(long objectsCount) { public void adjustObjectsRequest( RequestApproval requestApproval, long returnedObjectsCount) {} + @Override + public long getAvailableObjectCount() { + return 0; + } + @Override public void pruneRequests() {} }; @@ -36,6 +41,8 @@ public void pruneRequests() {} // they can have the objects they request otherwise they get none. Optional approveObjectsRequest(long objectsCount); + long getAvailableObjectCount(); + void adjustObjectsRequest(RequestApproval requestApproval, long returnedObjectsCount); void pruneRequests(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java index ac0dc87b244..c28bb31fa1f 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java @@ -57,6 +57,12 @@ public synchronized Optional approveObjectsRequest(final long o return Optional.of(requestApproval); } + @Override + public long getAvailableObjectCount() { + pruneRequests(); + return peerRateLimit - objectsWithinWindow; + } + @Override public synchronized void adjustObjectsRequest( final RequestApproval requestApproval, final long returnedObjectsCount) { From a2c5ac4df774496b2dbff757f97d4eae44bb20dd Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 17:09:15 +0400 Subject: [PATCH 49/70] DasPeerManagerImpl and DAS req/resp initial drafts (#40) * Refactor: * Make DasPeerCustodyCountSupplier a separate thing * Move DataColumnSidecarRetriever interface to subpackage * Add DataColumnPeerManagerImpl * Add BatchDataColumnReqResp and implement it in DataColumnPeerManagerImpl * Make DataColumnReqResp implementation on top of BatchDataColumnReqResp --- .../{CustodySync.java => DasCustodySync.java} | 5 +- .../SimpleDataAvailabilitySampler.java | 1 + .../retriever/BatchDataColumnReqResp.java | 30 +++++++ .../DasPeerCustodyCountSupplier.java | 13 +++ .../retriever/DataColumnPeerManager.java | 4 +- .../retriever/DataColumnPeerSearcher.java | 10 +++ .../retriever/DataColumnReqResp.java | 4 + .../DataColumnReqRespBatchingImpl.java | 80 ++++++++++++++++++ .../DataColumnSidecarRetriever.java | 3 +- .../retriever/SimpleSidecarRetriever.java | 25 +++--- .../eth2/peers/DataColumnPeerManagerImpl.java | 84 +++++++++++++++++++ .../beaconchain/BeaconChainController.java | 6 ++ 12 files changed, 250 insertions(+), 15 deletions(-) rename ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/{CustodySync.java => DasCustodySync.java} (95%) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java rename ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/{ => retriever}/DataColumnSidecarRetriever.java (88%) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java similarity index 95% rename from ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java rename to ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index e97cf950a7a..c7c76a11baf 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/CustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -22,8 +22,9 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnSidecarRetriever; -public class CustodySync implements SlotEventsChannel { +public class DasCustodySync implements SlotEventsChannel { private final UpdatableDataColumnSidecarCustody custody; private final DataColumnSidecarRetriever retriever; @@ -33,7 +34,7 @@ public class CustodySync implements SlotEventsChannel { private Map pendingRequests = new HashMap<>(); private boolean started = false; - public CustodySync( + public DasCustodySync( UpdatableDataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { this.custody = custody; this.retriever = retriever; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java index fac09bce0f4..12d4e539c33 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/SimpleDataAvailabilitySampler.java @@ -23,6 +23,7 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnSidecarRetriever; public class SimpleDataAvailabilitySampler implements DataAvailabilitySampler { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java new file mode 100644 index 00000000000..629afc0976c --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java @@ -0,0 +1,30 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +import java.util.List; +import java.util.Optional; + +public interface BatchDataColumnReqResp { + + SafeFuture> requestDataColumnSidecar( + UInt256 nodeId, List columnIdentifiers); + + int getCurrentRequestLimit(UInt256 nodeId); +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java new file mode 100644 index 00000000000..995d1c42bf4 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java @@ -0,0 +1,13 @@ +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; + +public interface DasPeerCustodyCountSupplier { + + static DasPeerCustodyCountSupplier createStub(int defaultValue) { + return (__) -> defaultValue; + } + + int getCustodyCountForPeer(UInt256 nodeId); + +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java index 4c7bdaab833..928713b9f81 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerManager.java @@ -15,7 +15,7 @@ import org.apache.tuweni.units.bigints.UInt256; -public interface DataColumnPeerManager extends DataColumnPeerSearcher { +public interface DataColumnPeerManager { void addPeerListener(PeerListener listener); @@ -23,7 +23,7 @@ public interface DataColumnPeerManager extends DataColumnPeerSearcher { interface PeerListener { - void peerConnected(UInt256 nodeId, int extraCustodySubnetCount); + void peerConnected(UInt256 nodeId); void peerDisconnected(UInt256 nodeId); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java index 75f366ca295..3378753c3f5 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java @@ -17,6 +17,16 @@ public interface DataColumnPeerSearcher { + DataColumnPeerSearcher NOOP = + new DataColumnPeerSearcher() { + private final PeerSearchRequest NOOP_REQUEST = () -> {}; + + @Override + public PeerSearchRequest requestPeers(UInt64 slot, UInt64 columnIndex) { + return NOOP_REQUEST; + } + }; + PeerSearchRequest requestPeers(UInt64 slot, UInt64 columnIndex); interface PeerSearchRequest { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java index 26599461bf2..28621629cd7 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java @@ -20,6 +20,10 @@ public interface DataColumnReqResp { + class DataColumnReqRespException extends RuntimeException {} + class DasColumnNotAvailableException extends DataColumnReqRespException {} + class DasPeerDisconnectedException extends DataColumnReqRespException {} + SafeFuture requestDataColumnSidecar( UInt256 nodeId, DataColumnIdentifier columnIdentifier); diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java new file mode 100644 index 00000000000..6502502ec2b --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -0,0 +1,80 @@ +package tech.pegasys.teku.statetransition.datacolumns.retriever; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DataColumnReqRespBatchingImpl implements DataColumnReqResp { + + private final BatchDataColumnReqResp batchRpc; + + public DataColumnReqRespBatchingImpl(BatchDataColumnReqResp batchRpc) { + this.batchRpc = batchRpc; + } + + private record RequestEntry( + UInt256 nodeId, + DataColumnIdentifier columnIdentifier, + SafeFuture promise) {} + + private List bufferedRequests = new ArrayList<>(); + + @Override + public synchronized SafeFuture requestDataColumnSidecar( + UInt256 nodeId, DataColumnIdentifier columnIdentifier) { + RequestEntry entry = new RequestEntry(nodeId, columnIdentifier, new SafeFuture<>()); + bufferedRequests.add(entry); + return entry.promise(); + } + + @Override + public void flush() { + final List requests; + synchronized (this) { + requests = bufferedRequests; + bufferedRequests = new ArrayList<>(); + } + Map> byNodes = new HashMap<>(); + for (RequestEntry request : requests) { + byNodes.computeIfAbsent(request.nodeId, __ -> new ArrayList<>()).add(request); + } + for (Map.Entry> entry : byNodes.entrySet()) { + flushForNode(entry.getKey(), entry.getValue()); + } + } + + private void flushForNode(UInt256 nodeId, List nodeRequests) { + SafeFuture> response = + batchRpc.requestDataColumnSidecar( + nodeId, nodeRequests.stream().map(e -> e.columnIdentifier).toList()); + + response.finish( + resp -> { + Map byIds = new HashMap<>(); + for (DataColumnSidecar sidecar : resp) { + byIds.put( + new DataColumnIdentifier(sidecar.getBlockRoot(), sidecar.getIndex()), sidecar); + } + for (RequestEntry nodeRequest : nodeRequests) { + DataColumnSidecar maybeResponse = byIds.get(nodeRequest.columnIdentifier); + if (maybeResponse != null) { + nodeRequest.promise().complete(maybeResponse); + } else { + nodeRequest.promise().completeExceptionally(new DasColumnNotAvailableException()); + } + } + }, + err -> nodeRequests.forEach(e -> e.promise().completeExceptionally(err))); + } + + @Override + public int getCurrentRequestLimit(UInt256 nodeId) { + return batchRpc.getCurrentRequestLimit(nodeId); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnSidecarRetriever.java similarity index 88% rename from ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java rename to ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnSidecarRetriever.java index 7f7b01e32a6..a93f1590895 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnSidecarRetriever.java @@ -11,10 +11,11 @@ * specific language governing permissions and limitations under the License. */ -package tech.pegasys.teku.statetransition.datacolumns; +package tech.pegasys.teku.statetransition.datacolumns.retriever; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; /** The class which searches for a specific {@link DataColumnSidecar} across nodes in the network */ public interface DataColumnSidecarRetriever { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 2e6d2be3872..5add1a2b5b2 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -31,7 +31,6 @@ import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarRetriever; import tech.pegasys.teku.statetransition.validation.DataColumnSidecarValidator; // TODO improve thread-safety: external calls are better to do outside of the synchronize block to @@ -41,15 +40,20 @@ public class SimpleSidecarRetriever private final Spec spec; private final DataColumnPeerManager peerManager; + private final DataColumnPeerSearcher peerSearcher; + private final DasPeerCustodyCountSupplier custodyCountSupplier; private final DataColumnReqResp reqResp; public SimpleSidecarRetriever( Spec spec, DataColumnPeerManager peerManager, + DataColumnPeerSearcher peerSearcher, DasPeerCustodyCountSupplier custodyCountSupplier, DataColumnReqResp reqResp, DataColumnSidecarValidator validator) { this.spec = spec; this.peerManager = peerManager; + this.peerSearcher = peerSearcher; + this.custodyCountSupplier = custodyCountSupplier; this.reqResp = new ValidatingDataColumnReqResp(peerManager, reqResp, validator); peerManager.addPeerListener(this); } @@ -61,7 +65,7 @@ public SimpleSidecarRetriever( @Override public synchronized SafeFuture retrieve(ColumnSlotAndIdentifier columnId) { DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest = - peerManager.requestPeers(columnId.slot(), columnId.identifier().getIndex()); + peerSearcher.requestPeers(columnId.slot(), columnId.identifier().getIndex()); synchronized (this) { RetrieveRequest existingRequest = pendingRequests.get(columnId); @@ -145,8 +149,8 @@ private void reqRespCompleted( } @Override - public synchronized void peerConnected(UInt256 nodeId, int extraCustodySubnetCount) { - connectedPeers.put(nodeId, new ConnectedPeer(nodeId, extraCustodySubnetCount)); + public synchronized void peerConnected(UInt256 nodeId) { + connectedPeers.put(nodeId, new ConnectedPeer(nodeId)); } @Override @@ -170,19 +174,20 @@ private RetrieveRequest( private class ConnectedPeer { final UInt256 nodeId; - final int extraCustodySubnetCount; +// final int extraCustodySubnetCount; - public ConnectedPeer(UInt256 nodeId, int extraCustodySubnetCount) { + public ConnectedPeer(UInt256 nodeId/*, int extraCustodySubnetCount*/) { this.nodeId = nodeId; - this.extraCustodySubnetCount = extraCustodySubnetCount; +// this.extraCustodySubnetCount = extraCustodySubnetCount; } private Set getNodeCustodyIndexes(UInt64 slot) { SpecVersion specVersion = spec.atSlot(slot); - int minCustodyRequirement = - SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); +// int minCustodyRequirement = +// SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); return MiscHelpersEip7594.required(specVersion.miscHelpers()) - .computeCustodyColumnIndexes(nodeId, minCustodyRequirement + extraCustodySubnetCount); + .computeCustodyColumnIndexes( + nodeId, custodyCountSupplier.getCustodyCountForPeer(nodeId)); } public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java new file mode 100644 index 00000000000..c9eb7d48a96 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java @@ -0,0 +1,84 @@ +package tech.pegasys.teku.networking.eth2.peers; + +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.subscribers.Subscribers; +import tech.pegasys.teku.networking.p2p.peer.NodeId; +import tech.pegasys.teku.networking.p2p.peer.PeerConnectedSubscriber; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.statetransition.datacolumns.retriever.BatchDataColumnReqResp; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnPeerManager; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqResp; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DataColumnPeerManagerImpl implements DataColumnPeerManager, PeerConnectedSubscriber, BatchDataColumnReqResp { + + private final Subscribers listeners = Subscribers.create(true); + private Map connectedPeers = new ConcurrentHashMap<>(); + + @Override + public void onConnected(Eth2Peer peer) { + peerConnected(peer); + } + + private void peerConnected(Eth2Peer peer) { + UInt256 uintPeerId = nodeIdToUInt(peer.getId()); + listeners.forEach(l -> l.peerConnected(uintPeerId)); + connectedPeers.put(uintPeerId, peer); + peer.subscribeDisconnect((__1, __2) -> peerDisconnected(peer)); + } + + private void peerDisconnected(Eth2Peer peer) { + UInt256 uintPeerId = nodeIdToUInt(peer.getId()); + listeners.forEach(l -> l.peerDisconnected(uintPeerId)); + connectedPeers.remove(uintPeerId); + } + + private UInt256 nodeIdToUInt(NodeId nodeId) { + return UInt256.fromBytes(nodeId.toBytes()); + } + + @Override + public void addPeerListener(PeerListener listener) { + listeners.subscribe(listener); + } + + @Override + public void banNode(UInt256 node) { + // TODO + } + + @Override + public SafeFuture> requestDataColumnSidecar(UInt256 nodeId, List columnIdentifiers) { + Eth2Peer eth2Peer = connectedPeers.get(nodeId); + if (eth2Peer == null) { + return SafeFuture.failedFuture(new DataColumnReqResp.DasPeerDisconnectedException()); + } else { + List responseCollector = new ArrayList<>(); + return eth2Peer + .requestDataColumnSidecarsByRoot( + columnIdentifiers, + sidecar -> { + responseCollector.add(sidecar); + return SafeFuture.COMPLETE; + }) + .thenApply(__ -> responseCollector); + } + } + + @Override + public int getCurrentRequestLimit(UInt256 nodeId) { + Eth2Peer eth2Peer = connectedPeers.get(nodeId); + if (eth2Peer == null) { + return 0; + } else { + return (int) eth2Peer.getAvailableDataColumnSidecarsRequestCount(); + } + } +} diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 6c70b7d9842..b0767ab3a6f 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -87,6 +87,7 @@ import tech.pegasys.teku.networking.eth2.gossip.subnets.StableSubnetSubscriber; import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; import tech.pegasys.teku.networking.eth2.mock.NoOpEth2P2PNetwork; +import tech.pegasys.teku.networking.eth2.peers.DataColumnPeerManagerImpl; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig; import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.networks.StateBoostrapConfig; @@ -640,6 +641,11 @@ protected void initDasCustody() { this.dataColumnSidecarCustody = dataColumnSidecarCustodyImpl; } + protected void initDataColumnPeerManager() { + DataColumnPeerManagerImpl dasPeerManager = new DataColumnPeerManagerImpl(); + p2pNetwork.subscribeConnect(dasPeerManager); + } + protected void initMergeMonitors() { if (spec.isMilestoneSupported(SpecMilestone.BELLATRIX)) { terminalPowBlockMonitor = From 23f1416882aca6f1541e49df59dcd7fe3a02977b Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 17:13:39 +0400 Subject: [PATCH 50/70] First efforts to launch DasCustodySync (#41) * Add draft init code to create DasCustodySync in BeaconChainController * Make the primitive triggering of SimpleSidecarRetriever: every 1 sec * Fix GossipForkManager to subscribe currentDataColumnSidecarSubnets on startSubscriptions * Use the right NodeId in the DataColumnPeerManagerImpl * BeaconChainController.initDasCustody(): * subscribe missing elements to slot channel events. * Add DasCustodySync start initiator --- .../retriever/SimpleSidecarRetriever.java | 42 ++++++++++++++----- .../eth2/gossip/forks/GossipForkManager.java | 1 + .../eth2/peers/DataColumnPeerManagerImpl.java | 16 +++---- .../beaconchain/BeaconChainController.java | 35 ++++++++++++++-- 4 files changed, 70 insertions(+), 24 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 5add1a2b5b2..ce7aad4bd30 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.statetransition.datacolumns.retriever; +import java.time.Duration; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; @@ -22,12 +23,15 @@ import java.util.Map; import java.util.Optional; import java.util.Set; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecVersion; -import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; @@ -37,23 +41,29 @@ // prevent potential dead locks public class SimpleSidecarRetriever implements DataColumnSidecarRetriever, DataColumnPeerManager.PeerListener { + private static final Logger LOG = LogManager.getLogger(); private final Spec spec; - private final DataColumnPeerManager peerManager; private final DataColumnPeerSearcher peerSearcher; private final DasPeerCustodyCountSupplier custodyCountSupplier; private final DataColumnReqResp reqResp; + private final AsyncRunner asyncRunner; + private final Duration roundPeriod; public SimpleSidecarRetriever( Spec spec, DataColumnPeerManager peerManager, - DataColumnPeerSearcher peerSearcher, DasPeerCustodyCountSupplier custodyCountSupplier, + DataColumnPeerSearcher peerSearcher, + DasPeerCustodyCountSupplier custodyCountSupplier, DataColumnReqResp reqResp, - DataColumnSidecarValidator validator) { + DataColumnSidecarValidator validator, + AsyncRunner asyncRunner, + Duration roundPeriod) { this.spec = spec; - this.peerManager = peerManager; this.peerSearcher = peerSearcher; this.custodyCountSupplier = custodyCountSupplier; + this.asyncRunner = asyncRunner; + this.roundPeriod = roundPeriod; this.reqResp = new ValidatingDataColumnReqResp(peerManager, reqResp, validator); peerManager.addPeerListener(this); } @@ -61,6 +71,15 @@ public SimpleSidecarRetriever( private final Map pendingRequests = new LinkedHashMap<>(); private final Map connectedPeers = new HashMap<>(); + private boolean started = false; + + private void startIfNecessary() { + if (!started) { + started = true; + asyncRunner.runWithFixedDelay( + this::nextRound, roundPeriod, err -> LOG.info("Unexpected error", err)); + } + } @Override public synchronized SafeFuture retrieve(ColumnSlotAndIdentifier columnId) { @@ -72,6 +91,7 @@ public synchronized SafeFuture retrieve(ColumnSlotAndIdentifi if (existingRequest == null) { RetrieveRequest request = new RetrieveRequest(columnId, peerSearchRequest); pendingRequests.put(columnId, request); + startIfNecessary(); return request.result; } else { peerSearchRequest.dispose(); @@ -121,7 +141,6 @@ private void disposeCancelledRequests() { } } - // TODO implement triggering of rounds or do it in a finer grained fashion void nextRound() { List matches = matchRequestsAndPeers(); for (RequestMatch match : matches) { @@ -174,17 +193,18 @@ private RetrieveRequest( private class ConnectedPeer { final UInt256 nodeId; -// final int extraCustodySubnetCount; - public ConnectedPeer(UInt256 nodeId/*, int extraCustodySubnetCount*/) { + // final int extraCustodySubnetCount; + + public ConnectedPeer(UInt256 nodeId /*, int extraCustodySubnetCount*/) { this.nodeId = nodeId; -// this.extraCustodySubnetCount = extraCustodySubnetCount; + // this.extraCustodySubnetCount = extraCustodySubnetCount; } private Set getNodeCustodyIndexes(UInt64 slot) { SpecVersion specVersion = spec.atSlot(slot); -// int minCustodyRequirement = -// SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); + // int minCustodyRequirement = + // SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); return MiscHelpersEip7594.required(specVersion.miscHelpers()) .computeCustodyColumnIndexes( nodeId, custodyCountSupplier.getCustodyCountForPeer(nodeId)); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java index 555bb3b012f..917e214edc8 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/forks/GossipForkManager.java @@ -307,6 +307,7 @@ private void startSubscriptions(final GossipForkSubscriptions subscription) { recentChainData.isChainHeadOptimistic()); currentAttestationSubnets.forEach(subscription::subscribeToAttestationSubnetId); currentSyncCommitteeSubnets.forEach(subscription::subscribeToSyncCommitteeSubnet); + currentDataColumnSidecarSubnets.forEach(subscription::subscribeToDataColumnSidecarSubnet); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java index c9eb7d48a96..241635fb751 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java @@ -28,20 +28,16 @@ public void onConnected(Eth2Peer peer) { } private void peerConnected(Eth2Peer peer) { - UInt256 uintPeerId = nodeIdToUInt(peer.getId()); - listeners.forEach(l -> l.peerConnected(uintPeerId)); - connectedPeers.put(uintPeerId, peer); + UInt256 nodeId = peer.getDiscoveryNodeId(); + listeners.forEach(l -> l.peerConnected(nodeId)); + connectedPeers.put(nodeId, peer); peer.subscribeDisconnect((__1, __2) -> peerDisconnected(peer)); } private void peerDisconnected(Eth2Peer peer) { - UInt256 uintPeerId = nodeIdToUInt(peer.getId()); - listeners.forEach(l -> l.peerDisconnected(uintPeerId)); - connectedPeers.remove(uintPeerId); - } - - private UInt256 nodeIdToUInt(NodeId nodeId) { - return UInt256.fromBytes(nodeId.toBytes()); + UInt256 nodeId = peer.getDiscoveryNodeId(); + listeners.forEach(l -> l.peerDisconnected(nodeId)); + connectedPeers.remove(nodeId); } @Override diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index b0767ab3a6f..c3d571b9579 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -137,11 +137,18 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; +import tech.pegasys.teku.statetransition.datacolumns.DasCustodySync; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustodyImpl; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarDBImpl; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManager; import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManagerImpl; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DasPeerCustodyCountSupplier; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnPeerSearcher; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqResp; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqRespBatchingImpl; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnSidecarRetriever; +import tech.pegasys.teku.statetransition.datacolumns.retriever.SimpleSidecarRetriever; import tech.pegasys.teku.statetransition.forkchoice.ForkChoice; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifier; import tech.pegasys.teku.statetransition.forkchoice.ForkChoiceNotifierImpl; @@ -288,6 +295,7 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile BlobSidecarManager blobSidecarManager; protected volatile DataColumnSidecarManager dataColumnSidecarManager; protected volatile DataColumnSidecarCustody dataColumnSidecarCustody; + protected volatile DasCustodySync dasCustodySync; protected volatile Optional terminalPowBlockMonitor = Optional.empty(); protected volatile ProposersDataManager proposersDataManager; protected volatile KeyValueStore keyValueStore; @@ -377,7 +385,8 @@ protected void startServices() { blockManager.start(), syncService.start(), SafeFuture.fromRunnable( - () -> terminalPowBlockMonitor.ifPresent(TerminalPowBlockMonitor::start))) + () -> terminalPowBlockMonitor.ifPresent(TerminalPowBlockMonitor::start)), + SafeFuture.fromRunnable(() -> dasCustodySync.start())) .finish( error -> { Throwable rootCause = Throwables.getRootCause(error); @@ -636,14 +645,34 @@ protected void initDasCustody() { DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = new DataColumnSidecarCustodyImpl( spec, blockRootResolver, sidecarDB, nodeId, totalCustodySubnets); + eventChannels.subscribe(SlotEventsChannel.class, dataColumnSidecarCustodyImpl); dataColumnSidecarManager.subscribeToValidDataColumnSidecars( dataColumnSidecarCustodyImpl::onNewValidatedDataColumnSidecar); this.dataColumnSidecarCustody = dataColumnSidecarCustodyImpl; - } - protected void initDataColumnPeerManager() { DataColumnPeerManagerImpl dasPeerManager = new DataColumnPeerManagerImpl(); p2pNetwork.subscribeConnect(dasPeerManager); + + DataColumnReqResp dasRpc = new DataColumnReqRespBatchingImpl(dasPeerManager); + // TODO there is no generic solution to retrieve extra custody subnet count for a connected peer + DasPeerCustodyCountSupplier custodyCountSupplier = + DasPeerCustodyCountSupplier.createStub(custodyRequirement); + // TODO NOOP peer searcher should work for interop but needs to be implemented + DataColumnPeerSearcher dataColumnPeerSearcher = DataColumnPeerSearcher.NOOP; + // TODO + DataColumnSidecarValidator dataColumnSidecarValidator = DataColumnSidecarValidator.NOOP; + DataColumnSidecarRetriever sidecarRetriever = + new SimpleSidecarRetriever( + spec, + dasPeerManager, + dataColumnPeerSearcher, + custodyCountSupplier, + dasRpc, + dataColumnSidecarValidator, + operationPoolAsyncRunner, + Duration.ofSeconds(1)); + dasCustodySync = new DasCustodySync(dataColumnSidecarCustodyImpl, sidecarRetriever); + eventChannels.subscribe(SlotEventsChannel.class, dasCustodySync); } protected void initMergeMonitors() { From b7ae7b9d03488245afc0a9058a9912d07aae0502 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 17:15:45 +0400 Subject: [PATCH 51/70] Fixes to make sync working (#42) * Fix NodeRecordConverter.convertPublicKeyToNodeId() (would need more api from discovery to make it more or less nice) * Align computeDataColumnSidecarBackboneSubnets with current spec * Make DataColumnBlockRootResolver resolve only blocks with non-empty blob list: blocks without blobs have no columns so we are just ignoring them in the DAS subsystem * Relax DataColumnSidecarsByRoot RPC slot range requirement for now: will return them back when adopt byRange method variant * Fix bug in SimpleSidecarRetriever * Fix something in DasCustodySync * Fix updating DB firstIncompleteSlot --- .../GetCustodyColumnsTestExecutor.java | 5 +- .../eip7594/helpers/MiscHelpersEip7594.java | 50 ++++++----- .../datacolumns/DasCustodySync.java | 83 ++++++++++++----- .../DataColumnSidecarCustodyImpl.java | 89 +++++++++++++++---- .../retriever/SimpleSidecarRetriever.java | 3 +- ...ataColumnSidecarsByRootMessageHandler.java | 5 +- .../discovery/discv5/NodeRecordConverter.java | 12 ++- .../beaconchain/BeaconChainController.java | 15 +++- 8 files changed, 190 insertions(+), 72 deletions(-) diff --git a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java index 201d46add2e..f8429257143 100644 --- a/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java +++ b/eth-reference-tests/src/referenceTest/java/tech/pegasys/teku/reference/eip7594/networking/GetCustodyColumnsTestExecutor.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import java.math.BigInteger; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -35,10 +36,10 @@ public void runTest(final TestDefinition testDefinition) throws Exception { final GetCustodyColumnsMetaData metaData = loadYaml(testDefinition, "meta.yaml", GetCustodyColumnsMetaData.class); final SpecVersion spec = testDefinition.getSpec().getGenesisSpec(); - final Set actualResult = + final List actualResult = MiscHelpersEip7594.required(spec.miscHelpers()) .computeCustodyColumnIndexes(metaData.getNodeId(), metaData.getCustodySubnetCount()); - assertThat(actualResult).isEqualTo(metaData.getResult()); + assertThat(new HashSet<>(actualResult)).isEqualTo(metaData.getResult()); } private static class GetCustodyColumnsMetaData { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index aeb0b132770..2370ffc8b71 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -16,13 +16,11 @@ import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.bytesToUInt64; import static tech.pegasys.teku.spec.logic.common.helpers.MathHelpers.uint256ToBytes; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; import java.util.stream.IntStream; +import java.util.stream.Stream; import org.apache.commons.lang3.tuple.Pair; import org.apache.tuweni.bytes.Bytes32; import org.apache.tuweni.units.bigints.UInt256; @@ -84,7 +82,12 @@ public UInt64 computeSubnetForDataColumnSidecar(UInt64 columnIndex) { return columnIndex.mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); } - public Set computeCustodyColumnIndexes(final UInt256 nodeId, final int subnetCount) { + private UInt64 computeCustodySubnetIndex(final UInt256 nodeId) { + return bytesToUInt64(Hash.sha256(uint256ToBytes(nodeId)).slice(0, 8)) + .mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); + } + + public List computeCustodySubnetIndexes(final UInt256 nodeId, final int subnetCount) { // assert custody_subnet_count <= DATA_COLUMN_SIDECAR_SUBNET_COUNT if (subnetCount > specConfigEip7594.getDataColumnSidecarSubnetCount()) { throw new IllegalArgumentException( @@ -93,28 +96,30 @@ public Set computeCustodyColumnIndexes(final UInt256 nodeId, final int s subnetCount, specConfigEip7594.getNumberOfColumns())); } - final List subnetIds = new ArrayList<>(); - UInt256 curId = nodeId; - while (subnetIds.size() < subnetCount) { - final UInt64 subnetId = - bytesToUInt64(Hash.sha256(uint256ToBytes(curId)).slice(0, 8)) - .mod(specConfigEip7594.getDataColumnSidecarSubnetCount()); - if (!subnetIds.contains(subnetId)) { - subnetIds.add(subnetId); - } - if (curId.equals(UInt256.MAX_VALUE)) { - curId = UInt256.ZERO; - } - curId = curId.plus(1); + return Stream.iterate(nodeId, this::incrementByModule) + .map(this::computeCustodySubnetIndex) + .distinct() + .limit(subnetCount) + .sorted() + .toList(); + } + + private UInt256 incrementByModule(UInt256 n) { + if (n.equals(UInt256.MAX_VALUE)) { + return UInt256.ZERO; + } else { + return n.plus(1); } + } + public List computeCustodyColumnIndexes(final UInt256 nodeId, final int subnetCount) { + List subnetIds = computeCustodySubnetIndexes(nodeId, subnetCount); final int columnsPerSubnet = specConfigEip7594 .getNumberOfColumns() .dividedBy(specConfigEip7594.getDataColumnSidecarSubnetCount()) .intValue(); return subnetIds.stream() - .sorted() .flatMap( subnetId -> IntStream.range(0, columnsPerSubnet).mapToObj(i -> Pair.of(subnetId, i))) .map( @@ -123,16 +128,13 @@ public Set computeCustodyColumnIndexes(final UInt256 nodeId, final int s specConfigEip7594.getDataColumnSidecarSubnetCount() * pair.getRight() + pair.getLeft().intValue()) .map(UInt64::valueOf) - .collect(Collectors.toUnmodifiableSet()); + .sorted() + .toList(); } - // TODO public List computeDataColumnSidecarBackboneSubnets( final UInt256 nodeId, final UInt64 epoch, final int subnetCount) { - // TODO: implement whatever formula is finalized - return IntStream.range(0, subnetCount) - .mapToObj(index -> computeSubscribedSubnet(nodeId, epoch, index)) - .toList(); + return computeCustodySubnetIndexes(nodeId, subnetCount); } @Override diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index c7c76a11baf..5ce35ce9c03 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -17,7 +17,12 @@ import java.util.Iterator; import java.util.Map; import java.util.Set; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletionException; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import tech.pegasys.teku.ethereum.events.SlotEventsChannel; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; @@ -25,30 +30,57 @@ import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnSidecarRetriever; public class DasCustodySync implements SlotEventsChannel { + private static final Logger LOG = LogManager.getLogger(); private final UpdatableDataColumnSidecarCustody custody; private final DataColumnSidecarRetriever retriever; - private final int maxPendingColumnRequests = 1024; - private final int minPendingColumnRequests = 512; + private final int maxPendingColumnRequests; + private final int minPendingColumnRequests; - private Map pendingRequests = new HashMap<>(); + private final Map pendingRequests = new HashMap<>(); private boolean started = false; + private boolean coolDownTillNextSlot = false; + private final AtomicLong syncedColumnCount = new AtomicLong(); public DasCustodySync( - UpdatableDataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { + UpdatableDataColumnSidecarCustody custody, + DataColumnSidecarRetriever retriever, + int maxPendingColumnRequests, + int minPendingColumnRequests) { this.custody = custody; this.retriever = retriever; + this.maxPendingColumnRequests = maxPendingColumnRequests; + this.minPendingColumnRequests = minPendingColumnRequests; + } + + public DasCustodySync( + UpdatableDataColumnSidecarCustody custody, DataColumnSidecarRetriever retriever) { + this(custody, retriever, 10 * 1024, 2 * 1024); } - private synchronized void onRequestComplete(PendingRequest request) { - DataColumnSidecar result = request.columnPromise.join(); - custody.onNewValidatedDataColumnSidecar(result); + private synchronized void onRequestComplete(PendingRequest request, DataColumnSidecar response) { + custody.onNewValidatedDataColumnSidecar(response); pendingRequests.remove(request.columnId); + syncedColumnCount.incrementAndGet(); fillUpIfNeeded(); } + private boolean wasCancelledImplicitly(Throwable exception) { + return exception instanceof CancellationException + || (exception instanceof CompletionException + && exception.getCause() instanceof CancellationException); + } + + private synchronized void onRequestException(PendingRequest request, Throwable exception) { + if (wasCancelledImplicitly(exception)) { + // request was cancelled explicitly here + } else { + LOG.warn("Unexpected exception", exception); + } + } + private void fillUpIfNeeded() { - if (started && pendingRequests.size() <= minPendingColumnRequests) { + if (started && pendingRequests.size() <= minPendingColumnRequests && !coolDownTillNextSlot) { fillUp(); } } @@ -58,26 +90,36 @@ private synchronized void fillUp() { Set missingColumnsToRequest = custody .streamMissingColumns() - .filter(c -> !pendingRequests.containsKey(c)) + .filter(columnSlotId -> !pendingRequests.containsKey(columnSlotId)) .limit(newRequestCount) .collect(Collectors.toSet()); - // cancel those which are not missing anymore for whatever reason - Iterator> it = - pendingRequests.entrySet().iterator(); - while (it.hasNext()) { - Map.Entry pendingEntry = it.next(); - if (!missingColumnsToRequest.contains(pendingEntry.getKey())) { - pendingEntry.getValue().columnPromise().cancel(true); - it.remove(); - } - } + // TODO cancel those which are not missing anymore for whatever reason for (ColumnSlotAndIdentifier missingColumn : missingColumnsToRequest) { SafeFuture promise = retriever.retrieve(missingColumn); PendingRequest request = new PendingRequest(missingColumn, promise); pendingRequests.put(missingColumn, request); - promise.thenAccept(__ -> onRequestComplete(request)).ifExceptionGetsHereRaiseABug(); + promise.finish( + response -> onRequestComplete(request, response), + err -> onRequestException(request, err)); + } + + if (missingColumnsToRequest.isEmpty()) { + coolDownTillNextSlot = true; + } + + { + Set missingSlots = + missingColumnsToRequest.stream() + .map(ColumnSlotAndIdentifier::slot) + .collect(Collectors.toSet()); + LOG.debug( + "DataCustodySync.fillUp: synced={} pending={}, missingColumns={}({})", + syncedColumnCount, + pendingRequests.size(), + missingColumnsToRequest.size(), + missingSlots); } } @@ -96,6 +138,7 @@ public synchronized void stop() { @Override public void onSlot(UInt64 slot) { + coolDownTillNextSlot = false; fillUpIfNeeded(); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 82415c3c0a9..ba28914e474 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -19,6 +19,8 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes32; @@ -35,9 +37,13 @@ public class DataColumnSidecarCustodyImpl implements UpdatableDataColumnSidecarCustody, SlotEventsChannel { - public interface BlockRootResolver { + public interface DataColumnBlockRootResolver { - Optional getCanonicalBlockRootAtSlot(UInt64 slot); + /** + * Should return the canonical block root at slot if: - a block exist at this slot - block + * contains any blobs + */ + Optional getColumnBlockRootAtSlot(UInt64 slot); } private record SlotCustody( @@ -62,6 +68,10 @@ public Collection getIncompleteColumns() { .toList(); } + public boolean isComplete() { + return canonicalBlockRoot().isPresent() && !isIncomplete(); + } + public boolean isIncomplete() { return !getIncompleteColumns().isEmpty(); } @@ -72,7 +82,7 @@ public boolean isIncomplete() { private final Spec spec; private final DataColumnSidecarDB db; - private final BlockRootResolver blockRootResolver; + private final DataColumnBlockRootResolver blockRootResolver; private final UInt256 nodeId; private final int totalCustodySubnetCount; @@ -82,7 +92,7 @@ public boolean isIncomplete() { public DataColumnSidecarCustodyImpl( Spec spec, - BlockRootResolver blockRootResolver, + DataColumnBlockRootResolver blockRootResolver, DataColumnSidecarDB db, UInt256 nodeId, int totalCustodySubnetCount) { @@ -114,11 +124,11 @@ private UInt64 getEarliestCustodyEpoch(UInt64 currentEpoch) { return currentEpoch.minusMinZero(custodyPeriod).max(eip7594StartEpoch); } - private Set getCustodyColumnsForSlot(UInt64 slot) { + private List getCustodyColumnsForSlot(UInt64 slot) { return getCustodyColumnsForEpoch(spec.computeEpochAtSlot(slot)); } - private Set getCustodyColumnsForEpoch(UInt64 epoch) { + private List getCustodyColumnsForEpoch(UInt64 epoch) { return MiscHelpersEip7594.required(spec.atEpoch(epoch).miscHelpers()) .computeCustodyColumnIndexes(nodeId, totalCustodySubnetCount); } @@ -152,7 +162,7 @@ public SafeFuture> getCustodyDataColumnSidecar( private void onEpoch(UInt64 epoch) { UInt64 pruneSlot = spec.computeStartSlotAtEpoch(getEarliestCustodyEpoch(epoch)); db.pruneAllSidecars(pruneSlot); - advanceLatestCompleteSlot(); + advanceFirstIncompleteSlot(); } @Override @@ -164,14 +174,40 @@ public void onSlot(UInt64 slot) { } } - private void advanceLatestCompleteSlot() { - streamSlotCustodies() - .dropWhile(slotCustody -> !slotCustody.isIncomplete()) - .findFirst() - .ifPresent(firstIncomplete -> db.setFirstIncompleteSlot(firstIncomplete.slot)); + private void advanceFirstIncompleteSlot() { + record CompleteIncomplete(SlotCustody firstIncomplete, SlotCustody lastComplete) { + static final CompleteIncomplete ZERO = new CompleteIncomplete(null, null); + + CompleteIncomplete add(SlotCustody newCustody) { + if (firstIncomplete == null && newCustody.isIncomplete()) { + return new CompleteIncomplete(newCustody, lastComplete); + } else if (newCustody.isComplete()) { + return new CompleteIncomplete(firstIncomplete, newCustody); + } else { + return this; + } + } + + Optional getFirstIncompleteSlot() { + if (firstIncomplete != null) { + return Optional.of(firstIncomplete.slot); + } else if (lastComplete != null) { + return Optional.of(lastComplete.slot.increment()); + } else { + return Optional.empty(); + } + } + } + + streamPotentiallyIncompleteSlotCustodies() + .map(scan(CompleteIncomplete.ZERO, CompleteIncomplete::add)) + .takeWhile(c -> c.firstIncomplete == null) + .reduce((a, b) -> b) + .flatMap(CompleteIncomplete::getFirstIncompleteSlot) // take the last lement + .ifPresent(db::setFirstIncompleteSlot); } - private Stream streamSlotCustodies() { + private Stream streamPotentiallyIncompleteSlotCustodies() { if (currentSlot == null) { return Stream.empty(); } @@ -180,14 +216,12 @@ private Stream streamSlotCustodies() { db.getFirstIncompleteSlot().orElseGet(() -> getEarliestCustodySlot(currentSlot)); return Stream.iterate( - firstIncompleteSlot, - slot -> slot.plus(gossipWaitSlots).isLessThanOrEqualTo(currentSlot), - UInt64::increment) + firstIncompleteSlot, slot -> slot.isLessThanOrEqualTo(currentSlot), UInt64::increment) .map( slot -> { Optional maybeCanonicalBlockRoot = - blockRootResolver.getCanonicalBlockRootAtSlot(slot); - Set requiredColumns = getCustodyColumnsForSlot(slot); + blockRootResolver.getColumnBlockRootAtSlot(slot); + List requiredColumns = getCustodyColumnsForSlot(slot); List existingColumns = db.streamColumnIdentifiers(slot).toList(); return new SlotCustody( @@ -197,10 +231,27 @@ private Stream streamSlotCustodies() { @Override public Stream streamMissingColumns() { - return streamSlotCustodies() + return streamPotentiallyIncompleteSlotCustodies() + // waiting a column for [gossipWaitSlots] to be delivered by gossip + // and not considering it missing yet + .takeWhile( + slotCustody -> slotCustody.slot.plus(gossipWaitSlots).isLessThanOrEqualTo(currentSlot)) .flatMap( slotCustody -> slotCustody.getIncompleteColumns().stream() .map(colId -> new ColumnSlotAndIdentifier(slotCustody.slot(), colId))); } + + private static Function scan( + TAccum identity, BiFunction combiner) { + return new Function<>() { + private TAccum curValue = identity; + + @Override + public TAccum apply(TElem elem) { + curValue = combiner.apply(curValue, elem); + return curValue; + } + }; + } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index ce7aad4bd30..6e313ee6a37 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -161,6 +161,7 @@ private void reqRespCompleted( synchronized (this) { pendingRequests.remove(request.columnId); } + request.result.complete(maybeResult); request.peerSearchRequest.dispose(); } else { request.activeRpcRequest = null; @@ -201,7 +202,7 @@ public ConnectedPeer(UInt256 nodeId /*, int extraCustodySubnetCount*/) { // this.extraCustodySubnetCount = extraCustodySubnetCount; } - private Set getNodeCustodyIndexes(UInt64 slot) { + private List getNodeCustodyIndexes(UInt64 slot) { SpecVersion specVersion = spec.atSlot(slot); // int minCustodyRequirement = // SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java index 202656e777d..e01486dec20 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -83,7 +83,7 @@ public void onIncomingMessage( final DataColumnSidecarsByRootRequestMessage message, final ResponseCallback callback) { - LOG.trace( + LOG.debug( "Peer {} requested {} data column sidecars with identifiers: {}", peer.getId(), message.size(), @@ -170,7 +170,8 @@ private SafeFuture validateMinimumRequestEpoch( final UInt64 requestedEpoch = spec.computeEpochAtSlot(maybeSlot.get()); if (!spec.isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( combinedChainDataClient.getStore(), requestedEpoch) - || requestedEpoch.isLessThan(finalizedEpoch)) { + // TODO uncomment when sync by range is ready + /* || requestedEpoch.isLessThan(finalizedEpoch)*/) { throw new RpcException( INVALID_REQUEST_CODE, String.format( diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index b5583576f37..ad0a61df71f 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -24,9 +24,11 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt64; import org.ethereum.beacon.discovery.schema.EnrField; +import org.ethereum.beacon.discovery.schema.IdentitySchema; import org.ethereum.beacon.discovery.schema.NodeRecord; -import org.ethereum.beacon.discovery.schema.NodeRecordBuilder; +import org.ethereum.beacon.discovery.schema.NodeRecordFactory; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; @@ -38,7 +40,13 @@ public class NodeRecordConverter { private static final Logger LOG = LogManager.getLogger(); public Bytes convertPublicKeyToNodeId(Bytes publicKey) { - return new NodeRecordBuilder().publicKey(publicKey).build().getNodeId(); + // TODO need to open an additional API in discovery instead of this hack + NodeRecord tempNodeRecord = + NodeRecordFactory.DEFAULT.createFromValues( + UInt64.ZERO, + new EnrField(EnrField.PKEY_SECP256K1, publicKey), + new EnrField(EnrField.ID, IdentitySchema.V4)); + return tempNodeRecord.getNodeId(); } public Optional convertToDiscoveryPeer( diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index c3d571b9579..c3ee4fd9b07 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -627,11 +627,22 @@ protected void initDasCustody() { DataColumnSidecarDBImpl sidecarDB = new DataColumnSidecarDBImpl( combinedChainDataClient, eventChannels.getPublisher(SidecarUpdateChannel.class)); - DataColumnSidecarCustodyImpl.BlockRootResolver blockRootResolver = + DataColumnSidecarCustodyImpl.DataColumnBlockRootResolver blockRootResolver = slot -> combinedChainDataClient .getBlockAtSlotExact(slot) - .thenApply(maybeBlock -> maybeBlock.map(SignedBeaconBlock::getRoot)) + .thenApply( + maybeBlock -> + maybeBlock + .filter( + block -> + block + .getBeaconBlock() + .flatMap(b -> b.getBody().toVersionEip7594()) + .map(b -> b.getBlobKzgCommitments().size()) + .orElse(0) + > 0) + .map(SignedBeaconBlock::getRoot)) .join(); int dasExtraCustodySubnetCount = beaconConfig.p2pConfig().getDasExtraCustodySubnetCount(); From 1575fdaa1210397c6dc95ad5b338dbaaa197522f Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 9 May 2024 17:21:35 +0400 Subject: [PATCH 52/70] Spotless (#43) * spotless * fix compiler warns * make tests compile --- .../datacolumns/DasCustodySync.java | 3 +- .../DataColumnSidecarCustodyImpl.java | 4 +-- .../retriever/BatchDataColumnReqResp.java | 4 +-- .../DasPeerCustodyCountSupplier.java | 14 +++++++- .../retriever/DataColumnPeerSearcher.java | 2 +- .../retriever/DataColumnReqResp.java | 2 ++ .../DataColumnReqRespBatchingImpl.java | 22 ++++++++++--- .../retriever/SimpleSidecarRetriever.java | 5 +-- .../eth2/Eth2P2PNetworkBuilder.java | 8 ++--- ...dToDataColumnSidecarSubnetsCalculator.java | 5 +-- .../subnets/PeerSubnetSubscriptions.java | 1 - .../eth2/gossip/subnets/SubnetScorer.java | 1 - .../eth2/peers/DataColumnPeerManagerImpl.java | 32 +++++++++++++------ .../eth2/peers/DiscoveryNodeIdExtractor.java | 13 ++++++++ .../eth2/peers/Eth2PeerFactory.java | 1 - ...ataColumnSidecarsByRootMessageHandler.java | 7 ++-- .../eth2/gossip/subnets/SubnetScorerTest.java | 8 +++-- .../peers/Eth2PeerSelectionStrategyTest.java | 1 + .../networking/eth2/peers/Eth2PeerTest.java | 2 ++ .../eth2/Eth2P2PNetworkFactory.java | 4 ++- .../eth2/peers/RespondingEth2Peer.java | 11 +++++++ .../p2p/connection/ConnectionManagerTest.java | 2 ++ .../p2p/discovery/DiscoveryNetworkTest.java | 1 + .../discv5/NodeRecordConverterTest.java | 17 ++++++++-- .../p2p/libp2p/MultiaddrUtilTest.java | 5 +++ 25 files changed, 128 insertions(+), 47 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index 5ce35ce9c03..86fe4c31a26 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -14,7 +14,6 @@ package tech.pegasys.teku.statetransition.datacolumns; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.CancellationException; @@ -75,7 +74,7 @@ private synchronized void onRequestException(PendingRequest request, Throwable e if (wasCancelledImplicitly(exception)) { // request was cancelled explicitly here } else { - LOG.warn("Unexpected exception", exception); + LOG.warn("Unexpected exception for request " + request, exception); } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index ba28914e474..da4d873ab0c 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -201,8 +201,8 @@ Optional getFirstIncompleteSlot() { streamPotentiallyIncompleteSlotCustodies() .map(scan(CompleteIncomplete.ZERO, CompleteIncomplete::add)) - .takeWhile(c -> c.firstIncomplete == null) - .reduce((a, b) -> b) + .takeWhile(c -> c.firstIncomplete == null) + .reduce((a, b) -> b) .flatMap(CompleteIncomplete::getFirstIncompleteSlot) // take the last lement .ifPresent(db::setFirstIncompleteSlot); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java index 629afc0976c..fa023170fe4 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/BatchDataColumnReqResp.java @@ -13,14 +13,12 @@ package tech.pegasys.teku.statetransition.datacolumns.retriever; +import java.util.List; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; -import java.util.List; -import java.util.Optional; - public interface BatchDataColumnReqResp { SafeFuture> requestDataColumnSidecar( diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java index 995d1c42bf4..8d386a42c0e 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java @@ -1,3 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.statetransition.datacolumns.retriever; import org.apache.tuweni.units.bigints.UInt256; @@ -9,5 +22,4 @@ static DasPeerCustodyCountSupplier createStub(int defaultValue) { } int getCustodyCountForPeer(UInt256 nodeId); - } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java index 3378753c3f5..f9c270ce2ac 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnPeerSearcher.java @@ -19,7 +19,7 @@ public interface DataColumnPeerSearcher { DataColumnPeerSearcher NOOP = new DataColumnPeerSearcher() { - private final PeerSearchRequest NOOP_REQUEST = () -> {}; + private static final PeerSearchRequest NOOP_REQUEST = () -> {}; @Override public PeerSearchRequest requestPeers(UInt64 slot, UInt64 columnIndex) { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java index 28621629cd7..ad1fc7c9f67 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqResp.java @@ -21,7 +21,9 @@ public interface DataColumnReqResp { class DataColumnReqRespException extends RuntimeException {} + class DasColumnNotAvailableException extends DataColumnReqRespException {} + class DasPeerDisconnectedException extends DataColumnReqRespException {} SafeFuture requestDataColumnSidecar( diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index 6502502ec2b..432da07c42b 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -1,14 +1,26 @@ -package tech.pegasys.teku.statetransition.datacolumns.retriever; +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ -import org.apache.tuweni.units.bigints.UInt256; -import tech.pegasys.teku.infrastructure.async.SafeFuture; -import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; -import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +package tech.pegasys.teku.statetransition.datacolumns.retriever; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public class DataColumnReqRespBatchingImpl implements DataColumnReqResp { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 6e313ee6a37..b193267c59f 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -22,8 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; @@ -207,8 +205,7 @@ private List getNodeCustodyIndexes(UInt64 slot) { // int minCustodyRequirement = // SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); return MiscHelpersEip7594.required(specVersion.miscHelpers()) - .computeCustodyColumnIndexes( - nodeId, custodyCountSupplier.getCustodyCountForPeer(nodeId)); + .computeCustodyColumnIndexes(nodeId, custodyCountSupplier.getCustodyCountForPeer(nodeId)); } public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 355d9ac9bea..531f1170981 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -17,14 +17,13 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; +import io.libp2p.core.crypto.PubKey; import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; - -import io.libp2p.core.crypto.PubKey; import org.apache.tuweni.bytes.Bytes; import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.plugin.services.MetricsSystem; @@ -155,8 +154,9 @@ public Eth2P2PNetwork build() { peer -> { LibP2PPeer libP2PPeer = (LibP2PPeer) peer; PubKey libP2PPubKey = libP2PPeer.getPubKey(); - Bytes discoveryNodeIdBytes = DiscV5Service.DEFAULT_NODE_RECORD_CONVERTER.convertPublicKeyToNodeId( - Bytes.wrap(libP2PPubKey.raw())); + Bytes discoveryNodeIdBytes = + DiscV5Service.DEFAULT_NODE_RECORD_CONVERTER.convertPublicKeyToNodeId( + Bytes.wrap(libP2PPubKey.raw())); return UInt256.fromBytes(discoveryNodeIdBytes); }; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java index 0f3fe75ea68..8afca4f59d1 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java @@ -20,7 +20,6 @@ import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszBitvectorSchema; import tech.pegasys.teku.infrastructure.unsigned.UInt64; -import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; @@ -43,9 +42,7 @@ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( return (nodeId, extraSubnetCount) -> { List nodeSubnets = miscHelpers.computeDataColumnSidecarBackboneSubnets( - nodeId, - currentEpoch, - config.getCustodyRequirement() + extraSubnetCount); + nodeId, currentEpoch, config.getCustodyRequirement() + extraSubnetCount); return Optional.of( bitvectorSchema.ofBits(nodeSubnets.stream().map(UInt64::intValue).toList())); }; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index 3af3fb1aded..70547fb25fc 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -26,7 +26,6 @@ import java.util.OptionalInt; import java.util.function.Consumer; import java.util.stream.IntStream; - import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.exceptions.InvalidConfigurationException; import tech.pegasys.teku.infrastructure.metrics.SettableLabelledGauge; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java index 4ba75668bb1..3939ffcca78 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java @@ -14,7 +14,6 @@ package tech.pegasys.teku.networking.eth2.gossip.subnets; import java.util.function.IntUnaryOperator; - import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; import tech.pegasys.teku.networking.eth2.peers.PeerScorer; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java index 241635fb751..3534ce287df 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DataColumnPeerManagerImpl.java @@ -1,9 +1,25 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.networking.eth2.peers; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.subscribers.Subscribers; -import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.networking.p2p.peer.PeerConnectedSubscriber; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; @@ -11,13 +27,8 @@ import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnPeerManager; import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqResp; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -public class DataColumnPeerManagerImpl implements DataColumnPeerManager, PeerConnectedSubscriber, BatchDataColumnReqResp { +public class DataColumnPeerManagerImpl + implements DataColumnPeerManager, PeerConnectedSubscriber, BatchDataColumnReqResp { private final Subscribers listeners = Subscribers.create(true); private Map connectedPeers = new ConcurrentHashMap<>(); @@ -31,7 +42,7 @@ private void peerConnected(Eth2Peer peer) { UInt256 nodeId = peer.getDiscoveryNodeId(); listeners.forEach(l -> l.peerConnected(nodeId)); connectedPeers.put(nodeId, peer); - peer.subscribeDisconnect((__1, __2) -> peerDisconnected(peer)); + peer.subscribeDisconnect((__, ___) -> peerDisconnected(peer)); } private void peerDisconnected(Eth2Peer peer) { @@ -51,7 +62,8 @@ public void banNode(UInt256 node) { } @Override - public SafeFuture> requestDataColumnSidecar(UInt256 nodeId, List columnIdentifiers) { + public SafeFuture> requestDataColumnSidecar( + UInt256 nodeId, List columnIdentifiers) { Eth2Peer eth2Peer = connectedPeers.get(nodeId); if (eth2Peer == null) { return SafeFuture.failedFuture(new DataColumnReqResp.DasPeerDisconnectedException()); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java index 2f77d787199..9a73579d344 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DiscoveryNodeIdExtractor.java @@ -1,3 +1,16 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + package tech.pegasys.teku.networking.eth2.peers; import org.apache.tuweni.units.bigints.UInt256; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java index 1a308b3710a..f9d507f06aa 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java @@ -14,7 +14,6 @@ package tech.pegasys.teku.networking.eth2.peers; import java.util.Optional; - import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.time.TimeProvider; import tech.pegasys.teku.infrastructure.unsigned.UInt64; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java index e01486dec20..3dbd0f325ea 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -152,6 +152,7 @@ private UInt64 getFinalizedEpoch() { *

  • The block root references a block greater than or equal to the minimum_request_epoch * */ + @SuppressWarnings("unused") private SafeFuture validateMinimumRequestEpoch( final DataColumnIdentifier identifier, final Optional maybeSidecar, @@ -169,9 +170,9 @@ private SafeFuture validateMinimumRequestEpoch( } final UInt64 requestedEpoch = spec.computeEpochAtSlot(maybeSlot.get()); if (!spec.isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( - combinedChainDataClient.getStore(), requestedEpoch) - // TODO uncomment when sync by range is ready - /* || requestedEpoch.isLessThan(finalizedEpoch)*/) { + combinedChainDataClient.getStore(), requestedEpoch) + // TODO uncomment when sync by range is ready + /* || requestedEpoch.isLessThan(finalizedEpoch)*/ ) { throw new RpcException( INVALID_REQUEST_CODE, String.format( diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java index 5b5fd0da28a..9827c45d56e 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorerTest.java @@ -15,6 +15,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.entry; +import static tech.pegasys.teku.networking.p2p.discovery.discv5.DiscV5Service.DEFAULT_NODE_RECORD_CONVERTER; import it.unimi.dsi.fastutil.ints.IntList; import it.unimi.dsi.fastutil.ints.IntLists; @@ -204,9 +205,12 @@ private void assertCandidatePeerScores( private DiscoveryPeer createDiscoveryPeer(SszBitvector attSubnets, SszBitvector syncSubnets) { try { - return new DiscoveryPeer( + Bytes pubKey = Bytes.fromHexString( - "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"), + "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"); + return new DiscoveryPeer( + pubKey, + DEFAULT_NODE_RECORD_CONVERTER.convertPublicKeyToNodeId(pubKey), new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), Optional.empty(), attSubnets, diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java index c302f614a55..d6a14c20121 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerSelectionStrategyTest.java @@ -335,6 +335,7 @@ private static DiscoveryPeer createDiscoveryPeer(final PeerAddress peer, final i private static DiscoveryPeer createDiscoveryPeer(final Bytes peerId, final int... attnets) { return new DiscoveryPeer( + peerId, peerId, new InetSocketAddress(InetAddress.getLoopbackAddress(), peerId.trimLeadingZeros().toInt()), ENR_FORK_ID, diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java index f16c0f69379..c8e13ba5a73 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerTest.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Optional; import java.util.stream.IntStream; +import org.apache.tuweni.units.bigints.UInt256; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import tech.pegasys.teku.infrastructure.async.SafeFuture; @@ -72,6 +73,7 @@ class Eth2PeerTest { Eth2Peer.create( spec, delegate, + UInt256.ZERO, rpcMethods, statusMessageFactory, metadataMessagesFactory, diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index f42ba3b7de5..da38f4aff73 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -34,6 +34,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.units.bigints.UInt256; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.infrastructure.async.DelayedExecutorAsyncRunner; @@ -238,7 +239,8 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { 500, 50, spec, - KZG.NOOP); + KZG.NOOP, + (pk) -> UInt256.ZERO); List> rpcMethods = eth2PeerManager.getBeaconChainMethods().all().stream() diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java index f20b763f858..1ac5fb5b615 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java @@ -26,6 +26,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.tuweni.bytes.Bytes32; +import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.infrastructure.ssz.SszData; @@ -350,6 +351,11 @@ public Optional approveBlobSidecarsRequest( public void adjustBlobSidecarsRequest( final RequestApproval blobSidecarRequests, final long returnedBlobSidecarsCount) {} + @Override + public long getAvailableDataColumnSidecarsRequestCount() { + return 0; + } + @Override public Optional approveDataColumnSidecarsRequest( final ResponseCallback callback, final long dataColumnSidecarsCount) { @@ -377,6 +383,11 @@ public int getUnansweredPingCount() { return 0; } + @Override + public UInt256 getDiscoveryNodeId() { + return UInt256.ZERO; + } + @Override public NodeId getId() { return nodeId; diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java index ccf2ac2fc49..271103ea3c7 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/connection/ConnectionManagerTest.java @@ -34,6 +34,7 @@ import java.util.function.Supplier; import java.util.stream.Stream; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -470,6 +471,7 @@ private static DiscoveryPeer createDiscoveryPeer(final PeerAddress peer, final i private static DiscoveryPeer createDiscoveryPeer(final Bytes peerId, final int... subnetIds) { return new DiscoveryPeer( peerId, + Bytes32.ZERO, new InetSocketAddress(InetAddress.getLoopbackAddress(), peerId.trimLeadingZeros().toInt()), ENR_FORK_ID, SCHEMA_DEFINITIONS_SUPPLIER.getAttnetsENRFieldSchema().ofBits(subnetIds), diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java index 854b3a7704f..c9d0f5a7579 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetworkTest.java @@ -295,6 +295,7 @@ public DiscoveryPeer createDiscoveryPeer(Optional maybeForkId) { schemaDefinitions.getSyncnetsENRFieldSchema().getDefault(); return new DiscoveryPeer( BLSPublicKey.empty().toSSZBytes(), + Bytes32.ZERO, InetSocketAddress.createUnresolved("yo", 9999), maybeForkId, SszBitvectorSchema.create(spec.getNetworkingConfig().getAttestationSubnetCount()) diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java index 64db7b7ddf4..1349193dc82 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverterTest.java @@ -56,6 +56,7 @@ class NodeRecordConverterTest { SCHEMA_DEFINITIONS.getSyncnetsENRFieldSchema(); private static final SszBitvector SYNCNETS = SYNCNETS_SCHEMA.getDefault(); private static final NodeRecordConverter CONVERTER = new NodeRecordConverter(); + private static final Bytes NODE_ID = CONVERTER.convertPublicKeyToNodeId(PUB_KEY); @Test public void shouldConvertRealEnrToDiscoveryPeer() throws Exception { @@ -64,10 +65,13 @@ public void shouldConvertRealEnrToDiscoveryPeer() throws Exception { final NodeRecord nodeRecord = NodeRecordFactory.DEFAULT.fromBase64(enr); + Bytes pubKey = + Bytes.fromHexString("0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"); + Bytes nodeId = CONVERTER.convertPublicKeyToNodeId(pubKey); final DiscoveryPeer expectedPeer = new DiscoveryPeer( - Bytes.fromHexString( - "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"), + pubKey, + nodeId, new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), Optional.empty(), ATTNETS, @@ -107,6 +111,7 @@ public void shouldUseV4PortIfV6PortSpecifiedWithNoV6Ip() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 30303), ENR_FORK_ID, ATTNETS, @@ -138,6 +143,7 @@ public void shouldConvertIpV4Record() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("129.24.31.22", 1234), ENR_FORK_ID, ATTNETS, @@ -154,6 +160,7 @@ public void shouldConvertIpV6Record() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, @@ -174,6 +181,7 @@ public void shouldConvertAttnets() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), ENR_FORK_ID, persistentSubnets, @@ -194,6 +202,7 @@ public void shouldUseEmptyAttnetsWhenFieldValueIsInvalid() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATT_SUBNET_SCHEMA.getDefault(), @@ -214,6 +223,7 @@ public void shouldConvertSyncnets() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, @@ -236,6 +246,7 @@ public void shouldUseEmptySyncnetsFieldValueIsInvalid() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), ENR_FORK_ID, ATTNETS, @@ -256,6 +267,7 @@ public void shouldConvertEnrForkId() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), Optional.of(enrForkId), ATTNETS, @@ -275,6 +287,7 @@ public void shouldNotHaveEnrForkIdWhenValueIsInvalid() { .contains( new DiscoveryPeer( PUB_KEY, + NODE_ID, new InetSocketAddress("::1", 1234), Optional.empty(), ATTNETS, diff --git a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java index 323a2959933..45411b4640e 100644 --- a/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java +++ b/networking/p2p/src/test/java/tech/pegasys/teku/networking/p2p/libp2p/MultiaddrUtilTest.java @@ -23,6 +23,7 @@ import java.net.InetSocketAddress; import java.util.Optional; import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; import org.assertj.core.api.AbstractObjectAssert; import org.junit.jupiter.api.Test; import tech.pegasys.teku.infrastructure.ssz.collections.SszBitvector; @@ -79,6 +80,7 @@ public void fromDiscoveryPeer_shouldConvertIpV4Peer() throws Exception { final DiscoveryPeer peer = new DiscoveryPeer( PUB_KEY, + Bytes32.ZERO, new InetSocketAddress(InetAddress.getByAddress(ipAddress), port), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, @@ -98,6 +100,7 @@ public void fromDiscoveryPeer_shouldConvertIpV6Peer() throws Exception { final DiscoveryPeer peer = new DiscoveryPeer( PUB_KEY, + Bytes32.ZERO, new InetSocketAddress(InetAddress.getByAddress(ipAddress), port), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, @@ -117,6 +120,7 @@ public void fromDiscoveryPeer_shouldConvertRealPeer() throws Exception { new DiscoveryPeer( Bytes.fromHexString( "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"), + Bytes32.ZERO, new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, @@ -134,6 +138,7 @@ public void fromDiscoveryPeerAsUdp_shouldConvertDiscoveryPeer() throws Exception new DiscoveryPeer( Bytes.fromHexString( "0x03B86ED9F747A7FA99963F39E3B176B45E9E863108A2D145EA3A4E76D8D0935194"), + Bytes32.ZERO, new InetSocketAddress(InetAddress.getByAddress(new byte[] {127, 0, 0, 1}), 9000), ENR_FORK_ID, PERSISTENT_ATTESTATION_SUBNETS, From 8696e58e3ed7843ba5689dd14d9b3d18d298b2b0 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 10 May 2024 18:56:59 +0400 Subject: [PATCH 53/70] Track extra subnet count by gossip subscriptions (#44) * Implement GossipTopicDasPeerCustodyTracker * Refactor DataColumnBlockRootResolver to move more logic from BeaconChainController to DataColumnSidecarCustodyImpl * Fix the case when extra_column_subnets too large --- .../DataColumnSidecarCustodyImpl.java | 30 +++-- .../DasPeerCustodyCountSupplier.java | 8 ++ ...ColumnSidecarSubnetBackboneSubscriber.java | 16 +-- .../eth2/gossip/topics/GossipTopics.java | 25 ++-- .../GossipTopicDasPeerCustodyTracker.java | 110 ++++++++++++++++++ .../beaconchain/BeaconChainController.java | 37 +++--- 6 files changed, 185 insertions(+), 41 deletions(-) create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index da4d873ab0c..1e68f95e1e2 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -31,19 +31,20 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; public class DataColumnSidecarCustodyImpl implements UpdatableDataColumnSidecarCustody, SlotEventsChannel { - public interface DataColumnBlockRootResolver { + public interface CanonicalBlockResolver { /** * Should return the canonical block root at slot if: - a block exist at this slot - block * contains any blobs */ - Optional getColumnBlockRootAtSlot(UInt64 slot); + Optional getBlockAtSlot(UInt64 slot); } private record SlotCustody( @@ -82,7 +83,7 @@ public boolean isIncomplete() { private final Spec spec; private final DataColumnSidecarDB db; - private final DataColumnBlockRootResolver blockRootResolver; + private final CanonicalBlockResolver blockResolver; private final UInt256 nodeId; private final int totalCustodySubnetCount; @@ -92,19 +93,19 @@ public boolean isIncomplete() { public DataColumnSidecarCustodyImpl( Spec spec, - DataColumnBlockRootResolver blockRootResolver, + CanonicalBlockResolver blockResolver, DataColumnSidecarDB db, UInt256 nodeId, int totalCustodySubnetCount) { checkNotNull(spec); - checkNotNull(blockRootResolver); + checkNotNull(blockResolver); checkNotNull(db); checkNotNull(nodeId); this.spec = spec; this.db = db; - this.blockRootResolver = blockRootResolver; + this.blockResolver = blockResolver; this.nodeId = nodeId; this.totalCustodySubnetCount = totalCustodySubnetCount; this.eip7594StartEpoch = spec.getForkSchedule().getFork(SpecMilestone.EIP7594).getEpoch(); @@ -219,8 +220,7 @@ private Stream streamPotentiallyIncompleteSlotCustodies() { firstIncompleteSlot, slot -> slot.isLessThanOrEqualTo(currentSlot), UInt64::increment) .map( slot -> { - Optional maybeCanonicalBlockRoot = - blockRootResolver.getColumnBlockRootAtSlot(slot); + Optional maybeCanonicalBlockRoot = getBlockRootIfHaveBlobs(slot); List requiredColumns = getCustodyColumnsForSlot(slot); List existingColumns = db.streamColumnIdentifiers(slot).toList(); @@ -229,6 +229,20 @@ private Stream streamPotentiallyIncompleteSlotCustodies() { }); } + private Optional getBlockRootIfHaveBlobs(UInt64 slot) { + return blockResolver + .getBlockAtSlot(slot) + .filter( + block -> + block + .getBeaconBlock() + .flatMap(b -> b.getBody().toVersionEip7594()) + .map(b -> b.getBlobKzgCommitments().size()) + .orElse(0) + > 0) + .map(BeaconBlock::getRoot); + } + @Override public Stream streamMissingColumns() { return streamPotentiallyIncompleteSlotCustodies() diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java index 8d386a42c0e..67f0b3bc8e6 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DasPeerCustodyCountSupplier.java @@ -13,6 +13,9 @@ package tech.pegasys.teku.statetransition.datacolumns.retriever; +import static java.lang.Integer.max; +import static java.lang.Integer.min; + import org.apache.tuweni.units.bigints.UInt256; public interface DasPeerCustodyCountSupplier { @@ -21,5 +24,10 @@ static DasPeerCustodyCountSupplier createStub(int defaultValue) { return (__) -> defaultValue; } + static DasPeerCustodyCountSupplier capped( + DasPeerCustodyCountSupplier delegate, int minValue, int maxValue) { + return (nodeId) -> min(maxValue, max(minValue, delegate.getCustodyCountForPeer(nodeId))); + } + int getCustodyCountForPeer(UInt256 nodeId); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index f36a7c144fa..4d47342921d 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -22,7 +22,6 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.SpecConfigEip7594; public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { @@ -64,19 +63,22 @@ private void subscribeToSubnets(final Collection newSubscriptions) { currentSubscribedSubnets = newSubscriptionsSet; } + private int getTotalSubnetCount(final UInt64 epoch) { + SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(spec.atEpoch(epoch).getConfig()); + return Integer.min( + configEip7594.getDataColumnSidecarSubnetCount(), + configEip7594.getCustodyRequirement() + extraVoluntarySubnetCount); + } + private void onEpoch(final UInt64 epoch) { - SpecVersion specVersion = spec.atEpoch(epoch); - specVersion + spec.atEpoch(epoch) .miscHelpers() .toVersionEip7594() .ifPresent( eip7594Spec -> { - int totalSubnetCount = - SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement() - + extraVoluntarySubnetCount; List subnets = eip7594Spec.computeDataColumnSidecarBackboneSubnets( - nodeId, epoch, totalSubnetCount); + nodeId, epoch, getTotalSubnetCount(epoch)); subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); }); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java index 1e10a3034ac..cc2a0097ac7 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/topics/GossipTopics.java @@ -13,8 +13,11 @@ package tech.pegasys.teku.networking.eth2.gossip.topics; +import java.util.Collections; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import tech.pegasys.teku.infrastructure.bytes.Bytes4; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; import tech.pegasys.teku.spec.Spec; @@ -69,6 +72,18 @@ public static String getDataColumnSidecarSubnetTopic( forkDigest, GossipTopicName.getDataColumnSidecarSubnetTopicName(subnetId), gossipEncoding); } + public static Set getAllDataColumnSidecarSubnetTopics( + final GossipEncoding gossipEncoding, final Bytes4 forkDigest, final Spec spec) { + + return spec.getNetworkingConfigEip7594() + .map( + eip7594NetworkConfig -> + IntStream.range(0, eip7594NetworkConfig.getDataColumnSidecarSubnetCount()) + .mapToObj(i -> getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)) + .collect(Collectors.toSet())) + .orElse(Collections.emptySet()); + } + public static Set getAllTopics( final GossipEncoding gossipEncoding, final Bytes4 forkDigest, final Spec spec) { final Set topics = new HashSet<>(); @@ -84,13 +99,9 @@ public static Set getAllTopics( topics.add(getBlobSidecarSubnetTopic(forkDigest, i, gossipEncoding)); } } - spec.getNetworkingConfigEip7594() - .ifPresent( - eip7594NetworkConfig -> { - for (int i = 0; i < eip7594NetworkConfig.getDataColumnSidecarSubnetCount(); i++) { - topics.add(getDataColumnSidecarSubnetTopic(forkDigest, i, gossipEncoding)); - } - }); + + topics.addAll(getAllDataColumnSidecarSubnetTopics(gossipEncoding, forkDigest, spec)); + for (GossipTopicName topicName : GossipTopicName.values()) { topics.add(GossipTopics.getTopic(forkDigest, topicName, gossipEncoding)); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java new file mode 100644 index 00000000000..7b722cd9f24 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java @@ -0,0 +1,110 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.peers; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; +import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopics; +import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; +import tech.pegasys.teku.networking.p2p.peer.NodeId; +import tech.pegasys.teku.networking.p2p.peer.PeerConnectedSubscriber; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.state.ForkInfo; +import tech.pegasys.teku.statetransition.datacolumns.retriever.DasPeerCustodyCountSupplier; + +public class GossipTopicDasPeerCustodyTracker + implements DasPeerCustodyCountSupplier, PeerConnectedSubscriber { + + public static final int NO_SUBNET_COUNT_INFO = -1; + + private final Spec spec; + private final GossipNetwork gossipNetwork; + private final GossipEncoding gossipEncoding; + private final Supplier> currentForkInfoSupplier; + + private final Map connectedPeerExtraSubnets = new ConcurrentHashMap<>(); + + public GossipTopicDasPeerCustodyTracker( + Spec spec, + GossipNetwork gossipNetwork, + GossipEncoding gossipEncoding, + Supplier> currentForkInfoSupplier) { + this.spec = spec; + this.gossipNetwork = gossipNetwork; + this.gossipEncoding = gossipEncoding; + this.currentForkInfoSupplier = currentForkInfoSupplier; + } + + @Override + public void onConnected(Eth2Peer peer) { + connectedPeerExtraSubnets.put( + peer.getDiscoveryNodeId(), new Entry(peer.getId(), NO_SUBNET_COUNT_INFO)); + peer.subscribeDisconnect((__, ___) -> peerDisconnected(peer)); + refreshExistingSubscriptions(); + } + + private void peerDisconnected(Eth2Peer peer) { + connectedPeerExtraSubnets.remove(peer.getDiscoveryNodeId()); + } + + private Set getCurrentDasTopics() { + return currentForkInfoSupplier + .get() + .map( + forkInfo -> + GossipTopics.getAllDataColumnSidecarSubnetTopics( + gossipEncoding, forkInfo.getForkDigest(spec), spec)) + .orElse(Collections.emptySet()); + } + + private void refreshExistingSubscriptions() { + Map> subscribersByTopic = gossipNetwork.getSubscribersByTopic(); + Set dasTopics = getCurrentDasTopics(); + record NodeTopic(NodeId nodeId, String topic) {} + + Map nodeToSubnetCount = + subscribersByTopic.entrySet().stream() + .flatMap( + entry -> + entry.getValue().stream().map(nodeId -> new NodeTopic(nodeId, entry.getKey()))) + .filter(entry -> dasTopics.contains(entry.topic())) + .collect(Collectors.groupingBy(NodeTopic::nodeId, Collectors.counting())); + connectedPeerExtraSubnets.replaceAll( + (nodeId, entry) -> { + Long maybeCount = nodeToSubnetCount.get(entry.libp2pPeerId()); + int count = maybeCount == null ? NO_SUBNET_COUNT_INFO : maybeCount.intValue(); + return entry.withSubnetCount(count); + }); + } + + @Override + public int getCustodyCountForPeer(UInt256 nodeId) { + Entry entry = connectedPeerExtraSubnets.get(nodeId); + return entry != null ? entry.subnetCount() : 0; + } + + private record Entry(NodeId libp2pPeerId, Integer subnetCount) { + public Entry withSubnetCount(int newCount) { + return newCount == subnetCount ? this : new Entry(libp2pPeerId, newCount); + } + } +} diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index c3ee4fd9b07..9f4acf36028 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -88,6 +88,7 @@ import tech.pegasys.teku.networking.eth2.gossip.subnets.SyncCommitteeSubscriptionManager; import tech.pegasys.teku.networking.eth2.mock.NoOpEth2P2PNetwork; import tech.pegasys.teku.networking.eth2.peers.DataColumnPeerManagerImpl; +import tech.pegasys.teku.networking.eth2.peers.GossipTopicDasPeerCustodyTracker; import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig; import tech.pegasys.teku.networks.Eth2NetworkConfiguration; import tech.pegasys.teku.networks.StateBoostrapConfig; @@ -627,35 +628,24 @@ protected void initDasCustody() { DataColumnSidecarDBImpl sidecarDB = new DataColumnSidecarDBImpl( combinedChainDataClient, eventChannels.getPublisher(SidecarUpdateChannel.class)); - DataColumnSidecarCustodyImpl.DataColumnBlockRootResolver blockRootResolver = + DataColumnSidecarCustodyImpl.CanonicalBlockResolver blockRootResolver = slot -> combinedChainDataClient .getBlockAtSlotExact(slot) - .thenApply( - maybeBlock -> - maybeBlock - .filter( - block -> - block - .getBeaconBlock() - .flatMap(b -> b.getBody().toVersionEip7594()) - .map(b -> b.getBlobKzgCommitments().size()) - .orElse(0) - > 0) - .map(SignedBeaconBlock::getRoot)) + .thenApply(sbb -> sbb.flatMap(SignedBeaconBlock::getBeaconBlock)) .join(); int dasExtraCustodySubnetCount = beaconConfig.p2pConfig().getDasExtraCustodySubnetCount(); SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); - int custodyRequirement = configEip7594.getCustodyRequirement(); + int minCustodyRequirement = configEip7594.getCustodyRequirement(); int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); - int totalCustodySubnets = - Integer.min(maxSubnets, custodyRequirement + dasExtraCustodySubnetCount); + int totalMyCustodySubnets = + Integer.min(maxSubnets, minCustodyRequirement + dasExtraCustodySubnetCount); DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = new DataColumnSidecarCustodyImpl( - spec, blockRootResolver, sidecarDB, nodeId, totalCustodySubnets); + spec, blockRootResolver, sidecarDB, nodeId, totalMyCustodySubnets); eventChannels.subscribe(SlotEventsChannel.class, dataColumnSidecarCustodyImpl); dataColumnSidecarManager.subscribeToValidDataColumnSidecars( dataColumnSidecarCustodyImpl::onNewValidatedDataColumnSidecar); @@ -665,9 +655,18 @@ protected void initDasCustody() { p2pNetwork.subscribeConnect(dasPeerManager); DataColumnReqResp dasRpc = new DataColumnReqRespBatchingImpl(dasPeerManager); - // TODO there is no generic solution to retrieve extra custody subnet count for a connected peer + + GossipTopicDasPeerCustodyTracker peerCustodyTracker = + new GossipTopicDasPeerCustodyTracker( + spec, + p2pNetwork, + beaconConfig.p2pConfig().getGossipEncoding(), + () -> recentChainData.getCurrentForkInfo()); + + p2pNetwork.subscribeConnect(peerCustodyTracker); DasPeerCustodyCountSupplier custodyCountSupplier = - DasPeerCustodyCountSupplier.createStub(custodyRequirement); + DasPeerCustodyCountSupplier.capped(peerCustodyTracker, minCustodyRequirement, maxSubnets); + // TODO NOOP peer searcher should work for interop but needs to be implemented DataColumnPeerSearcher dataColumnPeerSearcher = DataColumnPeerSearcher.NOOP; // TODO From 0e64217431187d63c76911a6c3658506198fbb67 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Mon, 13 May 2024 19:31:53 +0400 Subject: [PATCH 54/70] DataColumnSidecars by Range RPC (#45) --- .../multipeer/chains/SyncSourceFactory.java | 10 +- .../chains/ThrottlingSyncSource.java | 28 +- .../chains/ThrottlingSyncSourceTest.java | 3 +- ...taColumnSidecarsByRangeRequestMessage.java | 106 ++++++ .../schemas/SchemaDefinitionsEip7594.java | 12 + .../eth2/peers/DefaultEth2Peer.java | 71 ++++ .../networking/eth2/peers/SyncSource.java | 8 + .../rpc/beaconchain/BeaconChainMethodIds.java | 2 + .../rpc/beaconchain/BeaconChainMethods.java | 67 ++++ ...idecarsByRangeListenerValidatingProxy.java | 78 ++++ ...taColumnSidecarsByRangeMessageHandler.java | 339 ++++++++++++++++++ ...ecarsResponseInvalidResponseException.java | 1 + .../eth2/peers/RespondingEth2Peer.java | 10 + .../networking/eth2/peers/StubSyncSource.java | 11 + .../teku/storage/api/StorageQueryChannel.java | 5 + .../client/CombinedChainDataClient.java | 9 + .../teku/storage/server/ChainStorage.java | 17 + .../CombinedStorageChannelSplitter.java | 12 + .../pegasys/teku/storage/server/Database.java | 2 + .../server/kvstore/KvStoreDatabase.java | 5 + .../dataaccess/CombinedKvStoreDao.java | 7 + .../dataaccess/KvStoreCombinedDao.java | 2 + .../dataaccess/KvStoreCombinedDaoAdapter.java | 5 + .../dataaccess/V4FinalizedKvStoreDao.java | 6 + .../storage/server/noop/NoOpDatabase.java | 5 + .../storage/api/StubStorageQueryChannel.java | 11 + 26 files changed, 829 insertions(+), 3 deletions(-) create mode 100644 ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeListenerValidatingProxy.java create mode 100644 networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/SyncSourceFactory.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/SyncSourceFactory.java index 34c48f98bf2..a47d15d5c39 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/SyncSourceFactory.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/SyncSourceFactory.java @@ -46,12 +46,20 @@ public SyncSource getOrCreateSyncSource(final Eth2Peer peer, final Spec spec) { final int maxBlocksPerMinute = this.maxBlocksPerMinute - batchSize - 1; final Optional maxBlobSidecarsPerMinute = spec.getMaxBlobsPerBlock().map(maxBlobsPerBlock -> maxBlocksPerMinute * maxBlobsPerBlock); + final Optional maxDataColumnSidecarsPerMinute = + spec.getNumberOfDataColumns() + .map(dataColumnsPerBlock -> maxBlocksPerMinute * dataColumnsPerBlock.intValue()); return syncSourcesByPeer.computeIfAbsent( peer, source -> new ThrottlingSyncSource( - asyncRunner, timeProvider, source, maxBlocksPerMinute, maxBlobSidecarsPerMinute)); + asyncRunner, + timeProvider, + source, + maxBlocksPerMinute, + maxBlobSidecarsPerMinute, + maxDataColumnSidecarsPerMinute)); } public void onPeerDisconnected(final Eth2Peer peer) { diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java index 2f6ab09a4e5..33a2effacae 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.beacon.sync.forward.multipeer.chains; import java.time.Duration; +import java.util.List; import java.util.Optional; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -27,6 +28,7 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationAdjustment; import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; public class ThrottlingSyncSource implements SyncSource { @@ -38,13 +40,15 @@ public class ThrottlingSyncSource implements SyncSource { private final RateTracker blocksRateTracker; private final RateTracker blobSidecarsRateTracker; + private final RateTracker dataColumnSidecarsRateTracker; public ThrottlingSyncSource( final AsyncRunner asyncRunner, final TimeProvider timeProvider, final SyncSource delegate, final int maxBlocksPerMinute, - final Optional maybeMaxBlobSidecarsPerMinute) { + final Optional maybeMaxBlobSidecarsPerMinute, + final Optional maybeMaxDataColumnSidecarsPerMinute) { this.asyncRunner = asyncRunner; this.delegate = delegate; this.blocksRateTracker = RateTracker.create(maxBlocksPerMinute, TIME_OUT, timeProvider); @@ -54,6 +58,12 @@ public ThrottlingSyncSource( maxBlobSidecarsPerMinute -> RateTracker.create(maxBlobSidecarsPerMinute, TIME_OUT, timeProvider)) .orElse(RateTracker.NOOP); + this.dataColumnSidecarsRateTracker = + maybeMaxDataColumnSidecarsPerMinute + .map( + maxDataColumnSidecarsPerMinute -> + RateTracker.create(maxDataColumnSidecarsPerMinute, TIME_OUT, timeProvider)) + .orElse(RateTracker.NOOP); } @Override @@ -82,6 +92,22 @@ public SafeFuture requestBlobSidecarsByRange( } } + @Override + public SafeFuture requestDataColumnSidecarsByRange( + final UInt64 startSlot, + final UInt64 count, + final List columns, + final RpcResponseListener listener) { + if (dataColumnSidecarsRateTracker.approveObjectsRequest(count.longValue()).isPresent()) { + LOG.debug("Sending request for {} data column sidecars on {} columns", count, columns.size()); + return delegate.requestDataColumnSidecarsByRange(startSlot, count, columns, listener); + } else { + return asyncRunner.runAfterDelay( + () -> requestDataColumnSidecarsByRange(startSlot, count, columns, listener), + PEER_REQUEST_DELAY); + } + } + @Override public SafeFuture disconnectCleanly(final DisconnectReason reason) { return delegate.disconnectCleanly(reason); diff --git a/beacon/sync/src/test/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSourceTest.java b/beacon/sync/src/test/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSourceTest.java index 942c96a66c6..3d9f8360b86 100644 --- a/beacon/sync/src/test/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSourceTest.java +++ b/beacon/sync/src/test/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSourceTest.java @@ -54,7 +54,8 @@ class ThrottlingSyncSourceTest { timeProvider, delegate, MAX_BLOCKS_PER_MINUTE, - Optional.of(MAX_BLOB_SIDECARS_PER_MINUTE)); + Optional.of(MAX_BLOB_SIDECARS_PER_MINUTE), + Optional.empty()); @Test void shouldDelegateDisconnectImmediately() { diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java new file mode 100644 index 00000000000..85f7756042a --- /dev/null +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java @@ -0,0 +1,106 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc; + +import java.util.List; +import tech.pegasys.teku.infrastructure.ssz.collections.SszUInt64List; +import tech.pegasys.teku.infrastructure.ssz.containers.Container3; +import tech.pegasys.teku.infrastructure.ssz.containers.ContainerSchema3; +import tech.pegasys.teku.infrastructure.ssz.impl.AbstractSszPrimitive; +import tech.pegasys.teku.infrastructure.ssz.primitive.SszUInt64; +import tech.pegasys.teku.infrastructure.ssz.schema.SszListSchema; +import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; +import tech.pegasys.teku.infrastructure.ssz.schema.collections.SszUInt64ListSchema; +import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; + +public class DataColumnSidecarsByRangeRequestMessage + extends Container3 + implements RpcRequest { + + public static class DataColumnSidecarsByRangeRequestMessageSchema + extends ContainerSchema3< + DataColumnSidecarsByRangeRequestMessage, SszUInt64, SszUInt64, SszUInt64List> { + + public DataColumnSidecarsByRangeRequestMessageSchema( + final SpecConfigEip7594 specConfigEip7594) { + super( + "DataColumnSidecarsByRangeRequestMessage", + namedSchema("start_slot", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema("count", SszPrimitiveSchemas.UINT64_SCHEMA), + namedSchema( + "columns", + SszUInt64ListSchema.create(specConfigEip7594.getNumberOfColumns().intValue()))); + } + + @Override + public DataColumnSidecarsByRangeRequestMessage createFromBackingNode(final TreeNode node) { + return new DataColumnSidecarsByRangeRequestMessage(this, node); + } + + @SuppressWarnings("unchecked") + public SszListSchema getColumnsSchema() { + return (SszListSchema) getFieldSchema2(); + } + + public DataColumnSidecarsByRangeRequestMessage create( + final UInt64 startSlot, final UInt64 count, final List columns) { + return new DataColumnSidecarsByRangeRequestMessage( + this, + SszUInt64.of(startSlot), + SszUInt64.of(count), + (SszUInt64List) + this.getColumnsSchema() + .createFromElements(columns.stream().map(SszUInt64::of).toList())); + } + } + + private DataColumnSidecarsByRangeRequestMessage( + final DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema + type, + final TreeNode backingNode) { + super(type, backingNode); + } + + public DataColumnSidecarsByRangeRequestMessage( + final DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema + type, + final SszUInt64 startSlot, + final SszUInt64 count, + final SszUInt64List columns) { + super(type, startSlot, count, columns); + } + + public UInt64 getStartSlot() { + return getField0().get(); + } + + public UInt64 getCount() { + return getField1().get(); + } + + public UInt64 getMaxSlot() { + return getStartSlot().plus(getCount()).minusMinZero(1); + } + + public List getColumns() { + return getField2().stream().map(AbstractSszPrimitive::get).toList(); + } + + @Override + public int getMaximumResponseChunks() { + return getCount().intValue() * getColumns().size(); + } +} diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java index a6c74862d44..e5199d4a1ac 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java @@ -44,6 +44,7 @@ import tech.pegasys.teku.spec.datastructures.execution.ExecutionPayloadSchema; import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadHeaderSchemaEip7594; import tech.pegasys.teku.spec.datastructures.execution.versions.eip7594.ExecutionPayloadSchemaEip7594; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.BeaconStateSchema; import tech.pegasys.teku.spec.datastructures.state.beaconstate.versions.eip7594.BeaconStateEip7594; @@ -78,6 +79,9 @@ public class SchemaDefinitionsEip7594 extends SchemaDefinitionsDeneb { private final DataColumnSidecarSchema dataColumnSidecarSchema; private final DataColumnSidecarsByRootRequestMessageSchema dataColumnSidecarsByRootRequestMessageSchema; + private final DataColumnSidecarsByRangeRequestMessage + .DataColumnSidecarsByRangeRequestMessageSchema + dataColumnSidecarsByRangeRequestMessageSchema; public SchemaDefinitionsEip7594(final SpecConfigEip7594 specConfig) { super(specConfig); @@ -134,6 +138,9 @@ public SchemaDefinitionsEip7594(final SpecConfigEip7594 specConfig) { SignedBeaconBlockHeader.SSZ_SCHEMA, dataColumnSchema, specConfig); this.dataColumnSidecarsByRootRequestMessageSchema = new DataColumnSidecarsByRootRequestMessageSchema(specConfig); + this.dataColumnSidecarsByRangeRequestMessageSchema = + new DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema( + specConfig); } public static SchemaDefinitionsEip7594 required(final SchemaDefinitions schemaDefinitions) { @@ -272,4 +279,9 @@ public CellSchema getCellSchema() { getDataColumnSidecarsByRootRequestMessageSchema() { return dataColumnSidecarsByRootRequestMessageSchema; } + + public DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema + getDataColumnSidecarsByRangeRequestMessageSchema() { + return dataColumnSidecarsByRangeRequestMessageSchema; + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index ee5569a90dc..99172d26893 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -39,6 +39,7 @@ import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootValidator; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlocksByRangeListenerWrapper; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRangeListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRootListenerValidatingProxy; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.StatusMessageFactory; @@ -53,6 +54,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -63,6 +65,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; @@ -97,10 +100,14 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { private final RateTracker requestTracker; private final KZG kzg; private final Supplier firstSlotSupportingBlobSidecarsByRange; + private final Supplier firstSlotSupportingDataColumnSidecarsByRange; private final Supplier blobSidecarsByRootRequestMessageSchema; private final Supplier dataColumnSidecarsByRootRequestMessageSchema; + private final Supplier< + DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema> + dataColumnSidecarsByRangeRequestMessageSchema; private final Supplier maxBlobsPerBlock; DefaultEth2Peer( @@ -140,12 +147,24 @@ class DefaultEth2Peer extends DelegatingPeer implements Eth2Peer { SchemaDefinitionsDeneb.required( spec.forMilestone(SpecMilestone.DENEB).getSchemaDefinitions()) .getBlobSidecarsByRootRequestMessageSchema()); + this.firstSlotSupportingDataColumnSidecarsByRange = + Suppliers.memoize( + () -> { + final UInt64 eip7594ForkEpoch = getSpecConfigEip7594().getEip7594ForkEpoch(); + return spec.computeStartSlotAtEpoch(eip7594ForkEpoch); + }); this.dataColumnSidecarsByRootRequestMessageSchema = Suppliers.memoize( () -> SchemaDefinitionsEip7594.required( spec.forMilestone(SpecMilestone.EIP7594).getSchemaDefinitions()) .getDataColumnSidecarsByRootRequestMessageSchema()); + this.dataColumnSidecarsByRangeRequestMessageSchema = + Suppliers.memoize( + () -> + SchemaDefinitionsEip7594.required( + spec.forMilestone(SpecMilestone.EIP7594).getSchemaDefinitions()) + .getDataColumnSidecarsByRangeRequestMessageSchema()); this.maxBlobsPerBlock = Suppliers.memoize(() -> getSpecConfigDeneb().getMaxBlobsPerBlock()); } @@ -398,6 +417,54 @@ public SafeFuture requestBlobSidecarsByRange( .orElse(failWithUnsupportedMethodException("BlobSidecarsByRange")); } + @Override + public SafeFuture requestDataColumnSidecarsByRange( + final UInt64 startSlot, + final UInt64 count, + final List columns, + final RpcResponseListener listener) { + return rpcMethods + .getDataColumnSidecarsByRange() + .map( + method -> { + final UInt64 firstSupportedSlot = firstSlotSupportingDataColumnSidecarsByRange.get(); + final DataColumnSidecarsByRangeRequestMessage request; + + if (startSlot.isLessThan(firstSupportedSlot)) { + LOG.debug( + "Requesting data column sidecars from slot {} instead of slot {} because the request is spanning the Deneb fork transition", + firstSupportedSlot, + startSlot); + final UInt64 updatedCount = + count.minusMinZero(firstSupportedSlot.minusMinZero(startSlot)); + if (updatedCount.isZero()) { + return SafeFuture.COMPLETE; + } + request = + dataColumnSidecarsByRangeRequestMessageSchema + .get() + .create(firstSupportedSlot, updatedCount, columns); + } else { + request = + dataColumnSidecarsByRangeRequestMessageSchema + .get() + .create(startSlot, count, columns); + } + return requestStream( + method, + request, + new DataColumnSidecarsByRangeListenerValidatingProxy( + spec, + this, + listener, + kzg, + request.getStartSlot(), + request.getCount(), + request.getColumns())); + }) + .orElse(failWithUnsupportedMethodException("DataColumnSidecarsByRange")); + } + @Override public SafeFuture requestMetadata() { return requestSingleItem(rpcMethods.getMetadata(), EmptyMessage.EMPTY_MESSAGE); @@ -544,6 +611,10 @@ private SpecConfigDeneb getSpecConfigDeneb() { return SpecConfigDeneb.required(spec.forMilestone(SpecMilestone.DENEB).getConfig()); } + private SpecConfigEip7594 getSpecConfigEip7594() { + return SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); + } + private SafeFuture failWithUnsupportedMethodException(final String method) { return SafeFuture.failedFuture( new UnsupportedOperationException(method + " method is not supported")); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/SyncSource.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/SyncSource.java index 76fd491174d..5f9a4412c66 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/SyncSource.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/SyncSource.java @@ -13,12 +13,14 @@ package tech.pegasys.teku.networking.eth2.peers; +import java.util.List; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.p2p.peer.DisconnectReason; import tech.pegasys.teku.networking.p2p.reputation.ReputationAdjustment; import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; /** @@ -32,6 +34,12 @@ SafeFuture requestBlocksByRange( SafeFuture requestBlobSidecarsByRange( UInt64 startSlot, UInt64 count, RpcResponseListener listener); + SafeFuture requestDataColumnSidecarsByRange( + UInt64 startSlot, + UInt64 count, + List columns, + RpcResponseListener listener); + void adjustReputation(final ReputationAdjustment adjustment); SafeFuture disconnectCleanly(DisconnectReason reason); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java index 192d854e593..311c17658d9 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethodIds.java @@ -28,6 +28,8 @@ public class BeaconChainMethodIds { static final String DATA_COLUMN_SIDECARS_BY_ROOT = "/eth2/beacon_chain/req/data_column_sidecars_by_root"; + static final String DATA_COLUMN_SIDECARS_BY_RANGE = + "/eth2/beacon_chain/req/data_column_sidecars_by_range"; static final String GET_METADATA = "/eth2/beacon_chain/req/metadata"; static final String PING = "/eth2/beacon_chain/req/ping"; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java index 5077740d06d..97e9322e345 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BeaconBlocksByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRangeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.BlobSidecarsByRootMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRangeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsByRootMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.GoodbyeMessageHandler; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessageHandler; @@ -44,6 +45,7 @@ import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.SpecConfigDeneb; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; @@ -54,6 +56,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.BlobSidecarsByRootRequestMessageSchema; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRangeRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessageSchema; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.EmptyMessage; @@ -81,6 +84,8 @@ public class BeaconChainMethods { blobSidecarsByRange; private final Optional> dataColumnSidecarsByRoot; + private final Optional> + dataColumnSidecarsByRange; private final Eth2RpcMethod getMetadata; private final Eth2RpcMethod ping; @@ -97,6 +102,8 @@ private BeaconChainMethods( blobSidecarsByRange, final Optional> dataColumnSidecarsByRoot, + final Optional> + dataColumnSidecarsByRange, final Eth2RpcMethod getMetadata, final Eth2RpcMethod ping) { this.status = status; @@ -106,6 +113,7 @@ private BeaconChainMethods( this.blobSidecarsByRoot = blobSidecarsByRoot; this.blobSidecarsByRange = blobSidecarsByRange; this.dataColumnSidecarsByRoot = dataColumnSidecarsByRoot; + this.dataColumnSidecarsByRange = dataColumnSidecarsByRange; this.getMetadata = getMetadata; this.ping = ping; this.allMethods = @@ -163,6 +171,14 @@ public static BeaconChainMethods create( peerLookup, rpcEncoding, recentChainData), + createDataColumnsSidecarsByRange( + spec, + metricsSystem, + asyncRunner, + combinedChainDataClient, + peerLookup, + rpcEncoding, + recentChainData), createMetadata(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding), createPing(spec, asyncRunner, metadataMessagesFactory, peerLookup, rpcEncoding)); } @@ -401,6 +417,48 @@ private static Eth2RpcMethod createGoodBye( spec.getNetworkingConfig())); } + private static Optional> + createDataColumnsSidecarsByRange( + final Spec spec, + final MetricsSystem metricsSystem, + final AsyncRunner asyncRunner, + final CombinedChainDataClient combinedChainDataClient, + final PeerLookup peerLookup, + final RpcEncoding rpcEncoding, + final RecentChainData recentChainData) { + + if (!spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + return Optional.empty(); + } + + final DataColumnSidecarsByRangeRequestMessage.DataColumnSidecarsByRangeRequestMessageSchema + requestType = + SchemaDefinitionsEip7594.required( + spec.forMilestone(SpecMilestone.EIP7594).getSchemaDefinitions()) + .getDataColumnSidecarsByRangeRequestMessageSchema(); + + final RpcContextCodec forkDigestContextCodec = + RpcContextCodec.forkDigest( + spec, recentChainData, ForkDigestPayloadContext.DATA_COLUMN_SIDECAR); + + final DataColumnSidecarsByRangeMessageHandler dataColumnSidecarsByRangeMessageHandler = + new DataColumnSidecarsByRangeMessageHandler( + spec, getSpecConfigEip7594(spec), metricsSystem, combinedChainDataClient); + + return Optional.of( + new SingleProtocolEth2RpcMethod<>( + asyncRunner, + BeaconChainMethodIds.DATA_COLUMN_SIDECARS_BY_RANGE, + 1, + rpcEncoding, + requestType, + true, + forkDigestContextCodec, + dataColumnSidecarsByRangeMessageHandler, + peerLookup, + spec.getNetworkingConfig())); + } + private static Eth2RpcMethod createMetadata( final Spec spec, final AsyncRunner asyncRunner, @@ -488,6 +546,10 @@ private static SpecConfigDeneb getSpecConfigDeneb(final Spec spec) { return SpecConfigDeneb.required(spec.forMilestone(SpecMilestone.DENEB).getConfig()); } + private static SpecConfigEip7594 getSpecConfigEip7594(final Spec spec) { + return SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); + } + public Collection> all() { return Collections.unmodifiableCollection(allMethods); } @@ -518,6 +580,11 @@ public Eth2RpcMethod beaco return dataColumnSidecarsByRoot; } + public Optional> + getDataColumnSidecarsByRange() { + return dataColumnSidecarsByRange; + } + public Optional> blobSidecarsByRange() { return blobSidecarsByRange; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeListenerValidatingProxy.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeListenerValidatingProxy.java new file mode 100644 index 00000000000..f1ed50b4719 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeListenerValidatingProxy.java @@ -0,0 +1,78 @@ +/* + * Copyright Consensys Software Inc., 2022 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.DataColumnSidecarsResponseInvalidResponseException.InvalidResponseType.DATA_COLUMN_SIDECAR_SLOT_NOT_IN_RANGE; + +import java.util.List; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.kzg.KZG; +import tech.pegasys.teku.networking.p2p.peer.Peer; +import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; + +public class DataColumnSidecarsByRangeListenerValidatingProxy + implements RpcResponseListener { + + protected final Peer peer; + protected final Spec spec; + protected final KZG kzg; + + private final RpcResponseListener dataColumnSidecarResponseListener; + + private final UInt64 startSlot; + private final UInt64 endSlot; + + @SuppressWarnings("UnusedVariable") + private final List columns; + + public DataColumnSidecarsByRangeListenerValidatingProxy( + final Spec spec, + final Peer peer, + final RpcResponseListener dataColumnSidecarResponseListener, + final KZG kzg, + final UInt64 startSlot, + final UInt64 count, + final List columns) { + this.peer = peer; + this.spec = spec; + this.kzg = kzg; + this.dataColumnSidecarResponseListener = dataColumnSidecarResponseListener; + this.startSlot = startSlot; + this.endSlot = startSlot.plus(count).minusMinZero(1); + this.columns = columns; + } + + @Override + public SafeFuture onResponse(final DataColumnSidecar dataColumnSidecar) { + return SafeFuture.of( + () -> { + final UInt64 dataColumnSidecarSlot = dataColumnSidecar.getSlot(); + if (!dataColumnSidecarSlotIsInRange(dataColumnSidecarSlot)) { + throw new DataColumnSidecarsResponseInvalidResponseException( + peer, DATA_COLUMN_SIDECAR_SLOT_NOT_IN_RANGE); + } + // TODO all checks + + return dataColumnSidecarResponseListener.onResponse(dataColumnSidecar); + }); + } + + private boolean dataColumnSidecarSlotIsInRange(final UInt64 dataColumnSidecarSlot) { + return dataColumnSidecarSlot.isGreaterThanOrEqualTo(startSlot) + && dataColumnSidecarSlot.isLessThanOrEqualTo(endSlot); + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java new file mode 100644 index 00000000000..10d2381fb09 --- /dev/null +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java @@ -0,0 +1,339 @@ +/* + * Copyright Consensys Software Inc., 2023 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods; + +import static tech.pegasys.teku.networking.eth2.rpc.core.RpcResponseStatus.INVALID_REQUEST_CODE; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSortedMap; +import java.nio.channels.ClosedChannelException; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.plugin.services.MetricsSystem; +import org.hyperledger.besu.plugin.services.metrics.Counter; +import org.hyperledger.besu.plugin.services.metrics.LabelledMetric; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.metrics.TekuMetricCategory; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.networking.eth2.peers.Eth2Peer; +import tech.pegasys.teku.networking.eth2.peers.RequestApproval; +import tech.pegasys.teku.networking.eth2.rpc.core.PeerRequiredLocalMessageHandler; +import tech.pegasys.teku.networking.eth2.rpc.core.ResponseCallback; +import tech.pegasys.teku.networking.eth2.rpc.core.RpcException; +import tech.pegasys.teku.networking.eth2.rpc.core.RpcException.ResourceUnavailableException; +import tech.pegasys.teku.networking.p2p.rpc.StreamClosedException; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRangeRequestMessage; +import tech.pegasys.teku.spec.datastructures.util.ColumnSlotAndIdentifier; +import tech.pegasys.teku.storage.client.CombinedChainDataClient; + +/** + * DataColumnSidecarsByRange + * v1 + */ +public class DataColumnSidecarsByRangeMessageHandler + extends PeerRequiredLocalMessageHandler< + DataColumnSidecarsByRangeRequestMessage, DataColumnSidecar> { + + private static final Logger LOG = LogManager.getLogger(); + + private final Spec spec; + private final SpecConfigEip7594 specConfigEip7594; + private final CombinedChainDataClient combinedChainDataClient; + private final LabelledMetric requestCounter; + private final Counter totalDataColumnSidecarsRequestedCounter; + + public DataColumnSidecarsByRangeMessageHandler( + final Spec spec, + final SpecConfigEip7594 specConfigEip7594, + final MetricsSystem metricsSystem, + final CombinedChainDataClient combinedChainDataClient) { + this.spec = spec; + this.specConfigEip7594 = specConfigEip7594; + this.combinedChainDataClient = combinedChainDataClient; + requestCounter = + metricsSystem.createLabelledCounter( + TekuMetricCategory.NETWORK, + "rpc_data_column_sidecars_by_range_requests_total", + "Total number of data column sidecars by range requests received", + "status"); + totalDataColumnSidecarsRequestedCounter = + metricsSystem.createCounter( + TekuMetricCategory.NETWORK, + "rpc_data_column_sidecars_by_range_requested_sidecars_total", + "Total number of data column sidecars requested in accepted blob sidecars by range requests from peers"); + } + + @Override + public void onIncomingMessage( + final String protocolId, + final Eth2Peer peer, + final DataColumnSidecarsByRangeRequestMessage message, + final ResponseCallback callback) { + final UInt64 startSlot = message.getStartSlot(); + final UInt64 endSlot = message.getMaxSlot(); + final List columns = message.getColumns(); + + LOG.trace( + "Peer {} requested {} slots with columns {} of data column sidecars starting at slot {}.", + peer.getId(), + message.getCount(), + columns, + startSlot); + + final int requestedCount = message.getMaximumResponseChunks(); + + if (requestedCount > specConfigEip7594.getMaxRequestDataColumnSidecars()) { + requestCounter.labels("count_too_big").inc(); + callback.completeWithErrorResponse( + new RpcException( + INVALID_REQUEST_CODE, + String.format( + "Only a maximum of %s blob sidecars can be requested per request. Requested: %s", + specConfigEip7594.getMaxRequestDataColumnSidecars(), requestedCount))); + return; + } + + final Optional dataColumnSidecarsRequestApproval = + peer.approveDataColumnSidecarsRequest(callback, requestedCount); + + if (!peer.approveRequest() || dataColumnSidecarsRequestApproval.isEmpty()) { + requestCounter.labels("rate_limited").inc(); + return; + } + + requestCounter.labels("ok").inc(); + totalDataColumnSidecarsRequestedCounter.inc(message.getCount().longValue()); + final SafeFuture> earliestDataColumnSidecarSlotFuture = + combinedChainDataClient.getEarliestDataColumnSidecarSlot(); + final SafeFuture> firstIncompleteSlotFuture = + combinedChainDataClient.getFirstIncompleteSlot(); + SafeFuture.collectAll(earliestDataColumnSidecarSlotFuture, firstIncompleteSlotFuture) + .thenCompose( + slotOptionals -> { + final Optional earliestSidecarSlot = slotOptionals.get(0); + final Optional firstIncompleteSlot = slotOptionals.get(1); + final UInt64 requestEpoch = spec.computeEpochAtSlot(startSlot); + if (spec.isAvailabilityOfDataColumnSidecarsRequiredAtEpoch( + combinedChainDataClient.getStore(), requestEpoch) + && !checkDataColumnSidecarsAreAvailable( + earliestSidecarSlot, firstIncompleteSlot, endSlot)) { + return SafeFuture.failedFuture( + new ResourceUnavailableException( + "Requested data column sidecars are not available.")); + } + + UInt64 finalizedSlot = + combinedChainDataClient.getFinalizedBlockSlot().orElse(UInt64.ZERO); + + final SortedMap canonicalHotRoots; + if (endSlot.isGreaterThan(finalizedSlot)) { + final UInt64 hotSlotsCount = endSlot.increment().minusMinZero(startSlot); + + canonicalHotRoots = + combinedChainDataClient.getAncestorRoots(startSlot, UInt64.ONE, hotSlotsCount); + + // refresh finalized slot to avoid race condition that can occur if we finalize just + // before getting hot canonical roots + finalizedSlot = combinedChainDataClient.getFinalizedBlockSlot().orElse(UInt64.ZERO); + } else { + canonicalHotRoots = ImmutableSortedMap.of(); + } + + final RequestState initialState = + new RequestState( + callback, + specConfigEip7594.getMaxRequestDataColumnSidecars(), + startSlot, + endSlot, + columns, + canonicalHotRoots, + finalizedSlot); + if (initialState.isComplete()) { + return SafeFuture.completedFuture(initialState); + } + return sendDataColumnSidecars(initialState); + }) + .finish( + requestState -> { + final int sentDataColumnSidecars = requestState.sentDataColumnSidecars.get(); + if (sentDataColumnSidecars != requestedCount) { + peer.adjustDataColumnSidecarsRequest( + dataColumnSidecarsRequestApproval.get(), sentDataColumnSidecars); + } + LOG.trace( + "Sent {} data column sidecars to peer {}.", sentDataColumnSidecars, peer.getId()); + callback.completeSuccessfully(); + }, + error -> { + peer.adjustDataColumnSidecarsRequest(dataColumnSidecarsRequestApproval.get(), 0); + handleProcessingRequestError(error, callback); + }); + ; + } + + private boolean checkDataColumnSidecarsAreAvailable( + final Optional earliestAvailableSidecarSlotOptional, + final Optional firstIncompleteSlotOptional, + final UInt64 requestSlot) { + if (earliestAvailableSidecarSlotOptional.isPresent()) { + if (earliestAvailableSidecarSlotOptional.get().isLessThanOrEqualTo(requestSlot)) { + return true; + } + return firstIncompleteSlotOptional + .map(firstIncompleteSlot -> firstIncompleteSlot.isGreaterThan(requestSlot)) + .orElse(false); + } else { + return false; + } + } + + private SafeFuture sendDataColumnSidecars(final RequestState requestState) { + return requestState + .loadNextDataColumnSidecar() + .thenCompose( + maybeDataColumnSidecar -> + maybeDataColumnSidecar + .map(requestState::sendDataColumnSidecar) + .orElse(SafeFuture.COMPLETE)) + .thenCompose( + __ -> { + if (requestState.isComplete()) { + return SafeFuture.completedFuture(requestState); + } else { + return sendDataColumnSidecars(requestState); + } + }); + } + + private void handleProcessingRequestError( + final Throwable error, final ResponseCallback callback) { + final Throwable rootCause = Throwables.getRootCause(error); + if (rootCause instanceof RpcException) { + LOG.trace("Rejecting data column sidecars by range request", error); + callback.completeWithErrorResponse((RpcException) rootCause); + } else { + if (rootCause instanceof StreamClosedException + || rootCause instanceof ClosedChannelException) { + LOG.trace("Stream closed while sending requested data column sidecars", error); + } else { + LOG.error("Failed to process data column sidecars request", error); + } + callback.completeWithUnexpectedError(error); + } + } + + @VisibleForTesting + class RequestState { + + private final AtomicInteger sentDataColumnSidecars = new AtomicInteger(0); + private final ResponseCallback callback; + private final UInt64 maxRequestDataColumnSidecars; + private final UInt64 startSlot; + private final UInt64 endSlot; + private final List columns; + private final UInt64 finalizedSlot; + private final Map canonicalHotRoots; + + // since our storage stores hot and finalized data columns sidecar on the same "table", this + // iterator can span + // over hot and finalized data column sidecar + private Optional> dataColumnSidecarKeysIterator = + Optional.empty(); + + RequestState( + final ResponseCallback callback, + final int maxRequestDataColumnSidecars, + final UInt64 startSlot, + final UInt64 endSlot, + final List columns, + final Map canonicalHotRoots, + final UInt64 finalizedSlot) { + this.callback = callback; + this.maxRequestDataColumnSidecars = UInt64.valueOf(maxRequestDataColumnSidecars); + this.startSlot = startSlot; + this.endSlot = endSlot; + this.columns = columns; + this.finalizedSlot = finalizedSlot; + this.canonicalHotRoots = canonicalHotRoots; + } + + SafeFuture sendDataColumnSidecar(final DataColumnSidecar dataColumnSidecar) { + return callback.respond(dataColumnSidecar).thenRun(sentDataColumnSidecars::incrementAndGet); + } + + SafeFuture> loadNextDataColumnSidecar() { + if (dataColumnSidecarKeysIterator.isEmpty()) { + return combinedChainDataClient + .getDataColumnIdentifiers(startSlot, endSlot, maxRequestDataColumnSidecars) + .thenCompose( + keys -> { + dataColumnSidecarKeysIterator = Optional.of(keys.iterator()); + return getNextDataColumnSidecar(dataColumnSidecarKeysIterator.get()); + }); + } else { + return getNextDataColumnSidecar(dataColumnSidecarKeysIterator.get()); + } + } + + private SafeFuture> getNextDataColumnSidecar( + final Iterator dataColumnSidecarIdentifiers) { + if (dataColumnSidecarIdentifiers.hasNext()) { + final ColumnSlotAndIdentifier columnSlotAndIdentifier = dataColumnSidecarIdentifiers.next(); + + // Column that was not requested. TODO: get identifiers only for requested columns from DB + if (!columns.contains(columnSlotAndIdentifier.identifier().getIndex())) { + return getNextDataColumnSidecar(dataColumnSidecarIdentifiers); + } + + if (finalizedSlot.isGreaterThanOrEqualTo(columnSlotAndIdentifier.slot())) { + return combinedChainDataClient.getSidecar(columnSlotAndIdentifier); + } + + // not finalized, let's check if it is on canonical chain + if (isCanonicalHotDataColumnSidecar(columnSlotAndIdentifier)) { + return combinedChainDataClient.getSidecar(columnSlotAndIdentifier); + } + + // non-canonical, try next one + return getNextDataColumnSidecar(dataColumnSidecarIdentifiers); + } + + return SafeFuture.completedFuture(Optional.empty()); + } + + private boolean isCanonicalHotDataColumnSidecar( + final ColumnSlotAndIdentifier columnSlotAndIdentifier) { + return Optional.ofNullable(canonicalHotRoots.get(columnSlotAndIdentifier.slot())) + .map(blockRoot -> blockRoot.equals(columnSlotAndIdentifier.identifier().getBlockRoot())) + .orElse(false); + } + + boolean isComplete() { + return endSlot.isLessThan(startSlot) + || dataColumnSidecarKeysIterator.map(iterator -> !iterator.hasNext()).orElse(false); + } + } +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java index 863fa0803c5..bc77b7d1980 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsResponseInvalidResponseException.java @@ -36,6 +36,7 @@ public DataColumnSidecarsResponseInvalidResponseException( public enum InvalidResponseType { DATA_COLUMN_SIDECAR_KZG_VERIFICATION_FAILED( "KZG verification for DataColumnSidecar has failed"), + DATA_COLUMN_SIDECAR_SLOT_NOT_IN_RANGE("DataColumnSidecar's slot is not within requested range"), DATA_COLUMN_SIDECAR_UNEXPECTED_IDENTIFIER( "DataColumnSidecar is not within requested identifiers"); diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java index 1ac5fb5b615..17e459d782a 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/RespondingEth2Peer.java @@ -268,6 +268,16 @@ public SafeFuture requestDataColumnSidecarsByRoot( return SafeFuture.COMPLETE; } + @Override + public SafeFuture requestDataColumnSidecarsByRange( + final UInt64 startSlot, + final UInt64 count, + final List columns, + final RpcResponseListener listener) { + // TODO + return SafeFuture.COMPLETE; + } + @Override public SafeFuture> requestBlockBySlot(final UInt64 slot) { final PendingRequestHandler, SignedBeaconBlock> handler = diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubSyncSource.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubSyncSource.java index 9442fba9f23..21d028cde7c 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubSyncSource.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/peers/StubSyncSource.java @@ -27,6 +27,7 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationAdjustment; import tech.pegasys.teku.networking.p2p.rpc.RpcResponseListener; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; public class StubSyncSource implements SyncSource { @@ -81,6 +82,16 @@ public SafeFuture requestBlobSidecarsByRange( return request; } + @Override + public SafeFuture requestDataColumnSidecarsByRange( + final UInt64 startSlot, + final UInt64 count, + final List columns, + final RpcResponseListener listener) { + // TODO + return SafeFuture.COMPLETE; + } + @Override public SafeFuture disconnectCleanly(final DisconnectReason reason) { return SafeFuture.COMPLETE; diff --git a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java index 31366cd0d9a..e8e33380c67 100644 --- a/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java +++ b/storage/api/src/main/java/tech/pegasys/teku/storage/api/StorageQueryChannel.java @@ -115,4 +115,9 @@ SafeFuture> getBlobSidecarKeys( SafeFuture> getSidecar(ColumnSlotAndIdentifier identifier); SafeFuture> getDataColumnIdentifiers(UInt64 slot); + + SafeFuture> getDataColumnIdentifiers( + UInt64 startSlot, UInt64 endSlot, UInt64 limit); + + SafeFuture> getEarliestDataColumnSidecarSlot(); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java index 64de7c3424c..0713722d884 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/client/CombinedChainDataClient.java @@ -857,4 +857,13 @@ public SafeFuture> getSidecar( public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { return historicalChainData.getDataColumnIdentifiers(slot); } + + public SafeFuture> getDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot, final UInt64 limit) { + return historicalChainData.getDataColumnIdentifiers(startSlot, endSlot, limit); + } + + public SafeFuture> getEarliestDataColumnSidecarSlot() { + return historicalChainData.getEarliestDataColumnSidecarSlot(); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java index 048a715c248..5686393e5e9 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/ChainStorage.java @@ -389,6 +389,23 @@ public SafeFuture> getDataColumnIdentifiers(final }); } + @Override + public SafeFuture> getDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot, final UInt64 limit) { + return SafeFuture.of( + () -> { + try (final Stream dataColumnIdentifiersStream = + database.streamDataColumnIdentifiers(startSlot, endSlot).limit(limit.longValue())) { + return dataColumnIdentifiersStream.toList(); + } + }); + } + + @Override + public SafeFuture> getEarliestDataColumnSidecarSlot() { + return SafeFuture.of(database::getEarliestDataColumnSidecarSlot); + } + @Override public void onFirstIncompleteSlot(final UInt64 slot) { database.setFirstIncompleteSlot(slot); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java index 8df2ca54c6d..a503df3b08b 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/CombinedStorageChannelSplitter.java @@ -262,4 +262,16 @@ public SafeFuture> getSidecar( public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { return asyncRunner.runAsync(() -> queryDelegate.getDataColumnIdentifiers(slot)); } + + @Override + public SafeFuture> getDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot, final UInt64 limit) { + return asyncRunner.runAsync( + () -> queryDelegate.getDataColumnIdentifiers(startSlot, endSlot, limit)); + } + + @Override + public SafeFuture> getEarliestDataColumnSidecarSlot() { + return asyncRunner.runAsync(queryDelegate::getEarliestDataColumnSidecarSlot); + } } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java index 0e791fa864e..9159fc20231 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/Database.java @@ -252,6 +252,8 @@ default Stream streamDataColumnIdentifiers(final UInt64 return streamDataColumnIdentifiers(slot, slot); } + Optional getEarliestDataColumnSidecarSlot(); + void setFirstIncompleteSlot(UInt64 slot); void addSidecar(DataColumnSidecar sidecar); diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java index ecb5075b6d3..03ba28df30a 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/KvStoreDatabase.java @@ -942,6 +942,11 @@ public Stream streamDataColumnIdentifiers( return dao.streamDataColumnIdentifiers(firstSlot, lastSlot); } + @Override + public Optional getEarliestDataColumnSidecarSlot() { + return dao.getEarliestDataSidecarColumnSlot(); + } + @Override public void setFirstIncompleteSlot(final UInt64 slot) { try (final FinalizedUpdater updater = finalizedUpdater()) { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java index 05cbc65d461..e247869f699 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/CombinedKvStoreDao.java @@ -550,6 +550,13 @@ public List getDataColumnIdentifiers(SlotAndBlockRoot s } } + @Override + public Optional getEarliestDataSidecarColumnSlot() { + return db.getFirstEntry(schema.getColumnSidecarByColumnSlotAndIdentifier()) + .map(ColumnEntry::getKey) + .map(ColumnSlotAndIdentifier::slot); + } + static class V4CombinedUpdater implements CombinedUpdater { private final KvStoreTransaction transaction; diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java index ebd3cbe8384..ce7ea306553 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDao.java @@ -172,6 +172,8 @@ List getNonCanonicalBlobSidecarKeys( List getDataColumnIdentifiers(SlotAndBlockRoot slotAndBlockRoot); + Optional getEarliestDataSidecarColumnSlot(); + interface CombinedUpdater extends HotUpdater, FinalizedUpdater {} interface HotUpdater extends AutoCloseable { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java index e35e75eb65e..359c4438562 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/KvStoreCombinedDaoAdapter.java @@ -328,6 +328,11 @@ public List getDataColumnIdentifiers( return finalizedDao.getDataColumnIdentifiers(slotAndBlockRoot); } + @Override + public Optional getEarliestDataSidecarColumnSlot() { + return finalizedDao.getEarliestAvailableDataColumnSlot(); + } + @Override public void ingest( final KvStoreCombinedDao dao, final int batchSize, final Consumer logger) { diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java index 83deb98584b..7d1d9f9c1ed 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/kvstore/dataaccess/V4FinalizedKvStoreDao.java @@ -220,6 +220,12 @@ public List getDataColumnIdentifiers( } } + public Optional getEarliestAvailableDataColumnSlot() { + return db.getFirstEntry(schema.getColumnSidecarByColumnSlotAndIdentifier()) + .map(ColumnEntry::getKey) + .map(ColumnSlotAndIdentifier::slot); + } + public Optional getRawVariable(final KvStoreVariable var) { return db.getRaw(var); } diff --git a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java index 569bdcb0f18..d9ad0760f50 100644 --- a/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java +++ b/storage/src/main/java/tech/pegasys/teku/storage/server/noop/NoOpDatabase.java @@ -346,6 +346,11 @@ public Stream streamDataColumnIdentifiers( return Stream.empty(); } + @Override + public Optional getEarliestDataColumnSidecarSlot() { + return Optional.empty(); + } + @Override public void setFirstIncompleteSlot(UInt64 slot) {} diff --git a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java index 02d07f5562a..23a6b644c49 100644 --- a/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java +++ b/storage/src/testFixtures/java/tech/pegasys/teku/storage/api/StubStorageQueryChannel.java @@ -194,4 +194,15 @@ public SafeFuture> getSidecar( public SafeFuture> getDataColumnIdentifiers(final UInt64 slot) { return SafeFuture.completedFuture(Collections.emptyList()); } + + @Override + public SafeFuture> getDataColumnIdentifiers( + final UInt64 startSlot, final UInt64 endSlot, final UInt64 limit) { + return SafeFuture.completedFuture(Collections.emptyList()); + } + + @Override + public SafeFuture> getEarliestDataColumnSidecarSlot() { + return SafeFuture.completedFuture(Optional.empty()); + } } From adfb08949904602727ec1e306f37d709bd75f692 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 14 May 2024 15:23:16 +0400 Subject: [PATCH 55/70] Read numberOfColumns from config instead of calculating (#46) * NUMBER_OF_COLUMNS is now in config, better to read it * removed hardcode from fieldElementsPerCell as there is no more division by it --- .../src/main/java/tech/pegasys/teku/spec/Spec.java | 2 +- .../pegasys/teku/spec/config/SpecConfigEip7594.java | 4 +--- .../teku/spec/config/SpecConfigEip7594Impl.java | 10 ++++++++++ .../teku/spec/config/builder/Eip7594Builder.java | 11 +++++++++-- .../versions/eip7594/helpers/MiscHelpersEip7594.java | 8 +++----- .../pegasys/teku/spec/config/configs/mainnet.yaml | 1 + .../pegasys/teku/spec/config/configs/minimal.yaml | 1 + .../tech/pegasys/teku/spec/config/configs/swift.yaml | 1 + .../teku/spec/config/SpecConfigEip7594Test.java | 1 + .../teku/networking/eth2/peers/Eth2PeerFactory.java | 5 +---- 10 files changed, 29 insertions(+), 15 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java index cb50bc64396..fd5066ddeb2 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/Spec.java @@ -956,7 +956,7 @@ public UInt64 computeSubnetForBlobSidecar(final BlobSidecar blobSidecar) { return blobSidecar.getIndex().mod(specConfigDeneb.getBlobSidecarSubnetCount()); } - public Optional getNumberOfDataColumns() { + public Optional getNumberOfDataColumns() { return getSpecConfigEip7594().map(SpecConfigEip7594::getNumberOfColumns); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java index d03e7de6f8e..8556136b425 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594.java @@ -40,9 +40,7 @@ static SpecConfigEip7594 required(final SpecConfig specConfig) { /** DataColumnSidecar's */ UInt64 getKzgCommitmentsInclusionProofDepth(); - default UInt64 getNumberOfColumns() { - return getFieldElementsPerExtBlob().dividedBy(getFieldElementsPerCell()); - } + int getNumberOfColumns(); @Override Optional toVersionEip7594(); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java index be40556553a..62d08b2bb9f 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Impl.java @@ -23,6 +23,7 @@ public class SpecConfigEip7594Impl extends DelegatingSpecConfigDeneb implements private final Bytes4 eip7594ForkVersion; private final UInt64 eip7594ForkEpoch; + private final int numberOfColumns; private final int dataColumnSidecarSubnetCount; private final int custodyRequirement; private final UInt64 fieldElementsPerCell; @@ -38,6 +39,7 @@ public SpecConfigEip7594Impl( final UInt64 fieldElementsPerCell, final UInt64 fieldElementsPerExtBlob, final UInt64 kzgCommitmentsInclusionProofDepth, + final int numberOfColumns, final int dataColumnSidecarSubnetCount, final int custodyRequirement, final int minEpochsForDataColumnSidecarsRequests, @@ -48,6 +50,7 @@ public SpecConfigEip7594Impl( this.fieldElementsPerCell = fieldElementsPerCell; this.fieldElementsPerExtBlob = fieldElementsPerExtBlob; this.kzgCommitmentsInclusionProofDepth = kzgCommitmentsInclusionProofDepth; + this.numberOfColumns = numberOfColumns; this.dataColumnSidecarSubnetCount = dataColumnSidecarSubnetCount; this.custodyRequirement = custodyRequirement; this.minEpochsForDataColumnSidecarsRequests = minEpochsForDataColumnSidecarsRequests; @@ -79,6 +82,11 @@ public UInt64 getKzgCommitmentsInclusionProofDepth() { return kzgCommitmentsInclusionProofDepth; } + @Override + public int getNumberOfColumns() { + return numberOfColumns; + } + @Override public int getDataColumnSidecarSubnetCount() { return dataColumnSidecarSubnetCount; @@ -119,6 +127,7 @@ public boolean equals(final Object o) { && Objects.equals(fieldElementsPerCell, that.fieldElementsPerCell) && Objects.equals(fieldElementsPerExtBlob, that.fieldElementsPerExtBlob) && Objects.equals(kzgCommitmentsInclusionProofDepth, that.kzgCommitmentsInclusionProofDepth) + && numberOfColumns == that.numberOfColumns && dataColumnSidecarSubnetCount == that.dataColumnSidecarSubnetCount && custodyRequirement == that.custodyRequirement && minEpochsForDataColumnSidecarsRequests == that.minEpochsForDataColumnSidecarsRequests @@ -131,6 +140,7 @@ public int hashCode() { specConfig, eip7594ForkVersion, eip7594ForkEpoch, + numberOfColumns, dataColumnSidecarSubnetCount, custodyRequirement, fieldElementsPerCell, diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java index 30e9627f3c4..d61c148dbf9 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/config/builder/Eip7594Builder.java @@ -30,10 +30,10 @@ public class Eip7594Builder implements ForkConfigBuilder getValidationMap() { constants.put("eip7594ForkEpoch", eip7594ForkEpoch); constants.put("eip7594ForkVersion", eip7594ForkVersion); + constants.put("numberOfColumns", numberOfColumns); constants.put("dataColumnSidecarSubnetCount", dataColumnSidecarSubnetCount); constants.put("custodyRequirement", custodyRequirement); constants.put("fieldElementsPerCell", fieldElementsPerCell); diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java index 2370ffc8b71..f00ffbf4b10 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/versions/eip7594/helpers/MiscHelpersEip7594.java @@ -115,10 +115,8 @@ private UInt256 incrementByModule(UInt256 n) { public List computeCustodyColumnIndexes(final UInt256 nodeId, final int subnetCount) { List subnetIds = computeCustodySubnetIndexes(nodeId, subnetCount); final int columnsPerSubnet = - specConfigEip7594 - .getNumberOfColumns() - .dividedBy(specConfigEip7594.getDataColumnSidecarSubnetCount()) - .intValue(); + specConfigEip7594.getNumberOfColumns() + / specConfigEip7594.getDataColumnSidecarSubnetCount(); return subnetIds.stream() .flatMap( subnetId -> IntStream.range(0, columnsPerSubnet).mapToObj(i -> Pair.of(subnetId, i))) @@ -139,7 +137,7 @@ public List computeDataColumnSidecarBackboneSubnets( @Override public boolean verifyDataColumnSidecarKzgProof(KZG kzg, DataColumnSidecar dataColumnSidecar) { - final UInt64 dataColumns = specConfigEip7594.getNumberOfColumns(); + final int dataColumns = specConfigEip7594.getNumberOfColumns(); if (dataColumnSidecar.getIndex().isGreaterThanOrEqualTo(dataColumns)) { return false; } diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 84ed7d3b279..775ba5f2590 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -146,5 +146,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] +NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index caba7a8fd6f..0ee1bd3d051 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -147,5 +147,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] +NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml index bdcdd82b52b..6b7f7536da1 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml @@ -142,5 +142,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096 BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] +NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 diff --git a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java index c155d9b906a..a8e152baff6 100644 --- a/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java +++ b/ethereum/spec/src/test/java/tech/pegasys/teku/spec/config/SpecConfigEip7594Test.java @@ -85,6 +85,7 @@ private SpecConfigEip7594 createRandomEip7594Config( dataStructureUtil.randomUInt64(64), dataStructureUtil.randomUInt64(8192), dataStructureUtil.randomUInt64(10), + dataStructureUtil.randomPositiveInt(128), dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(64), dataStructureUtil.randomPositiveInt(4096), diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java index f9d507f06aa..b11b75b4a45 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java @@ -16,7 +16,6 @@ import java.util.Optional; import org.hyperledger.besu.plugin.services.MetricsSystem; import tech.pegasys.teku.infrastructure.time.TimeProvider; -import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.kzg.KZG; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.BeaconChainMethods; import tech.pegasys.teku.networking.eth2.rpc.beaconchain.methods.MetadataMessagesFactory; @@ -79,9 +78,7 @@ public Eth2Peer create(final Peer peer, final BeaconChainMethods rpcMethods) { RateTracker.create( peerRateLimit * spec.getMaxBlobsPerBlock().orElse(1), TIME_OUT, timeProvider), RateTracker.create( - peerRateLimit * spec.getNumberOfDataColumns().orElse(UInt64.ONE).intValue(), - TIME_OUT, - timeProvider), + peerRateLimit * spec.getNumberOfDataColumns().orElse(1), TIME_OUT, timeProvider), RateTracker.create(peerRequestLimit, TIME_OUT, timeProvider), kzg); } From 63ab9aa3f2db26197af1a795fd629f1a650d5fc7 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 14 May 2024 15:46:12 +0300 Subject: [PATCH 56/70] Reduce RPC column request limit (#47) * Reduce MAX_REQUEST_DATA_COLUMN_SIDECARS const to 512 * Fix propagation of call exception to returned future * Fix/refactor SimpleSidecarRetriever to respect maxRequestCount and the number of currently running requests per peer * Fix compile error from prev PR --- ...taColumnSidecarsByRangeRequestMessage.java | 3 +- .../teku/spec/config/configs/mainnet.yaml | 2 +- .../teku/spec/config/configs/minimal.yaml | 2 +- .../teku/spec/config/configs/swift.yaml | 2 +- .../DataColumnReqRespBatchingImpl.java | 6 +- .../retriever/SimpleSidecarRetriever.java | 69 ++++++++++++++++--- 6 files changed, 67 insertions(+), 17 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java index 85f7756042a..8085620b13a 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnSidecarsByRangeRequestMessage.java @@ -41,8 +41,7 @@ public DataColumnSidecarsByRangeRequestMessageSchema( namedSchema("start_slot", SszPrimitiveSchemas.UINT64_SCHEMA), namedSchema("count", SszPrimitiveSchemas.UINT64_SCHEMA), namedSchema( - "columns", - SszUInt64ListSchema.create(specConfigEip7594.getNumberOfColumns().intValue()))); + "columns", SszUInt64ListSchema.create(specConfigEip7594.getNumberOfColumns()))); } @Override diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml index 775ba5f2590..03fefc9d16f 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/mainnet.yaml @@ -148,4 +148,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 -MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 512 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml index 0ee1bd3d051..b39039af230 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/minimal.yaml @@ -149,4 +149,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 -MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 512 diff --git a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml index 6b7f7536da1..ec0be848e0f 100644 --- a/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml +++ b/ethereum/spec/src/main/resources/tech/pegasys/teku/spec/config/configs/swift.yaml @@ -144,4 +144,4 @@ BLOB_SIDECAR_SUBNET_COUNT: 6 # [New in EIP7594] NUMBER_OF_COLUMNS: 128 DATA_COLUMN_SIDECAR_SUBNET_COUNT: 32 -MAX_REQUEST_DATA_COLUMN_SIDECARS: 16384 +MAX_REQUEST_DATA_COLUMN_SIDECARS: 512 diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index 432da07c42b..38ab51f3291 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -63,8 +63,10 @@ public void flush() { private void flushForNode(UInt256 nodeId, List nodeRequests) { SafeFuture> response = - batchRpc.requestDataColumnSidecar( - nodeId, nodeRequests.stream().map(e -> e.columnIdentifier).toList()); + SafeFuture.of( + () -> + batchRpc.requestDataColumnSidecar( + nodeId, nodeRequests.stream().map(e -> e.columnIdentifier).toList())); response.finish( resp -> { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index b193267c59f..8ebbfb4a4d6 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -21,7 +21,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; @@ -29,7 +31,9 @@ import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.SpecVersion; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; import tech.pegasys.teku.statetransition.datacolumns.ColumnSlotAndIdentifier; @@ -47,6 +51,7 @@ public class SimpleSidecarRetriever private final DataColumnReqResp reqResp; private final AsyncRunner asyncRunner; private final Duration roundPeriod; + private final int maxRequestCount; public SimpleSidecarRetriever( Spec spec, @@ -64,6 +69,9 @@ public SimpleSidecarRetriever( this.roundPeriod = roundPeriod; this.reqResp = new ValidatingDataColumnReqResp(peerManager, reqResp, validator); peerManager.addPeerListener(this); + this.maxRequestCount = + SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()) + .getMaxRequestDataColumnSidecars(); } private final Map pendingRequests = @@ -100,26 +108,32 @@ public synchronized SafeFuture retrieve(ColumnSlotAndIdentifi private synchronized List matchRequestsAndPeers() { disposeCancelledRequests(); + RequestTracker ongoingRequestsTracker = createFromCurrentPendingRequests(); return pendingRequests.entrySet().stream() .filter(entry -> entry.getValue().activeRpcRequest == null) .flatMap( entry -> { RetrieveRequest request = entry.getValue(); - return findBestMatchingPeer(request).stream() + return findBestMatchingPeer(request, ongoingRequestsTracker).stream() + .peek(peer -> ongoingRequestsTracker.decreaseAvailableRequests(peer.nodeId)) .map(peer -> new RequestMatch(peer, request)); }) .toList(); } - private Optional findBestMatchingPeer(RetrieveRequest request) { - return findMatchingPeers(request).stream() - .max(Comparator.comparing(peer -> reqResp.getCurrentRequestLimit(peer.nodeId))); + private Optional findBestMatchingPeer( + RetrieveRequest request, RequestTracker ongoingRequestsTracker) { + return findMatchingPeers(request, ongoingRequestsTracker).stream() + .max( + Comparator.comparing( + peer -> ongoingRequestsTracker.getAvailableRequestCount(peer.nodeId))); } - private Collection findMatchingPeers(RetrieveRequest request) { + private Collection findMatchingPeers( + RetrieveRequest request, RequestTracker ongoingRequestsTracker) { return connectedPeers.values().stream() .filter(peer -> peer.isCustodyFor(request.columnId)) - .filter(peer -> reqResp.getCurrentRequestLimit(peer.nodeId) > 0) + .filter(peer -> ongoingRequestsTracker.hasAvailableRequests(peer.nodeId)) .toList(); } @@ -133,7 +147,7 @@ private void disposeCancelledRequests() { pendingIterator.remove(); pendingRequest.peerSearchRequest.dispose(); if (pendingRequest.activeRpcRequest != null) { - pendingRequest.activeRpcRequest.cancel(true); + pendingRequest.activeRpcRequest.promise().cancel(true); } } } @@ -145,8 +159,10 @@ void nextRound() { SafeFuture reqRespPromise = reqResp.requestDataColumnSidecar(match.peer.nodeId, match.request.columnId.identifier()); match.request.activeRpcRequest = - reqRespPromise.whenComplete( - (sidecar, err) -> reqRespCompleted(match.request, sidecar, err)); + new ActiveRequest( + reqRespPromise.whenComplete( + (sidecar, err) -> reqRespCompleted(match.request, sidecar, err)), + match.peer); } reqResp.flush(); @@ -176,11 +192,13 @@ public synchronized void peerDisconnected(UInt256 nodeId) { connectedPeers.remove(nodeId); } + private record ActiveRequest(SafeFuture promise, ConnectedPeer peer) {} + private static class RetrieveRequest { final ColumnSlotAndIdentifier columnId; final DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest; final SafeFuture result = new SafeFuture<>(); - volatile SafeFuture activeRpcRequest = null; + volatile ActiveRequest activeRpcRequest = null; private RetrieveRequest( ColumnSlotAndIdentifier columnId, @@ -214,4 +232,35 @@ public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { } private record RequestMatch(ConnectedPeer peer, RetrieveRequest request) {} + + private RequestTracker createFromCurrentPendingRequests() { + Map pendingRequestsCount = + pendingRequests.values().stream() + .map(r -> r.activeRpcRequest) + .filter(Objects::nonNull) + .map(r -> r.peer().nodeId) + .collect(Collectors.groupingBy(r -> r, Collectors.reducing(0, e -> 1, Integer::sum))); + return new RequestTracker(pendingRequestsCount); + } + + private class RequestTracker { + private final Map pendingRequestsCount; + + private RequestTracker(Map pendingRequestsCount) { + this.pendingRequestsCount = pendingRequestsCount; + } + + int getAvailableRequestCount(UInt256 nodeId) { + return Integer.min(maxRequestCount, reqResp.getCurrentRequestLimit(nodeId)) + - pendingRequestsCount.getOrDefault(nodeId, 0); + } + + boolean hasAvailableRequests(UInt256 nodeId) { + return getAvailableRequestCount(nodeId) > 0; + } + + void decreaseAvailableRequests(UInt256 nodeId) { + pendingRequestsCount.compute(nodeId, (__, cnt) -> cnt == null ? 1 : cnt + 1); + } + } } From 48edab4e8d7268a11bf35cac668fdc13db73a6e3 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 14 May 2024 17:05:36 +0300 Subject: [PATCH 57/70] Add some logs --- .../datacolumns/DasCustodySync.java | 2 +- .../datacolumns/DataColumnSidecarDBImpl.java | 15 ++++++++++++ .../DataColumnReqRespBatchingImpl.java | 24 ++++++++++++++++++- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index 86fe4c31a26..355abe904f8 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -113,7 +113,7 @@ private synchronized void fillUp() { missingColumnsToRequest.stream() .map(ColumnSlotAndIdentifier::slot) .collect(Collectors.toSet()); - LOG.debug( + LOG.info( "DataCustodySync.fillUp: synced={} pending={}, missingColumns={}({})", syncedColumnCount, pendingRequests.size(), diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index ee132e61b3d..60b76ddf104 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -15,6 +15,8 @@ import java.util.Optional; import java.util.stream.Stream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; @@ -24,6 +26,8 @@ // FIXME: remove stinky joins public class DataColumnSidecarDBImpl implements DataColumnSidecarDB { + private static final Logger LOG = LogManager.getLogger("das-nyota"); + private final CombinedChainDataClient combinedChainDataClient; private final SidecarUpdateChannel sidecarUpdateChannel; @@ -52,7 +56,18 @@ public Stream streamColumnIdentifiers(final UInt64 slot) { @Override public void setFirstIncompleteSlot(final UInt64 slot) { + Optional oldValue = getFirstIncompleteSlot(); sidecarUpdateChannel.onFirstIncompleteSlot(slot); + if (oldValue.isEmpty() || oldValue.get() != slot) { + long oldSlotColCount = oldValue.map(s -> streamColumnIdentifiers(s).count()).orElse(0L); + long newSlotCount = streamColumnIdentifiers(slot).count(); + LOG.info( + "DataColumnSidecarDB: setFirstIncompleteSlot {} ({} cols) ~> {} ({} cols)", + oldValue.map(UInt64::toString).orElse("NA"), + oldSlotColCount, + slot, + newSlotCount); + } } @Override diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index 38ab51f3291..a616674bb5d 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -17,12 +17,15 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.infrastructure.async.SafeFuture; import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public class DataColumnReqRespBatchingImpl implements DataColumnReqResp { + private static final Logger LOG = LogManager.getLogger(); private final BatchDataColumnReqResp batchRpc; @@ -62,6 +65,11 @@ public void flush() { } private void flushForNode(UInt256 nodeId, List nodeRequests) { + LOG.info( + "Requesting batch of {} from {}, hash={}", + nodeRequests.size(), + nodeId.mod(65536).toHexString(), + nodeRequests.hashCode()); SafeFuture> response = SafeFuture.of( () -> @@ -70,6 +78,11 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { response.finish( resp -> { + LOG.info( + "Response batch of {} from {}, hash={}", + resp.size(), + nodeId.mod(65536).toHexString(), + nodeRequests.hashCode()); Map byIds = new HashMap<>(); for (DataColumnSidecar sidecar : resp) { byIds.put( @@ -84,7 +97,16 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { } } }, - err -> nodeRequests.forEach(e -> e.promise().completeExceptionally(err))); + err -> + nodeRequests.forEach( + e -> { + LOG.info( + "Error batch from {}, hash={}, err: {}", + nodeId.mod(65536).toHexString(), + nodeRequests.hashCode(), + e.toString()); + e.promise().completeExceptionally(err); + })); } @Override From fc23e9fe2552d9bbec1b2b047b59db0eb92bfe6c Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Tue, 14 May 2024 18:09:45 +0400 Subject: [PATCH 58/70] Some patch for Kurtosis (#48) --- .../teku/ethereum/json/types/EthereumTypes.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/EthereumTypes.java b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/EthereumTypes.java index 7fac43c10aa..f2ff54461c9 100644 --- a/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/EthereumTypes.java +++ b/ethereum/json-types/src/main/java/tech/pegasys/teku/ethereum/json/types/EthereumTypes.java @@ -94,7 +94,15 @@ public class EthereumTypes { public static final StringValueTypeDefinition MILESTONE_TYPE = new EnumTypeDefinition<>( - SpecMilestone.class, milestone -> milestone.name().toLowerCase(Locale.ROOT), Set.of()); + SpecMilestone.class, + milestone -> { + // FIXME: remove me, bad hack to make Kurtosis working + if (milestone.equals(SpecMilestone.EIP7594)) { + return "deneb"; + } + return milestone.name().toLowerCase(Locale.ROOT); + }, + Set.of()); public static > ResponseContentTypeDefinition sszResponseType() { From 1aca86c21cc9c2d4cf0c6a83051d2eff0647485d Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 14 May 2024 17:16:53 +0300 Subject: [PATCH 59/70] Fix compiler warning --- .../statetransition/datacolumns/DataColumnSidecarDBImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index 60b76ddf104..3af6430ce19 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -58,7 +58,7 @@ public Stream streamColumnIdentifiers(final UInt64 slot) { public void setFirstIncompleteSlot(final UInt64 slot) { Optional oldValue = getFirstIncompleteSlot(); sidecarUpdateChannel.onFirstIncompleteSlot(slot); - if (oldValue.isEmpty() || oldValue.get() != slot) { + if (oldValue.isEmpty() || !oldValue.get().equals(slot)) { long oldSlotColCount = oldValue.map(s -> streamColumnIdentifiers(s).count()).orElse(0L); long newSlotCount = streamColumnIdentifiers(slot).count(); LOG.info( From f9d21fb57aa8ec5eb4d34906befad719aab9d7f6 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Tue, 14 May 2024 19:27:42 +0300 Subject: [PATCH 60/70] More intensive DAS logging --- .../datacolumns/DasCustodySync.java | 2 +- .../datacolumns/DataColumnSidecarCustodyImpl.java | 2 +- .../datacolumns/DataColumnSidecarDBImpl.java | 15 +++++++++++++++ .../retriever/DataColumnReqRespBatchingImpl.java | 2 +- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index 355abe904f8..72995a0219a 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -29,7 +29,7 @@ import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnSidecarRetriever; public class DasCustodySync implements SlotEventsChannel { - private static final Logger LOG = LogManager.getLogger(); + private static final Logger LOG = LogManager.getLogger("das-nyota"); private final UpdatableDataColumnSidecarCustody custody; private final DataColumnSidecarRetriever retriever; diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 1e68f95e1e2..a493f422d24 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -163,7 +163,6 @@ public SafeFuture> getCustodyDataColumnSidecar( private void onEpoch(UInt64 epoch) { UInt64 pruneSlot = spec.computeStartSlotAtEpoch(getEarliestCustodyEpoch(epoch)); db.pruneAllSidecars(pruneSlot); - advanceFirstIncompleteSlot(); } @Override @@ -173,6 +172,7 @@ public void onSlot(UInt64 slot) { if (slot.equals(spec.computeStartSlotAtEpoch(epoch))) { onEpoch(epoch); } + advanceFirstIncompleteSlot(); } private void advanceFirstIncompleteSlot() { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index 3af6430ce19..15f9b0679f2 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -14,6 +14,7 @@ package tech.pegasys.teku.statetransition.datacolumns; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -30,6 +31,8 @@ public class DataColumnSidecarDBImpl implements DataColumnSidecarDB { private final CombinedChainDataClient combinedChainDataClient; private final SidecarUpdateChannel sidecarUpdateChannel; + private final AtomicInteger addCounter = new AtomicInteger(); + private int maxAddedSlot = 0; public DataColumnSidecarDBImpl( final CombinedChainDataClient combinedChainDataClient, @@ -73,6 +76,18 @@ public void setFirstIncompleteSlot(final UInt64 slot) { @Override public void addSidecar(final DataColumnSidecar sidecar) { sidecarUpdateChannel.onNewSidecar(sidecar); + addCounter.incrementAndGet(); + int slot = sidecar.getSlot().intValue(); + synchronized (this) { + if (slot > maxAddedSlot) { + LOG.info( + "DataColumnSidecarDB.addSidecar: new slot: {}, prevSlot count: {}, total count: {}", + slot, + streamColumnIdentifiers(UInt64.valueOf(maxAddedSlot)).count(), + addCounter.get()); + maxAddedSlot = slot; + } + } } @Override diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index a616674bb5d..c7d220541f5 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -25,7 +25,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; public class DataColumnReqRespBatchingImpl implements DataColumnReqResp { - private static final Logger LOG = LogManager.getLogger(); + private static final Logger LOG = LogManager.getLogger("das-nyota"); private final BatchDataColumnReqResp batchRpc; From a2731abca27e686f234c61ce76469d25497a8372 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 15 May 2024 10:51:18 +0400 Subject: [PATCH 61/70] Extra custody command line option (#49) --- .../tech/pegasys/teku/networking/eth2/P2PConfig.java | 3 ++- .../tech/pegasys/teku/cli/options/P2POptions.java | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 99a8dcf46a4..6abc92784f5 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -42,6 +42,7 @@ public class P2PConfig { public static final int DEFAULT_BATCH_VERIFY_QUEUE_CAPACITY = 15_000; public static final int DEFAULT_BATCH_VERIFY_MAX_BATCH_SIZE = 250; public static final boolean DEFAULT_BATCH_VERIFY_STRICT_THREAD_LIMIT_ENABLED = false; + public static final int DEFAULT_DAS_EXTRA_CUSTODY_SUBNET_COUNT = 0; private final Spec spec; private final NetworkConfig networkConfig; @@ -173,7 +174,7 @@ public static class Builder { private GossipEncoding gossipEncoding = GossipEncoding.SSZ_SNAPPY; private Integer targetSubnetSubscriberCount = DEFAULT_P2P_TARGET_SUBNET_SUBSCRIBER_COUNT; private Boolean subscribeAllSubnetsEnabled = DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; - private int dasExtraCustodySubnetCount = 0; + private int dasExtraCustodySubnetCount = DEFAULT_DAS_EXTRA_CUSTODY_SUBNET_COUNT; private Integer peerRateLimit = DEFAULT_PEER_RATE_LIMIT; private Integer peerRequestLimit = DEFAULT_PEER_REQUEST_LIMIT; private int batchVerifyMaxThreads = DEFAULT_BATCH_VERIFY_MAX_THREADS; diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java index 3af1824c318..55e432376b8 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java @@ -316,6 +316,14 @@ public class P2POptions { fallbackValue = "true") private boolean yamuxEnabled = NetworkConfig.DEFAULT_YAMUX_ENABLED; + @Option( + names = {"--Xdas-extra-custody-subnet-count"}, + paramLabel = "", + description = "Number of extra custody subnets", + arity = "1", + hidden = true) + private int dasExtraCustodySubnetCount = P2PConfig.DEFAULT_DAS_EXTRA_CUSTODY_SUBNET_COUNT; + private int getP2pLowerBound() { if (p2pLowerBound > p2pUpperBound) { STATUS_LOG.adjustingP2pLowerBoundToUpperBound(p2pUpperBound); @@ -350,7 +358,8 @@ public void configure(final TekuConfiguration.Builder builder) { .isGossipScoringEnabled(gossipScoringEnabled) .peerRateLimit(peerRateLimit) .allTopicsFilterEnabled(allTopicsFilterEnabled) - .peerRequestLimit(peerRequestLimit)) + .peerRequestLimit(peerRequestLimit) + .dasExtraCustodySubnetCount(dasExtraCustodySubnetCount)) .discovery( d -> { if (p2pDiscoveryBootnodes != null) { From 7dcf5b3c0333bc7492ff1a23db40d904735c3fdb Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Wed, 15 May 2024 13:30:54 +0400 Subject: [PATCH 62/70] Enable all custody subnets with all subnets subscribed (#51) --- .../spec/logic/common/helpers/MathHelpers.java | 8 ++++++++ .../networking/eth2/ActiveEth2P2PNetwork.java | 17 ++++++++++++++++- .../pegasys/teku/networking/eth2/P2PConfig.java | 4 ++++ ...taColumnSidecarSubnetBackboneSubscriber.java | 4 +++- .../beaconchain/BeaconChainController.java | 8 +++++++- 5 files changed, 38 insertions(+), 3 deletions(-) diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java index 0f6876e9166..d0947921a65 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/logic/common/helpers/MathHelpers.java @@ -143,4 +143,12 @@ public static Bytes uint256ToBytes(final UInt256 number) { // We should keep 32 bytes return Bytes32.leftPad(intBytes); } + + public static int intPlusMaxIntCapped(final int a, final int b) { + final UInt64 sum = UInt64.valueOf(a).plus(b); + if (sum.isLessThanOrEqualTo(UInt64.valueOf(Integer.MAX_VALUE))) { + return sum.intValue(); + } + return Integer.MAX_VALUE; + } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index 76195a90aef..b1cebbe22b7 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -43,6 +43,8 @@ import tech.pegasys.teku.networking.p2p.peer.NodeId; import tech.pegasys.teku.networking.p2p.peer.PeerConnectedSubscriber; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -157,7 +159,20 @@ private synchronized void startGossip() { discoveryNetworkSyncCommitteeSubnetsSubscription = syncCommitteeSubnetService.subscribeToUpdates( discoveryNetwork::setSyncCommitteeSubnetSubscriptions); - dasExtraCustodySubnetCount.ifPresent(discoveryNetwork::setDASExtraCustodySubnetCount); + if (spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + final int extraCustodySubnetCountConfig = dasExtraCustodySubnetCount.orElse(0); + final SpecConfigEip7594 configEip7594 = + SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); + final int minCustodyRequirement = configEip7594.getCustodyRequirement(); + final int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); + final int extraCustodySubnetCount = + Integer.min( + Integer.max(0, maxSubnets - minCustodyRequirement), extraCustodySubnetCountConfig); + LOG.info("Using extra custody sidecar columns count: {}", extraCustodySubnetCount); + if (extraCustodySubnetCount != 0) { + discoveryNetwork.setDASExtraCustodySubnetCount(extraCustodySubnetCount); + } + } gossipForkManager.configureGossipForEpoch(recentChainData.getCurrentEpoch().orElseThrow()); if (allTopicsFilterEnabled) { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 6abc92784f5..081bc354809 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -211,6 +211,10 @@ public P2PConfig build() { discoveryConfig.listenUdpPortDefault(networkConfig.getListenPort()); discoveryConfig.advertisedUdpPortDefault(OptionalInt.of(networkConfig.getAdvertisedPort())); + if (subscribeAllSubnetsEnabled) { + dasExtraCustodySubnetCount = Integer.MAX_VALUE; + } + return new P2PConfig( spec, networkConfig, diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index 4d47342921d..6ea9ccd7739 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -23,6 +23,7 @@ import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { private final Eth2P2PNetwork eth2P2PNetwork; @@ -67,7 +68,8 @@ private int getTotalSubnetCount(final UInt64 epoch) { SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(spec.atEpoch(epoch).getConfig()); return Integer.min( configEip7594.getDataColumnSidecarSubnetCount(), - configEip7594.getCustodyRequirement() + extraVoluntarySubnetCount); + MathHelpers.intPlusMaxIntCapped( + configEip7594.getCustodyRequirement(), extraVoluntarySubnetCount)); } private void onEpoch(final UInt64 epoch) { diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 9f4acf36028..316a822bea9 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -116,6 +116,7 @@ import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.executionlayer.ExecutionLayerBlockProductionManager; import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel; +import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult; import tech.pegasys.teku.spec.logic.common.util.BlockRewardCalculatorUtil; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; @@ -641,7 +642,9 @@ protected void initDasCustody() { int minCustodyRequirement = configEip7594.getCustodyRequirement(); int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); int totalMyCustodySubnets = - Integer.min(maxSubnets, minCustodyRequirement + dasExtraCustodySubnetCount); + Integer.min( + maxSubnets, + MathHelpers.intPlusMaxIntCapped(minCustodyRequirement, dasExtraCustodySubnetCount)); DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = new DataColumnSidecarCustodyImpl( @@ -982,6 +985,9 @@ protected void initSubnetSubscriber() { } protected void initDataColumnSidecarSubnetBackboneSubscriber() { + if (!spec.isMilestoneSupported(SpecMilestone.EIP7594)) { + return; + } LOG.debug("BeaconChainController.initDataColumnSidecarSubnetBackboneSubscriber"); DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = new DataColumnSidecarSubnetBackboneSubscriber( From 7ff372267a521b1241171cd402e7e4447bb69676 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Wed, 15 May 2024 15:26:37 +0300 Subject: [PATCH 63/70] Fix: 'complete' slot in DAS DB should be finalized (#50) * FirstIncompleteSlot: 'complete' slot should be finalized * Add logging of inbound DAS RPC requests * Add more logging details to DataColumnSidecarDBImpl * Add '[nyota]' prefix to all DAS related logs --- .../datacolumns/DasCustodySync.java | 4 ++-- .../DataColumnSidecarCustodyImpl.java | 20 ++++++++++++++----- .../datacolumns/DataColumnSidecarDBImpl.java | 7 ++++--- .../DataColumnReqRespBatchingImpl.java | 6 +++--- ...taColumnSidecarsByRangeMessageHandler.java | 15 ++++++++++++++ ...ataColumnSidecarsByRootMessageHandler.java | 13 ++++++++++++ .../beaconchain/BeaconChainController.java | 1 + 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java index 72995a0219a..f2da6bc31de 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DasCustodySync.java @@ -74,7 +74,7 @@ private synchronized void onRequestException(PendingRequest request, Throwable e if (wasCancelledImplicitly(exception)) { // request was cancelled explicitly here } else { - LOG.warn("Unexpected exception for request " + request, exception); + LOG.warn("[nyota] Unexpected exception for request " + request, exception); } } @@ -114,7 +114,7 @@ private synchronized void fillUp() { .map(ColumnSlotAndIdentifier::slot) .collect(Collectors.toSet()); LOG.info( - "DataCustodySync.fillUp: synced={} pending={}, missingColumns={}({})", + "[nyota] DataCustodySync.fillUp: synced={} pending={}, missingColumns={}({})", syncedColumnCount, pendingRequests.size(), missingColumnsToRequest.size(), diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index a493f422d24..27782c48be4 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -33,10 +33,12 @@ import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.logic.versions.eip7594.helpers.MiscHelpersEip7594; +import tech.pegasys.teku.storage.api.FinalizedCheckpointChannel; public class DataColumnSidecarCustodyImpl - implements UpdatableDataColumnSidecarCustody, SlotEventsChannel { + implements UpdatableDataColumnSidecarCustody, SlotEventsChannel, FinalizedCheckpointChannel { public interface CanonicalBlockResolver { @@ -172,10 +174,14 @@ public void onSlot(UInt64 slot) { if (slot.equals(spec.computeStartSlotAtEpoch(epoch))) { onEpoch(epoch); } - advanceFirstIncompleteSlot(); } - private void advanceFirstIncompleteSlot() { + @Override + public void onNewFinalizedCheckpoint(Checkpoint checkpoint, boolean fromOptimisticBlock) { + advanceFirstIncompleteSlot(checkpoint.getEpoch()); + } + + private void advanceFirstIncompleteSlot(UInt64 finalizedEpoch) { record CompleteIncomplete(SlotCustody firstIncomplete, SlotCustody lastComplete) { static final CompleteIncomplete ZERO = new CompleteIncomplete(null, null); @@ -200,11 +206,15 @@ Optional getFirstIncompleteSlot() { } } + UInt64 firstNonFinalizedSlot = spec.computeStartSlotAtEpoch(finalizedEpoch.increment()); + streamPotentiallyIncompleteSlotCustodies() + // will move FirstIncompleteSlot only to finalized slots + .takeWhile(sc -> sc.slot.isLessThan(firstNonFinalizedSlot)) .map(scan(CompleteIncomplete.ZERO, CompleteIncomplete::add)) .takeWhile(c -> c.firstIncomplete == null) - .reduce((a, b) -> b) - .flatMap(CompleteIncomplete::getFirstIncompleteSlot) // take the last lement + .reduce((a, b) -> b) // takeLast() + .flatMap(CompleteIncomplete::getFirstIncompleteSlot) .ifPresent(db::setFirstIncompleteSlot); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java index 15f9b0679f2..d7f9be0f936 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBImpl.java @@ -65,7 +65,7 @@ public void setFirstIncompleteSlot(final UInt64 slot) { long oldSlotColCount = oldValue.map(s -> streamColumnIdentifiers(s).count()).orElse(0L); long newSlotCount = streamColumnIdentifiers(slot).count(); LOG.info( - "DataColumnSidecarDB: setFirstIncompleteSlot {} ({} cols) ~> {} ({} cols)", + "[nyota] DataColumnSidecarDB: setFirstIncompleteSlot {} ({} cols) ~> {} ({} cols)", oldValue.map(UInt64::toString).orElse("NA"), oldSlotColCount, slot, @@ -81,10 +81,11 @@ public void addSidecar(final DataColumnSidecar sidecar) { synchronized (this) { if (slot > maxAddedSlot) { LOG.info( - "DataColumnSidecarDB.addSidecar: new slot: {}, prevSlot count: {}, total count: {}", + "[nyota] DataColumnSidecarDB.addSidecar: new slot: {}, prevSlot count: {}, total added: {}, finalizedSlot: {}", slot, streamColumnIdentifiers(UInt64.valueOf(maxAddedSlot)).count(), - addCounter.get()); + addCounter.get(), + getFirstIncompleteSlot().orElse(UInt64.ONE).decrement()); maxAddedSlot = slot; } } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index c7d220541f5..7f9a248e6f7 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -66,7 +66,7 @@ public void flush() { private void flushForNode(UInt256 nodeId, List nodeRequests) { LOG.info( - "Requesting batch of {} from {}, hash={}", + "[nyota] Requesting batch of {} from {}, hash={}", nodeRequests.size(), nodeId.mod(65536).toHexString(), nodeRequests.hashCode()); @@ -79,7 +79,7 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { response.finish( resp -> { LOG.info( - "Response batch of {} from {}, hash={}", + "[nyota] Response batch of {} from {}, hash={}", resp.size(), nodeId.mod(65536).toHexString(), nodeRequests.hashCode()); @@ -101,7 +101,7 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { nodeRequests.forEach( e -> { LOG.info( - "Error batch from {}, hash={}, err: {}", + "[nyota] Error batch from {}, hash={}, err: {}", nodeId.mod(65536).toHexString(), nodeRequests.hashCode(), e.toString()); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java index 10d2381fb09..b2276539919 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRangeMessageHandler.java @@ -58,6 +58,7 @@ public class DataColumnSidecarsByRangeMessageHandler DataColumnSidecarsByRangeRequestMessage, DataColumnSidecar> { private static final Logger LOG = LogManager.getLogger(); + private static final Logger LOG_DAS = LogManager.getLogger("das-nyota"); private final Spec spec; private final SpecConfigEip7594 specConfigEip7594; @@ -102,6 +103,12 @@ public void onIncomingMessage( message.getCount(), columns, startSlot); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRangeMessageHandler: REQUEST {} slots with columns {} of data column sidecars starting at slot {} from {}", + message.getCount(), + columns, + startSlot, + peer.getId()); final int requestedCount = message.getMaximumResponseChunks(); @@ -185,11 +192,19 @@ public void onIncomingMessage( } LOG.trace( "Sent {} data column sidecars to peer {}.", sentDataColumnSidecars, peer.getId()); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRangeMessageHandler: RESPONSE sent {} sidecars to {}", + sentDataColumnSidecars, + peer.getId()); callback.completeSuccessfully(); }, error -> { peer.adjustDataColumnSidecarsRequest(dataColumnSidecarsRequestApproval.get(), 0); handleProcessingRequestError(error, callback); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRangeMessageHandler: ERROR to {}: {}", + peer.getId(), + error.toString()); }); ; } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java index 3dbd0f325ea..f9222910501 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -50,6 +50,7 @@ public class DataColumnSidecarsByRootMessageHandler DataColumnSidecarsByRootRequestMessage, DataColumnSidecar> { private static final Logger LOG = LogManager.getLogger(); + private static final Logger LOG_DAS = LogManager.getLogger("das-nyota"); private final Spec spec; private final CombinedChainDataClient combinedChainDataClient; @@ -88,6 +89,10 @@ public void onIncomingMessage( peer.getId(), message.size(), message); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRootMessageHandler: REQUEST {} data column sidecars from {}", + message.size(), + peer.getId()); final Optional dataColumnSidecarsRequestApproval = peer.approveDataColumnSidecarsRequest(callback, message.size()); @@ -130,10 +135,18 @@ public void onIncomingMessage( dataColumnSidecarsRequestApproval.get(), sentDataColumnSidecars.get()); } callback.completeSuccessfully(); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRootMessageHandler: RESPOND {} data column sidecars to {}", + sentDataColumnSidecars.get(), + peer.getId()); }, err -> { peer.adjustDataColumnSidecarsRequest(dataColumnSidecarsRequestApproval.get(), 0); handleError(callback, err); + LOG_DAS.info( + "[nyota] DataColumnSidecarsByRootMessageHandler: ERROR to {}: {}", + peer.getId(), + err.toString()); }); } diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 316a822bea9..92a116bfd1c 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -650,6 +650,7 @@ protected void initDasCustody() { new DataColumnSidecarCustodyImpl( spec, blockRootResolver, sidecarDB, nodeId, totalMyCustodySubnets); eventChannels.subscribe(SlotEventsChannel.class, dataColumnSidecarCustodyImpl); + eventChannels.subscribe(FinalizedCheckpointChannel.class, dataColumnSidecarCustodyImpl); dataColumnSidecarManager.subscribeToValidDataColumnSidecars( dataColumnSidecarCustodyImpl::onNewValidatedDataColumnSidecar); this.dataColumnSidecarCustody = dataColumnSidecarCustodyImpl; From 94b147aa6158daae5039633e04ec0703d34d0f7e Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Wed, 15 May 2024 15:56:10 +0300 Subject: [PATCH 64/70] das-pr/3-peers-milestone (#52) * Switch to 'develop' libp2p version * Add logs on peer connect/disconnect * Log: fix nodeId output --- .../retriever/DataColumnReqRespBatchingImpl.java | 4 ++-- .../datacolumns/retriever/SimpleSidecarRetriever.java | 8 +++++++- gradle/versions.gradle | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index 7f9a248e6f7..8d7cc290400 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -68,7 +68,7 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { LOG.info( "[nyota] Requesting batch of {} from {}, hash={}", nodeRequests.size(), - nodeId.mod(65536).toHexString(), + "0x..." + nodeId.toHexString().substring(58), nodeRequests.hashCode()); SafeFuture> response = SafeFuture.of( @@ -81,7 +81,7 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { LOG.info( "[nyota] Response batch of {} from {}, hash={}", resp.size(), - nodeId.mod(65536).toHexString(), + "0x..." + nodeId.toHexString().substring(58), nodeRequests.hashCode()); Map byIds = new HashMap<>(); for (DataColumnSidecar sidecar : resp) { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index 8ebbfb4a4d6..f3f463f079f 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -43,7 +43,7 @@ // prevent potential dead locks public class SimpleSidecarRetriever implements DataColumnSidecarRetriever, DataColumnPeerManager.PeerListener { - private static final Logger LOG = LogManager.getLogger(); + private static final Logger LOG = LogManager.getLogger("das-nyota"); private final Spec spec; private final DataColumnPeerSearcher peerSearcher; @@ -184,11 +184,17 @@ private void reqRespCompleted( @Override public synchronized void peerConnected(UInt256 nodeId) { + LOG.info("[nyota] SimpleSidecarRetriever.peerConnected: {}", + "0x..." + nodeId.toHexString().substring(58) + ); connectedPeers.put(nodeId, new ConnectedPeer(nodeId)); } @Override public synchronized void peerDisconnected(UInt256 nodeId) { + LOG.info("[nyota] SimpleSidecarRetriever.peerDisconnected: {}", + "0x..." + nodeId.toHexString().substring(58) + ); connectedPeers.remove(nodeId); } diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 9b374fb9b5e..d2107725a60 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -32,7 +32,7 @@ dependencyManagement { entry 'javalin-rendering' } - dependency 'io.libp2p:jvm-libp2p:1.1.0-RELEASE' + dependency 'io.libp2p:jvm-libp2p:develop' dependency 'tech.pegasys:jblst:0.3.11' dependency 'tech.pegasys:jc-kzg-4844:das-test' From 7d4d65a5f19c298b4a89881f701ee5b8f26805e6 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 16 May 2024 13:33:57 +0400 Subject: [PATCH 65/70] Better logging for disconnection reasons (#53) * Better logging for disconnection reasons * request limit increase --- .../chains/ThrottlingSyncSource.java | 12 ++++++++--- .../retriever/SimpleSidecarRetriever.java | 12 +++++------ .../eth2/peers/DefaultEth2Peer.java | 4 ++-- .../eth2/peers/Eth2PeerFactory.java | 16 +++++++++++---- .../networking/eth2/peers/RateTracker.java | 7 +++++-- .../eth2/peers/RateTrackerImpl.java | 20 ++++++++++++++++++- .../networking/p2p/libp2p/LibP2PPeer.java | 4 ++-- 7 files changed, 55 insertions(+), 20 deletions(-) diff --git a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java index 33a2effacae..f0e3e25605d 100644 --- a/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java +++ b/beacon/sync/src/main/java/tech/pegasys/teku/beacon/sync/forward/multipeer/chains/ThrottlingSyncSource.java @@ -51,18 +51,24 @@ public ThrottlingSyncSource( final Optional maybeMaxDataColumnSidecarsPerMinute) { this.asyncRunner = asyncRunner; this.delegate = delegate; - this.blocksRateTracker = RateTracker.create(maxBlocksPerMinute, TIME_OUT, timeProvider); + this.blocksRateTracker = + RateTracker.create(maxBlocksPerMinute, TIME_OUT, timeProvider, "throttling-blocks"); this.blobSidecarsRateTracker = maybeMaxBlobSidecarsPerMinute .map( maxBlobSidecarsPerMinute -> - RateTracker.create(maxBlobSidecarsPerMinute, TIME_OUT, timeProvider)) + RateTracker.create( + maxBlobSidecarsPerMinute, TIME_OUT, timeProvider, "throttling-blobs")) .orElse(RateTracker.NOOP); this.dataColumnSidecarsRateTracker = maybeMaxDataColumnSidecarsPerMinute .map( maxDataColumnSidecarsPerMinute -> - RateTracker.create(maxDataColumnSidecarsPerMinute, TIME_OUT, timeProvider)) + RateTracker.create( + maxDataColumnSidecarsPerMinute, + TIME_OUT, + timeProvider, + "throttling-dataColumn")) .orElse(RateTracker.NOOP); } diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index f3f463f079f..ffcba140082 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -184,17 +184,17 @@ private void reqRespCompleted( @Override public synchronized void peerConnected(UInt256 nodeId) { - LOG.info("[nyota] SimpleSidecarRetriever.peerConnected: {}", - "0x..." + nodeId.toHexString().substring(58) - ); + LOG.info( + "[nyota] SimpleSidecarRetriever.peerConnected: {}", + "0x..." + nodeId.toHexString().substring(58)); connectedPeers.put(nodeId, new ConnectedPeer(nodeId)); } @Override public synchronized void peerDisconnected(UInt256 nodeId) { - LOG.info("[nyota] SimpleSidecarRetriever.peerDisconnected: {}", - "0x..." + nodeId.toHexString().substring(58) - ); + LOG.info( + "[nyota] SimpleSidecarRetriever.peerDisconnected: {}", + "0x..." + nodeId.toHexString().substring(58)); connectedPeers.remove(nodeId); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java index 99172d26893..16517c28392 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/DefaultEth2Peer.java @@ -523,7 +523,7 @@ public void adjustDataColumnSidecarsRequest( @Override public boolean approveRequest() { if (requestTracker.approveObjectsRequest(1L).isEmpty()) { - LOG.debug("Peer {} disconnected due to request rate limits", getId()); + LOG.info("Peer {} disconnected due to request rate limits for {}", getId(), requestTracker); disconnectCleanly(DisconnectReason.RATE_LIMITING).ifExceptionGetsHereRaiseABug(); return false; } @@ -568,7 +568,7 @@ private Optional approveObjectsRequest( final Optional requestApproval = requestTracker.approveObjectsRequest(objectsCount); if (requestApproval.isEmpty()) { - LOG.debug("Peer {} disconnected due to {} rate limits", getId(), requestType); + LOG.info("Peer {} disconnected due to {} rate limits", getId(), requestType); callback.completeWithErrorResponse( new RpcException(INVALID_REQUEST_CODE, "Peer has been rate limited")); disconnectCleanly(DisconnectReason.RATE_LIMITING).ifExceptionGetsHereRaiseABug(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java index b11b75b4a45..3ecc99884af 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerFactory.java @@ -28,6 +28,7 @@ public class Eth2PeerFactory { private static final long TIME_OUT = 60; + private static final int REQUEST_RATE_LIMIT_BOOST = 50; private final Spec spec; private final StatusMessageFactory statusMessageFactory; private final MetadataMessagesFactory metadataMessagesFactory; @@ -74,12 +75,19 @@ public Eth2Peer create(final Peer peer, final BeaconChainMethods rpcMethods) { statusMessageFactory, metadataMessagesFactory, PeerChainValidator.create(spec, metricsSystem, chainDataClient, requiredCheckpoint), - RateTracker.create(peerRateLimit, TIME_OUT, timeProvider), + RateTracker.create(peerRateLimit, TIME_OUT, timeProvider, "blocks"), RateTracker.create( - peerRateLimit * spec.getMaxBlobsPerBlock().orElse(1), TIME_OUT, timeProvider), + peerRateLimit * spec.getMaxBlobsPerBlock().orElse(1), + TIME_OUT, + timeProvider, + "blobSidecars"), RateTracker.create( - peerRateLimit * spec.getNumberOfDataColumns().orElse(1), TIME_OUT, timeProvider), - RateTracker.create(peerRequestLimit, TIME_OUT, timeProvider), + peerRateLimit * spec.getNumberOfDataColumns().orElse(1), + TIME_OUT, + timeProvider, + "dataColumns"), + RateTracker.create( + peerRequestLimit * REQUEST_RATE_LIMIT_BOOST, TIME_OUT, timeProvider, "requestTracker"), kzg); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java index f245d1cf62c..31c2d2b724a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTracker.java @@ -48,7 +48,10 @@ public void pruneRequests() {} void pruneRequests(); static RateTracker create( - final int peerRateLimit, final long timeoutSeconds, final TimeProvider timeProvider) { - return new RateTrackerImpl(peerRateLimit, timeoutSeconds, timeProvider); + final int peerRateLimit, + final long timeoutSeconds, + final TimeProvider timeProvider, + final String name) { + return new RateTrackerImpl(peerRateLimit, timeoutSeconds, timeProvider, name); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java index c28bb31fa1f..66a31e63c92 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/RateTrackerImpl.java @@ -26,15 +26,20 @@ public class RateTrackerImpl implements RateTracker { private final UInt64 timeoutSeconds; private long objectsWithinWindow = 0L; private final TimeProvider timeProvider; + private final String name; private final AtomicInteger newRequestId = new AtomicInteger(0); public RateTrackerImpl( - final int peerRateLimit, final long timeoutSeconds, final TimeProvider timeProvider) { + final int peerRateLimit, + final long timeoutSeconds, + final TimeProvider timeProvider, + final String name) { this.timeoutSeconds = UInt64.valueOf(timeoutSeconds); requests = new TreeMap<>(); this.peerRateLimit = peerRateLimit; this.timeProvider = timeProvider; + this.name = name; } // boundary: if a request comes in and remaining capacity is at least 1, then @@ -85,4 +90,17 @@ public void pruneRequests() { headMap.values().forEach(value -> objectsWithinWindow -= value); headMap.clear(); } + + @Override + public String toString() { + return "RateTrackerImpl{" + + "peerRateLimit=" + + peerRateLimit + + ", objectsWithinWindow=" + + objectsWithinWindow + + ", name='" + + name + + '\'' + + '}'; + } } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java index 3bb92f926b7..bee11a1efd9 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/libp2p/LibP2PPeer.java @@ -145,7 +145,7 @@ public void disconnectImmediately( disconnectLocallyInitiated = locallyInitiated; SafeFuture.of(connection.close()) .finish( - () -> LOG.trace("Disconnected from {} because {}", getId(), reason), + () -> LOG.info("Disconnected from {} because {}", getId(), reason), error -> LOG.warn("Failed to disconnect from peer {}", getId(), error)); } @@ -171,7 +171,7 @@ private SafeFuture getIdentify() { @Override public SafeFuture disconnectCleanly(final DisconnectReason reason) { - LOG.trace("Disconnecting peer {} because {}", getId(), reason); + LOG.info("Disconnecting peer {} because {}", getId(), reason); connected.set(false); disconnectReason = Optional.of(reason); disconnectLocallyInitiated = true; From 06254724ba4f5343e6fd0cbb2f8d684d63b418d9 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 16 May 2024 14:00:24 +0300 Subject: [PATCH 66/70] Fix remote subnet tracking, other stuff (#54) * SimpleSidecarRetriever: Add more synchronized * SimpleSidecarRetriever: add logging stats on the number of custody peers for columns * P2PConfig.getDasExtraCustodySubnetCount: capped in place with Spec * GossipTopicDasPeerCustodyTracker: track subscriptions periodically since right after cionnection it may fail as a remote peer didn't yest subscribed * Cleanup totl/extraCustodySubnetCount mess --- .../retriever/SimpleSidecarRetriever.java | 72 +++++++++++++------ .../networking/eth2/ActiveEth2P2PNetwork.java | 19 ++--- .../eth2/Eth2P2PNetworkBuilder.java | 7 +- .../teku/networking/eth2/P2PConfig.java | 18 ++++- ...ColumnSidecarSubnetBackboneSubscriber.java | 21 ++---- .../GossipTopicDasPeerCustodyTracker.java | 14 +++- .../eth2/ActiveEth2P2PNetworkTest.java | 4 +- .../eth2/Eth2P2PNetworkFactory.java | 2 +- .../beaconchain/BeaconChainController.java | 19 +++-- 9 files changed, 105 insertions(+), 71 deletions(-) diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java index ffcba140082..b707d37a053 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/SimpleSidecarRetriever.java @@ -23,7 +23,9 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.TreeMap; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; @@ -92,17 +94,15 @@ public synchronized SafeFuture retrieve(ColumnSlotAndIdentifi DataColumnPeerSearcher.PeerSearchRequest peerSearchRequest = peerSearcher.requestPeers(columnId.slot(), columnId.identifier().getIndex()); - synchronized (this) { - RetrieveRequest existingRequest = pendingRequests.get(columnId); - if (existingRequest == null) { - RetrieveRequest request = new RetrieveRequest(columnId, peerSearchRequest); - pendingRequests.put(columnId, request); - startIfNecessary(); - return request.result; - } else { - peerSearchRequest.dispose(); - return existingRequest.result; - } + RetrieveRequest existingRequest = pendingRequests.get(columnId); + if (existingRequest == null) { + RetrieveRequest request = new RetrieveRequest(columnId, peerSearchRequest); + pendingRequests.put(columnId, request); + startIfNecessary(); + return request.result; + } else { + peerSearchRequest.dispose(); + return existingRequest.result; } } @@ -153,7 +153,7 @@ private void disposeCancelledRequests() { } } - void nextRound() { + private synchronized void nextRound() { List matches = matchRequestsAndPeers(); for (RequestMatch match : matches) { SafeFuture reqRespPromise = @@ -165,11 +165,20 @@ void nextRound() { match.peer); } + long activeRequestCount = + pendingRequests.values().stream().filter(r -> r.activeRpcRequest != null).count(); + LOG.info( + "[nyota] SimpleSidecarRetriever.nextRound: total pending: {}, active pending: {}, new pending: {}, number of custody peers: {}", + pendingRequests.size(), + activeRequestCount, + matches.size(), + gatherAvailableCustodiesInfo()); + reqResp.flush(); } @SuppressWarnings("unused") - private void reqRespCompleted( + private synchronized void reqRespCompleted( RetrieveRequest request, DataColumnSidecar maybeResult, Throwable maybeError) { if (maybeResult != null) { synchronized (this) { @@ -182,6 +191,30 @@ private void reqRespCompleted( } } + private String gatherAvailableCustodiesInfo() { + SpecVersion specVersion = spec.forMilestone(SpecMilestone.EIP7594); + Map colIndexToCount = + connectedPeers.values().stream() + .flatMap(p -> p.getNodeCustodyIndexes(specVersion).stream()) + .collect(Collectors.groupingBy(i -> i, Collectors.counting())); + int numberOfColumns = SpecConfigEip7594.required(specVersion.getConfig()).getNumberOfColumns(); + IntStream.range(0, numberOfColumns) + .mapToObj(UInt64::valueOf) + .forEach(idx -> colIndexToCount.putIfAbsent(idx, 0L)); + colIndexToCount.replaceAll((colIdx, count) -> Long.min(3, count)); + Map custodyCountToPeerCount = + colIndexToCount.entrySet().stream() + .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.counting())); + return new TreeMap<>(custodyCountToPeerCount) + .entrySet().stream() + .map( + entry -> { + String peerCnt = entry.getKey() == 3 ? "3+" : "" + entry.getKey(); + return entry.getValue() + " cols: " + peerCnt + " peers"; + }) + .collect(Collectors.joining(",")); + } + @Override public synchronized void peerConnected(UInt256 nodeId) { LOG.info( @@ -217,23 +250,18 @@ private RetrieveRequest( private class ConnectedPeer { final UInt256 nodeId; - // final int extraCustodySubnetCount; - - public ConnectedPeer(UInt256 nodeId /*, int extraCustodySubnetCount*/) { + public ConnectedPeer(UInt256 nodeId) { this.nodeId = nodeId; - // this.extraCustodySubnetCount = extraCustodySubnetCount; } - private List getNodeCustodyIndexes(UInt64 slot) { - SpecVersion specVersion = spec.atSlot(slot); - // int minCustodyRequirement = - // SpecConfigEip7594.required(specVersion.getConfig()).getCustodyRequirement(); + private List getNodeCustodyIndexes(SpecVersion specVersion) { return MiscHelpersEip7594.required(specVersion.miscHelpers()) .computeCustodyColumnIndexes(nodeId, custodyCountSupplier.getCustodyCountForPeer(nodeId)); } public boolean isCustodyFor(ColumnSlotAndIdentifier columnId) { - return getNodeCustodyIndexes(columnId.slot()).contains(columnId.identifier().getIndex()); + return getNodeCustodyIndexes(spec.atSlot(columnId.slot())) + .contains(columnId.identifier().getIndex()); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index b1cebbe22b7..cb3175aa3c3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -44,7 +44,6 @@ import tech.pegasys.teku.networking.p2p.peer.PeerConnectedSubscriber; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; -import tech.pegasys.teku.spec.config.SpecConfigEip7594; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.operations.AttesterSlashing; import tech.pegasys.teku.spec.datastructures.operations.ProposerSlashing; @@ -74,7 +73,7 @@ public class ActiveEth2P2PNetwork extends DelegatingP2PNetwork impleme private final SubnetSubscriptionService dataColumnSidecarSubnetService; private final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; private final AtomicBoolean gossipStarted = new AtomicBoolean(false); - private final Optional dasExtraCustodySubnetCount; + private final int dasExtraCustodySubnetCount; private final GossipForkManager gossipForkManager; @@ -99,7 +98,7 @@ public ActiveEth2P2PNetwork( final GossipEncoding gossipEncoding, final GossipConfigurator gossipConfigurator, final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider, - final Optional dasExtraCustodySubnetCount, + final int dasExtraCustodySubnetCount, final boolean allTopicsFilterEnabled) { super(discoveryNetwork); this.spec = spec; @@ -160,17 +159,9 @@ private synchronized void startGossip() { syncCommitteeSubnetService.subscribeToUpdates( discoveryNetwork::setSyncCommitteeSubnetSubscriptions); if (spec.isMilestoneSupported(SpecMilestone.EIP7594)) { - final int extraCustodySubnetCountConfig = dasExtraCustodySubnetCount.orElse(0); - final SpecConfigEip7594 configEip7594 = - SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); - final int minCustodyRequirement = configEip7594.getCustodyRequirement(); - final int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); - final int extraCustodySubnetCount = - Integer.min( - Integer.max(0, maxSubnets - minCustodyRequirement), extraCustodySubnetCountConfig); - LOG.info("Using extra custody sidecar columns count: {}", extraCustodySubnetCount); - if (extraCustodySubnetCount != 0) { - discoveryNetwork.setDASExtraCustodySubnetCount(extraCustodySubnetCount); + LOG.info("Using extra custody sidecar columns count: {}", dasExtraCustodySubnetCount); + if (dasExtraCustodySubnetCount != 0) { + discoveryNetwork.setDASExtraCustodySubnetCount(dasExtraCustodySubnetCount); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 531f1170981..cead794473a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -74,6 +74,7 @@ import tech.pegasys.teku.networking.p2p.reputation.ReputationManager; import tech.pegasys.teku.networking.p2p.rpc.RpcMethod; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; import tech.pegasys.teku.spec.config.Constants; import tech.pegasys.teku.spec.datastructures.attestation.ValidatableAttestation; import tech.pegasys.teku.spec.datastructures.blobs.versions.deneb.BlobSidecar; @@ -196,10 +197,8 @@ public Eth2P2PNetwork build() { final GossipForkManager gossipForkManager = buildGossipForkManager(gossipEncoding, network); - final Optional dasExtraCustodySubnetCount = - config.getDasExtraCustodySubnetCount() == 0 - ? Optional.empty() - : Optional.of(config.getDasExtraCustodySubnetCount()); + int dasExtraCustodySubnetCount = + config.getDasExtraCustodySubnetCount(spec.forMilestone(SpecMilestone.EIP7594)); return new ActiveEth2P2PNetwork( config.getSpec(), diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 081bc354809..085a621b7cd 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -25,8 +25,11 @@ import tech.pegasys.teku.networking.p2p.discovery.DiscoveryConfig; import tech.pegasys.teku.networking.p2p.network.config.NetworkConfig; import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecVersion; import tech.pegasys.teku.spec.config.NetworkingSpecConfig; import tech.pegasys.teku.spec.config.SpecConfig; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; public class P2PConfig { @@ -129,8 +132,19 @@ public boolean isSubscribeAllSubnetsEnabled() { return subscribeAllSubnetsEnabled; } - public int getDasExtraCustodySubnetCount() { - return dasExtraCustodySubnetCount; + public int getDasExtraCustodySubnetCount(SpecVersion specVersion) { + SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(specVersion.getConfig()); + int minCustodyRequirement = configEip7594.getCustodyRequirement(); + return getTotalCustodySubnetCount(specVersion) - minCustodyRequirement; + } + + public int getTotalCustodySubnetCount(SpecVersion specVersion) { + SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(specVersion.getConfig()); + int minCustodyRequirement = configEip7594.getCustodyRequirement(); + int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); + return Integer.min( + maxSubnets, + MathHelpers.intPlusMaxIntCapped(minCustodyRequirement, dasExtraCustodySubnetCount)); } public int getPeerRateLimit() { diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java index 6ea9ccd7739..8168288f9f2 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/DataColumnSidecarSubnetBackboneSubscriber.java @@ -22,27 +22,22 @@ import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.networking.eth2.Eth2P2PNetwork; import tech.pegasys.teku.spec.Spec; -import tech.pegasys.teku.spec.config.SpecConfigEip7594; -import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; public class DataColumnSidecarSubnetBackboneSubscriber implements SlotEventsChannel { private final Eth2P2PNetwork eth2P2PNetwork; private final UInt256 nodeId; - private final int extraVoluntarySubnetCount; + private final int totalSubnetCount; private final Spec spec; private IntSet currentSubscribedSubnets = IntSet.of(); private UInt64 lastEpoch = UInt64.MAX_VALUE; public DataColumnSidecarSubnetBackboneSubscriber( - final Spec spec, - final Eth2P2PNetwork eth2P2PNetwork, - UInt256 nodeId, - int extraVoluntarySubnetCount) { + final Spec spec, final Eth2P2PNetwork eth2P2PNetwork, UInt256 nodeId, int totalSubnetCount) { this.spec = spec; this.eth2P2PNetwork = eth2P2PNetwork; this.nodeId = nodeId; - this.extraVoluntarySubnetCount = extraVoluntarySubnetCount; + this.totalSubnetCount = totalSubnetCount; } private void subscribeToSubnets(final Collection newSubscriptions) { @@ -64,14 +59,6 @@ private void subscribeToSubnets(final Collection newSubscriptions) { currentSubscribedSubnets = newSubscriptionsSet; } - private int getTotalSubnetCount(final UInt64 epoch) { - SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(spec.atEpoch(epoch).getConfig()); - return Integer.min( - configEip7594.getDataColumnSidecarSubnetCount(), - MathHelpers.intPlusMaxIntCapped( - configEip7594.getCustodyRequirement(), extraVoluntarySubnetCount)); - } - private void onEpoch(final UInt64 epoch) { spec.atEpoch(epoch) .miscHelpers() @@ -80,7 +67,7 @@ private void onEpoch(final UInt64 epoch) { eip7594Spec -> { List subnets = eip7594Spec.computeDataColumnSidecarBackboneSubnets( - nodeId, epoch, getTotalSubnetCount(epoch)); + nodeId, epoch, totalSubnetCount); subscribeToSubnets(subnets.stream().map(UInt64::intValue).toList()); }); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java index 7b722cd9f24..fc20135fe78 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java @@ -13,6 +13,7 @@ package tech.pegasys.teku.networking.eth2.peers; +import java.time.Duration; import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -21,7 +22,10 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.stream.Collectors; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.tuweni.units.bigints.UInt256; +import tech.pegasys.teku.infrastructure.async.AsyncRunner; import tech.pegasys.teku.networking.eth2.gossip.encoding.GossipEncoding; import tech.pegasys.teku.networking.eth2.gossip.topics.GossipTopics; import tech.pegasys.teku.networking.p2p.gossip.GossipNetwork; @@ -33,6 +37,7 @@ public class GossipTopicDasPeerCustodyTracker implements DasPeerCustodyCountSupplier, PeerConnectedSubscriber { + private static final Logger LOG = LogManager.getLogger("das-nyota"); public static final int NO_SUBNET_COUNT_INFO = -1; @@ -47,11 +52,16 @@ public GossipTopicDasPeerCustodyTracker( Spec spec, GossipNetwork gossipNetwork, GossipEncoding gossipEncoding, - Supplier> currentForkInfoSupplier) { + Supplier> currentForkInfoSupplier, + AsyncRunner asyncRunner) { this.spec = spec; this.gossipNetwork = gossipNetwork; this.gossipEncoding = gossipEncoding; this.currentForkInfoSupplier = currentForkInfoSupplier; + asyncRunner.runWithFixedDelay( + this::refreshExistingSubscriptions, + Duration.ofSeconds(1), + e -> LOG.warn("[nyota] Error {}", e, e)); } @Override @@ -76,7 +86,7 @@ private Set getCurrentDasTopics() { .orElse(Collections.emptySet()); } - private void refreshExistingSubscriptions() { + private synchronized void refreshExistingSubscriptions() { Map> subscribersByTopic = gossipNetwork.getSubscribersByTopic(); Set dasTopics = getCurrentDasTopics(); record NodeTopic(NodeId nodeId, String topic) {} diff --git a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java index d479dad96b4..62361a28552 100644 --- a/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java +++ b/networking/eth2/src/test/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetworkTest.java @@ -292,7 +292,7 @@ void isCloseToInSync_shouldReturnFalseWhenEmptyCurrentEpoch() { gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, - Optional.empty(), + 0, true); assertThat(network.isCloseToInSync()).isFalse(); @@ -330,7 +330,7 @@ ActiveEth2P2PNetwork createNetwork() { gossipEncoding, gossipConfigurator, processedAttestationSubscriptionProvider, - Optional.empty(), + 0, true); } } diff --git a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java index da38f4aff73..e9faa71e8a5 100644 --- a/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java +++ b/networking/eth2/src/testFixtures/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkFactory.java @@ -359,7 +359,7 @@ protected Eth2P2PNetwork buildNetwork(final P2PConfig config) { gossipEncoding, GossipConfigurator.NOOP, processedAttestationSubscriptionProvider, - Optional.empty(), + 0, config.isAllTopicsFilterEnabled()); } } diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 92a116bfd1c..60a8f3a5d96 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -116,7 +116,6 @@ import tech.pegasys.teku.spec.datastructures.validator.BroadcastValidationLevel; import tech.pegasys.teku.spec.executionlayer.ExecutionLayerBlockProductionManager; import tech.pegasys.teku.spec.executionlayer.ExecutionLayerChannel; -import tech.pegasys.teku.spec.logic.common.helpers.MathHelpers; import tech.pegasys.teku.spec.logic.common.statetransition.results.BlockImportResult; import tech.pegasys.teku.spec.logic.common.util.BlockRewardCalculatorUtil; import tech.pegasys.teku.spec.logic.versions.deneb.helpers.MiscHelpersDeneb; @@ -636,15 +635,14 @@ protected void initDasCustody() { .thenApply(sbb -> sbb.flatMap(SignedBeaconBlock::getBeaconBlock)) .join(); - int dasExtraCustodySubnetCount = beaconConfig.p2pConfig().getDasExtraCustodySubnetCount(); SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); int minCustodyRequirement = configEip7594.getCustodyRequirement(); int maxSubnets = configEip7594.getDataColumnSidecarSubnetCount(); int totalMyCustodySubnets = - Integer.min( - maxSubnets, - MathHelpers.intPlusMaxIntCapped(minCustodyRequirement, dasExtraCustodySubnetCount)); + beaconConfig + .p2pConfig() + .getTotalCustodySubnetCount(spec.forMilestone(SpecMilestone.EIP7594)); DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = new DataColumnSidecarCustodyImpl( @@ -665,7 +663,8 @@ protected void initDasCustody() { spec, p2pNetwork, beaconConfig.p2pConfig().getGossipEncoding(), - () -> recentChainData.getCurrentForkInfo()); + () -> recentChainData.getCurrentForkInfo(), + operationPoolAsyncRunner); p2pNetwork.subscribeConnect(peerCustodyTracker); DasPeerCustodyCountSupplier custodyCountSupplier = @@ -992,7 +991,13 @@ protected void initDataColumnSidecarSubnetBackboneSubscriber() { LOG.debug("BeaconChainController.initDataColumnSidecarSubnetBackboneSubscriber"); DataColumnSidecarSubnetBackboneSubscriber subnetBackboneSubscriber = new DataColumnSidecarSubnetBackboneSubscriber( - spec, p2pNetwork, nodeId, beaconConfig.p2pConfig().getDasExtraCustodySubnetCount()); + spec, + p2pNetwork, + nodeId, + beaconConfig + .p2pConfig() + .getTotalCustodySubnetCount(spec.forMilestone(SpecMilestone.EIP7594))); + eventChannels.subscribe(SlotEventsChannel.class, subnetBackboneSubscriber); } From 9fd3f7b5148868b9d708161d667dbd9e9dba170d Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 16 May 2024 17:24:56 +0400 Subject: [PATCH 67/70] Switch from extra subnet count in discovery to total subnet count like others do (#55) --- .../teku/networking/eth2/ActiveEth2P2PNetwork.java | 12 +++++------- .../teku/networking/eth2/Eth2P2PNetworkBuilder.java | 6 +++--- .../tech/pegasys/teku/networking/eth2/P2PConfig.java | 6 ------ .../NodeIdToDataColumnSidecarSubnetsCalculator.java | 12 ++++++------ .../eth2/gossip/subnets/PeerSubnetSubscriptions.java | 5 +++-- .../networking/eth2/gossip/subnets/SubnetScorer.java | 2 +- .../networking/p2p/discovery/DiscoveryNetwork.java | 2 +- .../teku/networking/p2p/discovery/DiscoveryPeer.java | 10 +++++----- .../p2p/discovery/discv5/NodeRecordConverter.java | 4 ++-- 9 files changed, 26 insertions(+), 33 deletions(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java index cb3175aa3c3..f1804d09c05 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/ActiveEth2P2PNetwork.java @@ -73,7 +73,7 @@ public class ActiveEth2P2PNetwork extends DelegatingP2PNetwork impleme private final SubnetSubscriptionService dataColumnSidecarSubnetService; private final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider; private final AtomicBoolean gossipStarted = new AtomicBoolean(false); - private final int dasExtraCustodySubnetCount; + private final int dasTotalCustodySubnetCount; private final GossipForkManager gossipForkManager; @@ -98,7 +98,7 @@ public ActiveEth2P2PNetwork( final GossipEncoding gossipEncoding, final GossipConfigurator gossipConfigurator, final ProcessedAttestationSubscriptionProvider processedAttestationSubscriptionProvider, - final int dasExtraCustodySubnetCount, + final int dasTotalCustodySubnetCount, final boolean allTopicsFilterEnabled) { super(discoveryNetwork); this.spec = spec; @@ -114,7 +114,7 @@ public ActiveEth2P2PNetwork( this.syncCommitteeSubnetService = syncCommitteeSubnetService; this.dataColumnSidecarSubnetService = dataColumnSidecarSubnetService; this.processedAttestationSubscriptionProvider = processedAttestationSubscriptionProvider; - this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount; + this.dasTotalCustodySubnetCount = dasTotalCustodySubnetCount; this.allTopicsFilterEnabled = allTopicsFilterEnabled; } @@ -159,10 +159,8 @@ private synchronized void startGossip() { syncCommitteeSubnetService.subscribeToUpdates( discoveryNetwork::setSyncCommitteeSubnetSubscriptions); if (spec.isMilestoneSupported(SpecMilestone.EIP7594)) { - LOG.info("Using extra custody sidecar columns count: {}", dasExtraCustodySubnetCount); - if (dasExtraCustodySubnetCount != 0) { - discoveryNetwork.setDASExtraCustodySubnetCount(dasExtraCustodySubnetCount); - } + LOG.info("Using custody sidecar columns count: {}", dasTotalCustodySubnetCount); + discoveryNetwork.setDASTotalCustodySubnetCount(dasTotalCustodySubnetCount); } gossipForkManager.configureGossipForEpoch(recentChainData.getCurrentEpoch().orElseThrow()); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index cead794473a..6c1f94a19b3 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -197,8 +197,8 @@ public Eth2P2PNetwork build() { final GossipForkManager gossipForkManager = buildGossipForkManager(gossipEncoding, network); - int dasExtraCustodySubnetCount = - config.getDasExtraCustodySubnetCount(spec.forMilestone(SpecMilestone.EIP7594)); + int dasTotalCustodySubnetCount = + config.getTotalCustodySubnetCount(spec.forMilestone(SpecMilestone.EIP7594)); return new ActiveEth2P2PNetwork( config.getSpec(), @@ -214,7 +214,7 @@ public Eth2P2PNetwork build() { gossipEncoding, config.getGossipConfigurator(), processedAttestationSubscriptionProvider, - dasExtraCustodySubnetCount, + dasTotalCustodySubnetCount, config.isAllTopicsFilterEnabled()); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index 085a621b7cd..d6c895928d6 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -132,12 +132,6 @@ public boolean isSubscribeAllSubnetsEnabled() { return subscribeAllSubnetsEnabled; } - public int getDasExtraCustodySubnetCount(SpecVersion specVersion) { - SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(specVersion.getConfig()); - int minCustodyRequirement = configEip7594.getCustodyRequirement(); - return getTotalCustodySubnetCount(specVersion) - minCustodyRequirement; - } - public int getTotalCustodySubnetCount(SpecVersion specVersion) { SpecConfigEip7594 configEip7594 = SpecConfigEip7594.required(specVersion.getConfig()); int minCustodyRequirement = configEip7594.getCustodyRequirement(); diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java index 8afca4f59d1..ce3effe3c52 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/NodeIdToDataColumnSidecarSubnetsCalculator.java @@ -29,9 +29,9 @@ @FunctionalInterface public interface NodeIdToDataColumnSidecarSubnetsCalculator { - Optional calculateSubnets(UInt256 nodeId, int extraSubnetCount); + Optional calculateSubnets(UInt256 nodeId, Optional subnetCount); - NodeIdToDataColumnSidecarSubnetsCalculator NOOP = (nodeId, extraSubnetCount) -> Optional.empty(); + NodeIdToDataColumnSidecarSubnetsCalculator NOOP = (nodeId, subnetCount) -> Optional.empty(); /** Creates a calculator instance for the specific slot */ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( @@ -39,10 +39,10 @@ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( UInt64 currentEpoch = miscHelpers.computeEpochAtSlot(currentSlot); SszBitvectorSchema bitvectorSchema = SszBitvectorSchema.create(config.getDataColumnSidecarSubnetCount()); - return (nodeId, extraSubnetCount) -> { + return (nodeId, subnetCount) -> { List nodeSubnets = miscHelpers.computeDataColumnSidecarBackboneSubnets( - nodeId, currentEpoch, config.getCustodyRequirement() + extraSubnetCount); + nodeId, currentEpoch, subnetCount.orElse(config.getCustodyRequirement())); return Optional.of( bitvectorSchema.ofBits(nodeSubnets.stream().map(UInt64::intValue).toList())); }; @@ -52,7 +52,7 @@ private static NodeIdToDataColumnSidecarSubnetsCalculator createAtSlot( static NodeIdToDataColumnSidecarSubnetsCalculator create( Spec spec, Supplier> currentSlotSupplier) { - return (nodeId, extraSubnetCount) -> + return (nodeId, subnetCount) -> currentSlotSupplier .get() .flatMap( @@ -68,7 +68,7 @@ static NodeIdToDataColumnSidecarSubnetsCalculator create( } else { calculatorAtSlot = NOOP; } - return calculatorAtSlot.calculateSubnets(nodeId, extraSubnetCount); + return calculatorAtSlot.calculateSubnets(nodeId, subnetCount); }); } } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java index 70547fb25fc..2747b13d56b 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/PeerSubnetSubscriptions.java @@ -23,6 +23,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.OptionalInt; import java.util.function.Consumer; import java.util.stream.IntStream; @@ -206,9 +207,9 @@ public SszBitvector getDataColumnSidecarSubnetSubscriptions(final NodeId peerId) } public SszBitvector getDataColumnSidecarSubnetSubscriptionsByNodeId( - final UInt256 peerId, final int extraSubnetCount) { + final UInt256 peerId, final Optional custodySubnetCount) { return nodeIdToDataColumnSidecarSubnetsCalculator - .calculateSubnets(peerId, extraSubnetCount) + .calculateSubnets(peerId, custodySubnetCount) .orElse(dataColumnSidecarSubnetSubscriptions.getSubscriptionSchema().getDefault()); } diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java index 3939ffcca78..e74a4c5a022 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/gossip/subnets/SubnetScorer.java @@ -54,7 +54,7 @@ public int scoreCandidatePeer(DiscoveryPeer candidate) { candidate.getPersistentAttestationSubnets(), candidate.getSyncCommitteeSubnets(), peerSubnetSubscriptions.getDataColumnSidecarSubnetSubscriptionsByNodeId( - UInt256.fromBytes(candidate.getNodeId()), candidate.getDasExtraCustodySubnetCount())); + UInt256.fromBytes(candidate.getNodeId()), candidate.getDasCustodySubnetCount())); } // @Override diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java index bde7a5dfa89..4911b073a7a 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryNetwork.java @@ -139,7 +139,7 @@ public void setSyncCommitteeSubnetSubscriptions(Iterable subnetIds) { .sszSerialize()); } - public void setDASExtraCustodySubnetCount(int count) { + public void setDASTotalCustodySubnetCount(int count) { discoveryService.updateCustomENRField( DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD, SszUInt64.of(UInt64.valueOf(count)).sszSerialize()); } diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java index d011bdb5834..12a45604f43 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/DiscoveryPeer.java @@ -28,7 +28,7 @@ public class DiscoveryPeer { private final Optional enrForkId; private final SszBitvector persistentAttestationSubnets; private final SszBitvector syncCommitteeSubnets; - private final int dasExtraCustodySubnetCount; + private final Optional dasCustodySubnetCount; public DiscoveryPeer( final Bytes publicKey, @@ -37,14 +37,14 @@ public DiscoveryPeer( final Optional enrForkId, final SszBitvector persistentAttestationSubnets, final SszBitvector syncCommitteeSubnets, - final Optional dasExtraCustodySubnetCount) { + final Optional dasCustodySubnetCount) { this.publicKey = publicKey; this.nodeId = nodeId; this.nodeAddress = nodeAddress; this.enrForkId = enrForkId; this.persistentAttestationSubnets = persistentAttestationSubnets; this.syncCommitteeSubnets = syncCommitteeSubnets; - this.dasExtraCustodySubnetCount = dasExtraCustodySubnetCount.orElse(0); + this.dasCustodySubnetCount = dasCustodySubnetCount; } public Bytes getPublicKey() { @@ -71,8 +71,8 @@ public SszBitvector getSyncCommitteeSubnets() { return syncCommitteeSubnets; } - public int getDasExtraCustodySubnetCount() { - return dasExtraCustodySubnetCount; + public Optional getDasCustodySubnetCount() { + return dasCustodySubnetCount; } @Override diff --git a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java index ad0a61df71f..0886f0ad145 100644 --- a/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java +++ b/networking/p2p/src/main/java/tech/pegasys/teku/networking/p2p/discovery/discv5/NodeRecordConverter.java @@ -75,7 +75,7 @@ private static DiscoveryPeer socketAddressToDiscoveryPeer( final SszBitvector syncCommitteeSubnets = parseField(nodeRecord, SYNC_COMMITTEE_SUBNET_ENR_FIELD, syncnetsSchema::fromBytes) .orElse(syncnetsSchema.getDefault()); - final Optional dasExtraCustodySubnetCount = + final Optional dasTotalCustodySubnetCount = parseField( nodeRecord, DAS_CUSTODY_SUBNET_COUNT_ENR_FIELD, @@ -89,7 +89,7 @@ private static DiscoveryPeer socketAddressToDiscoveryPeer( enrForkId, persistentAttestationSubnets, syncCommitteeSubnets, - dasExtraCustodySubnetCount); + dasTotalCustodySubnetCount); } private static Optional parseField( From 4abba1fbd0478916300b80e9ec58648bb082f319 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Thu, 16 May 2024 20:41:07 +0400 Subject: [PATCH 68/70] move all custody subnets to separate option (#56) --- .../tech/pegasys/teku/networking/eth2/P2PConfig.java | 10 +++++++++- .../tech/pegasys/teku/cli/options/P2POptions.java | 11 +++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java index d6c895928d6..a5fa73ac0fc 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/P2PConfig.java @@ -182,6 +182,7 @@ public static class Builder { private GossipEncoding gossipEncoding = GossipEncoding.SSZ_SNAPPY; private Integer targetSubnetSubscriberCount = DEFAULT_P2P_TARGET_SUBNET_SUBSCRIBER_COUNT; private Boolean subscribeAllSubnetsEnabled = DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; + private Boolean subscribeAllCustodySubnetsEnabled = DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; private int dasExtraCustodySubnetCount = DEFAULT_DAS_EXTRA_CUSTODY_SUBNET_COUNT; private Integer peerRateLimit = DEFAULT_PEER_RATE_LIMIT; private Integer peerRequestLimit = DEFAULT_PEER_REQUEST_LIMIT; @@ -219,7 +220,7 @@ public P2PConfig build() { discoveryConfig.listenUdpPortDefault(networkConfig.getListenPort()); discoveryConfig.advertisedUdpPortDefault(OptionalInt.of(networkConfig.getAdvertisedPort())); - if (subscribeAllSubnetsEnabled) { + if (subscribeAllCustodySubnetsEnabled) { dasExtraCustodySubnetCount = Integer.MAX_VALUE; } @@ -288,6 +289,13 @@ public Builder dasExtraCustodySubnetCount(int dasExtraCustodySubnetCount) { return this; } + public Builder subscribeAllCustodySubnetsEnabled( + final Boolean subscribeAllCustodySubnetsEnabled) { + checkNotNull(subscribeAllCustodySubnetsEnabled); + this.subscribeAllCustodySubnetsEnabled = subscribeAllCustodySubnetsEnabled; + return this; + } + public Builder peerRateLimit(final Integer peerRateLimit) { checkNotNull(peerRateLimit); if (peerRateLimit < 0) { diff --git a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java index 55e432376b8..92a51287144 100644 --- a/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java +++ b/teku/src/main/java/tech/pegasys/teku/cli/options/P2POptions.java @@ -222,6 +222,16 @@ public class P2POptions { fallbackValue = "true") private boolean subscribeAllSubnetsEnabled = P2PConfig.DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; + @Option( + names = {"--p2p-subscribe-all-custody-subnets-enabled"}, + paramLabel = "", + showDefaultValue = Visibility.ALWAYS, + description = "", + arity = "0..1", + fallbackValue = "true") + private boolean subscribeAllCustodySubnetsEnabled = + P2PConfig.DEFAULT_SUBSCRIBE_ALL_SUBNETS_ENABLED; + @Option( names = {"--Xp2p-gossip-scoring-enabled"}, paramLabel = "", @@ -350,6 +360,7 @@ public void configure(final TekuConfiguration.Builder builder) { .p2p( b -> b.subscribeAllSubnetsEnabled(subscribeAllSubnetsEnabled) + .subscribeAllCustodySubnetsEnabled(subscribeAllCustodySubnetsEnabled) .batchVerifyMaxThreads(batchVerifyMaxThreads) .batchVerifyQueueCapacity(batchVerifyQueueCapacity) .batchVerifyMaxBatchSize(batchVerifyMaxBatchSize) From bc33599a3da32a6521e1f562d1b7e7d451030500 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 17 May 2024 12:48:12 +0300 Subject: [PATCH 69/70] Implement long polling req/resp getDataColumnByRoot() method (#57) * Implement long polling req/resp getDataColumnByRoot() method * Add DataColumnIdentifier.createFromSidecar() factory method --- .../libp2p/rpc/DataColumnIdentifier.java | 5 + .../schemas/SchemaDefinitionsEip7594.java | 3 +- .../teku/spec/util/DataStructureUtil.java | 5 +- .../DataColumnSidecarCustodyImpl.java | 53 +++++++++- .../LateInitDataColumnSidecarCustody.java | 39 ++++++++ .../DataColumnReqRespBatchingImpl.java | 3 +- .../MappedOperationPoolTest.java | 1 + .../DataColumnSidecarCustodyImplTest.java | 97 +++++++++++++++++++ .../CanonicalBlockResolverStub.java | 49 ++++++++++ .../datacolumns/DataColumnSidecarDBStub.java | 62 ++++++++++++ .../eth2/Eth2P2PNetworkBuilder.java | 11 +++ .../eth2/peers/Eth2PeerManager.java | 5 + .../rpc/beaconchain/BeaconChainMethods.java | 7 +- ...ataColumnSidecarsByRootMessageHandler.java | 8 +- .../DataColumnSidecarsByRootValidator.java | 2 +- .../beaconchain/BeaconChainController.java | 23 +++-- 16 files changed, 352 insertions(+), 21 deletions(-) create mode 100644 ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/LateInitDataColumnSidecarCustody.java create mode 100644 ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImplTest.java create mode 100644 ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/CanonicalBlockResolverStub.java create mode 100644 ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBStub.java diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java index a1d43106c99..5e706a599b4 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/datastructures/networking/libp2p/rpc/DataColumnIdentifier.java @@ -21,6 +21,7 @@ import tech.pegasys.teku.infrastructure.ssz.schema.SszPrimitiveSchemas; import tech.pegasys.teku.infrastructure.ssz.tree.TreeNode; import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; public class DataColumnIdentifier extends Container2 { @@ -42,6 +43,10 @@ public DataColumnIdentifier createFromBackingNode(final TreeNode node) { public static final DataColumnIdentifierSchema SSZ_SCHEMA = new DataColumnIdentifierSchema(); + public static DataColumnIdentifier createFromSidecar(DataColumnSidecar sidecar) { + return new DataColumnIdentifier(sidecar.getBlockRoot(), sidecar.getIndex()); + } + private DataColumnIdentifier(final TreeNode node) { super(SSZ_SCHEMA, node); } diff --git a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java index e5199d4a1ac..6dcc41dbcbb 100644 --- a/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java +++ b/ethereum/spec/src/main/java/tech/pegasys/teku/spec/schemas/SchemaDefinitionsEip7594.java @@ -30,6 +30,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodyBuilder; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBodySchema; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodyBuilderEip7594; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BeaconBlockBodySchemaEip7594Impl; import tech.pegasys.teku.spec.datastructures.blocks.blockbody.versions.eip7594.BlindedBeaconBlockBodySchemaEip7594Impl; import tech.pegasys.teku.spec.datastructures.blocks.versions.deneb.BlockContentsSchema; @@ -159,7 +160,7 @@ public static SchemaDefinitionsEip7594 required(final SchemaDefinitions schemaDe } @Override - public BeaconBlockBodySchema getBeaconBlockBodySchema() { + public BeaconBlockBodySchemaEip7594 getBeaconBlockBodySchema() { return beaconBlockBodySchema; } diff --git a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java index 0eb12ce90ee..9db6dffa6c1 100644 --- a/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java +++ b/ethereum/spec/src/testFixtures/java/tech/pegasys/teku/spec/util/DataStructureUtil.java @@ -1043,10 +1043,13 @@ public BeaconBlock randomBeaconBlock(final long slotNum) { } public BeaconBlock randomBeaconBlock(final UInt64 slotNum) { + return randomBeaconBlock(slotNum, randomBeaconBlockBody(slotNum)); + } + + public BeaconBlock randomBeaconBlock(final UInt64 slotNum, BeaconBlockBody body) { final UInt64 proposerIndex = randomUInt64(); final Bytes32 previousRoot = randomBytes32(); final Bytes32 stateRoot = randomBytes32(); - final BeaconBlockBody body = randomBeaconBlockBody(slotNum); return new BeaconBlock( spec.atSlot(slotNum).getSchemaDefinitions().getBeaconBlockSchema(), diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java index 27782c48be4..50ef08a6719 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImpl.java @@ -15,10 +15,18 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.annotations.VisibleForTesting; +import java.time.Duration; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; @@ -27,6 +35,7 @@ import org.apache.tuweni.units.bigints.UInt256; import tech.pegasys.teku.ethereum.events.SlotEventsChannel; import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.exceptions.ExceptionUtil; import tech.pegasys.teku.infrastructure.unsigned.UInt64; import tech.pegasys.teku.spec.Spec; import tech.pegasys.teku.spec.SpecMilestone; @@ -90,15 +99,20 @@ public boolean isIncomplete() { private final int totalCustodySubnetCount; private final UInt64 eip7594StartEpoch; + private final Duration requestTimeout; private UInt64 currentSlot = null; + @VisibleForTesting + Map>> pendingRequests = new HashMap<>(); + public DataColumnSidecarCustodyImpl( Spec spec, CanonicalBlockResolver blockResolver, DataColumnSidecarDB db, UInt256 nodeId, - int totalCustodySubnetCount) { + int totalCustodySubnetCount, + Duration requestTimeout) { checkNotNull(spec); checkNotNull(blockResolver); @@ -111,6 +125,7 @@ public DataColumnSidecarCustodyImpl( this.nodeId = nodeId; this.totalCustodySubnetCount = totalCustodySubnetCount; this.eip7594StartEpoch = spec.getForkSchedule().getFork(SpecMilestone.EIP7594).getEpoch(); + this.requestTimeout = requestTimeout; } private UInt64 getEarliestCustodySlot(UInt64 currentSlot) { @@ -137,12 +152,24 @@ private List getCustodyColumnsForEpoch(UInt64 epoch) { } @Override - public void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar) { + public synchronized void onNewValidatedDataColumnSidecar(DataColumnSidecar dataColumnSidecar) { if (isMyCustody(dataColumnSidecar.getSlot(), dataColumnSidecar.getIndex())) { db.addSidecar(dataColumnSidecar); + final List> pendingRequests = + this.pendingRequests.remove(DataColumnIdentifier.createFromSidecar(dataColumnSidecar)); + if (pendingRequests != null) { + for (SafeFuture pendingRequest : pendingRequests) { + pendingRequest.complete(dataColumnSidecar); + } + } } } + private synchronized void clearCancelledPendingRequests() { + pendingRequests.values().forEach(promises -> promises.removeIf(CompletableFuture::isDone)); + pendingRequests.entrySet().removeIf(e -> e.getValue().isEmpty()); + } + private boolean isMyCustody(UInt64 slot, UInt64 columnIndex) { UInt64 epoch = spec.computeEpochAtSlot(slot); return spec.atEpoch(epoch) @@ -157,9 +184,27 @@ private boolean isMyCustody(UInt64 slot, UInt64 columnIndex) { } @Override - public SafeFuture> getCustodyDataColumnSidecar( + public synchronized SafeFuture> getCustodyDataColumnSidecar( DataColumnIdentifier columnId) { - return SafeFuture.completedFuture(db.getSidecar(columnId)); + Optional existingColumn = db.getSidecar(columnId); + if (existingColumn.isPresent()) { + return SafeFuture.completedFuture(existingColumn); + } else { + clearCancelledPendingRequests(); + SafeFuture promise = new SafeFuture<>(); + pendingRequests.computeIfAbsent(columnId, __ -> new ArrayList<>()).add(promise); + return promise + .orTimeout(requestTimeout) + .thenApply(Optional::of) + .exceptionally( + err -> { + if (ExceptionUtil.hasCause(err, TimeoutException.class)) { + return Optional.empty(); + } else { + throw new CompletionException(err); + } + }); + } } private void onEpoch(UInt64 epoch) { diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/LateInitDataColumnSidecarCustody.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/LateInitDataColumnSidecarCustody.java new file mode 100644 index 00000000000..8f81c8cb750 --- /dev/null +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/LateInitDataColumnSidecarCustody.java @@ -0,0 +1,39 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Optional; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public class LateInitDataColumnSidecarCustody implements DataColumnSidecarCustody { + private DataColumnSidecarCustody delegate = null; + + public void init(DataColumnSidecarCustody delegate) { + if (this.delegate != null) { + throw new IllegalStateException("Delegate was initialized already"); + } + this.delegate = delegate; + } + + @Override + public SafeFuture> getCustodyDataColumnSidecar( + DataColumnIdentifier columnId) { + if (delegate == null) { + throw new IllegalStateException("Delegate was not initialized"); + } + return delegate.getCustodyDataColumnSidecar(columnId); + } +} diff --git a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java index 8d7cc290400..ee72678572a 100644 --- a/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java +++ b/ethereum/statetransition/src/main/java/tech/pegasys/teku/statetransition/datacolumns/retriever/DataColumnReqRespBatchingImpl.java @@ -85,8 +85,7 @@ private void flushForNode(UInt256 nodeId, List nodeRequests) { nodeRequests.hashCode()); Map byIds = new HashMap<>(); for (DataColumnSidecar sidecar : resp) { - byIds.put( - new DataColumnIdentifier(sidecar.getBlockRoot(), sidecar.getIndex()), sidecar); + byIds.put(DataColumnIdentifier.createFromSidecar(sidecar), sidecar); } for (RequestEntry nodeRequest : nodeRequests) { DataColumnSidecar maybeResponse = byIds.get(nodeRequest.columnIdentifier); diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java index d993cac30f9..1c1753f1af1 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java @@ -24,6 +24,7 @@ import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.ACCEPT; import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.IGNORE; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImplTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImplTest.java new file mode 100644 index 00000000000..7b1a1eacfe6 --- /dev/null +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarCustodyImplTest.java @@ -0,0 +1,97 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.apache.tuweni.units.bigints.UInt256; +import org.junit.jupiter.api.Test; +import tech.pegasys.teku.infrastructure.async.SafeFuture; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.SpecMilestone; +import tech.pegasys.teku.spec.TestSpecFactory; +import tech.pegasys.teku.spec.config.SpecConfigEip7594; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlockHeader; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class DataColumnSidecarCustodyImplTest { + + final Spec spec = TestSpecFactory.createMinimalEip7594(); + final DataColumnSidecarDB db = new DataColumnSidecarDBStub(); + final CanonicalBlockResolverStub blockResolver = new CanonicalBlockResolverStub(spec); + final UInt256 myNodeId = UInt256.ONE; + + final SpecConfigEip7594 config = + SpecConfigEip7594.required(spec.forMilestone(SpecMilestone.EIP7594).getConfig()); + final int subnetCount = config.getDataColumnSidecarSubnetCount(); + final int custodyCount = config.getCustodyRequirement(); + + private final DataStructureUtil dataStructureUtil = new DataStructureUtil(0, spec); + + private DataColumnSidecar createSidecar(BeaconBlock block, int column) { + return dataStructureUtil.randomDataColumnSidecar(createSigned(block), UInt64.valueOf(column)); + } + + private SignedBeaconBlockHeader createSigned(BeaconBlock block) { + return dataStructureUtil.signedBlock(block).asHeader(); + } + + @Test + @SuppressWarnings("JavaCase") + void sanityTest() throws Throwable { + DataColumnSidecarCustodyImpl custody = + new DataColumnSidecarCustodyImpl( + spec, blockResolver, db, myNodeId, subnetCount, Duration.ofMillis(200)); + BeaconBlock block = blockResolver.addBlock(10, true); + DataColumnSidecar sidecar0 = createSidecar(block, 0); + DataColumnIdentifier columnId0 = DataColumnIdentifier.createFromSidecar(sidecar0); + + SafeFuture> fRet1 = custody.getCustodyDataColumnSidecar(columnId0); + Optional ret1 = fRet1.get(1, TimeUnit.SECONDS); + + assertThat(ret1).isEmpty(); + + SafeFuture> fRet2_1 = + custody.getCustodyDataColumnSidecar(columnId0); + SafeFuture> fRet2_2 = + custody.getCustodyDataColumnSidecar(columnId0); + + custody.onNewValidatedDataColumnSidecar(sidecar0); + + assertThat(fRet2_1.get().get()).isEqualTo(sidecar0); + assertThat(fRet2_2.get().get()).isEqualTo(sidecar0); + + SafeFuture> fRet3 = custody.getCustodyDataColumnSidecar(columnId0); + + assertThat(fRet3.get().get()).isEqualTo(sidecar0); + + DataColumnSidecar sidecar1 = createSidecar(block, 1); + DataColumnIdentifier columnId1 = DataColumnIdentifier.createFromSidecar(sidecar1); + + SafeFuture> fRet4 = custody.getCustodyDataColumnSidecar(columnId1); + assertThat(fRet4).isNotDone(); + + custody.onNewValidatedDataColumnSidecar(sidecar1); + assertThat(fRet4.get().get()).isEqualTo(sidecar1); + + assertThat(custody.pendingRequests).isEmpty(); + } +} diff --git a/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/CanonicalBlockResolverStub.java b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/CanonicalBlockResolverStub.java new file mode 100644 index 00000000000..6307f7d8761 --- /dev/null +++ b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/CanonicalBlockResolverStub.java @@ -0,0 +1,49 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.Spec; +import tech.pegasys.teku.spec.datastructures.blocks.BeaconBlock; +import tech.pegasys.teku.spec.datastructures.blocks.blockbody.BeaconBlockBody; +import tech.pegasys.teku.spec.util.DataStructureUtil; + +public class CanonicalBlockResolverStub + implements DataColumnSidecarCustodyImpl.CanonicalBlockResolver { + + private final Map chain = new HashMap<>(); + + private final DataStructureUtil dataStructureUtil; + + public CanonicalBlockResolverStub(Spec spec) { + dataStructureUtil = new DataStructureUtil(0, spec); + } + + public BeaconBlock addBlock(int slot, boolean hasBlobs) { + UInt64 slotU = UInt64.valueOf(slot); + BeaconBlockBody beaconBlockBody = + dataStructureUtil.randomBeaconBlockBodyWithCommitments(hasBlobs ? 1 : 0); + BeaconBlock block = dataStructureUtil.randomBeaconBlock(slotU, beaconBlockBody); + chain.put(slotU, block); + return block; + } + + @Override + public Optional getBlockAtSlot(UInt64 slot) { + return Optional.ofNullable(chain.get(slot)); + } +} diff --git a/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBStub.java b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBStub.java new file mode 100644 index 00000000000..c79ed2c8a56 --- /dev/null +++ b/ethereum/statetransition/src/testFixtures/java/tech/pegasys/teku/statetransition/datacolumns/DataColumnSidecarDBStub.java @@ -0,0 +1,62 @@ +/* + * Copyright Consensys Software Inc., 2024 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ + +package tech.pegasys.teku.statetransition.datacolumns; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Stream; +import tech.pegasys.teku.infrastructure.unsigned.UInt64; +import tech.pegasys.teku.spec.datastructures.blobs.versions.eip7594.DataColumnSidecar; +import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; + +public class DataColumnSidecarDBStub implements DataColumnSidecarDB { + + private Optional firstIncompleteSlot = Optional.empty(); + private Map db = new HashMap<>(); + private Map> slotIds = new HashMap<>(); + + @Override + public void setFirstIncompleteSlot(UInt64 slot) { + this.firstIncompleteSlot = Optional.of(slot); + } + + @Override + public Optional getFirstIncompleteSlot() { + return firstIncompleteSlot; + } + + @Override + public void addSidecar(DataColumnSidecar sidecar) { + DataColumnIdentifier identifier = DataColumnIdentifier.createFromSidecar(sidecar); + db.put(identifier, sidecar); + slotIds.computeIfAbsent(sidecar.getSlot(), __ -> new HashSet<>()).add(identifier); + } + + @Override + public Optional getSidecar(DataColumnIdentifier identifier) { + return Optional.ofNullable(db.get(identifier)); + } + + @Override + public Stream streamColumnIdentifiers(UInt64 slot) { + return slotIds.getOrDefault(slot, Collections.emptySet()).stream(); + } + + @Override + public void pruneAllSidecars(UInt64 tillSlot) {} +} diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java index 6c1f94a19b3..2d7f909a00a 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/Eth2P2PNetworkBuilder.java @@ -89,6 +89,7 @@ import tech.pegasys.teku.spec.datastructures.state.Checkpoint; import tech.pegasys.teku.spec.datastructures.util.ForkAndSpecMilestone; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsSupplier; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.store.KeyValueStore; @@ -105,6 +106,7 @@ public class Eth2P2PNetworkBuilder { protected P2PConfig config; protected EventChannels eventChannels; protected CombinedChainDataClient combinedChainDataClient; + protected DataColumnSidecarCustody dataColumnSidecarCustody; protected OperationProcessor gossipedBlockProcessor; protected OperationProcessor gossipedBlobSidecarProcessor; protected OperationProcessor gossipedAttestationConsumer; @@ -170,6 +172,7 @@ public Eth2P2PNetwork build() { Eth2PeerManager.create( asyncRunner, combinedChainDataClient, + dataColumnSidecarCustody, metricsSystem, attestationSubnetService, syncCommitteeSubnetService, @@ -446,6 +449,7 @@ private void validate() { assertNotNull("eventChannels", eventChannels); assertNotNull("metricsSystem", metricsSystem); assertNotNull("combinedChainDataClient", combinedChainDataClient); + assertNotNull("dataColumnSidecarCustody", dataColumnSidecarCustody); assertNotNull("keyValueStore", keyValueStore); assertNotNull("timeProvider", timeProvider); assertNotNull("gossipedBlockProcessor", gossipedBlockProcessor); @@ -485,6 +489,13 @@ public Eth2P2PNetworkBuilder combinedChainDataClient( return this; } + public Eth2P2PNetworkBuilder dataColumnSidecarCustody( + DataColumnSidecarCustody dataColumnSidecarCustody) { + checkNotNull(dataColumnSidecarCustody); + this.dataColumnSidecarCustody = dataColumnSidecarCustody; + return this; + } + public Eth2P2PNetworkBuilder keyValueStore(final KeyValueStore kvStore) { checkNotNull(kvStore); this.keyValueStore = kvStore; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java index 8da048d2e2b..e4d8b7321e9 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/Eth2PeerManager.java @@ -45,6 +45,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessageSchema; import tech.pegasys.teku.spec.datastructures.state.Checkpoint; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.client.RecentChainData; @@ -72,6 +73,7 @@ public class Eth2PeerManager implements PeerLookup, PeerHandler { final Spec spec, final AsyncRunner asyncRunner, final CombinedChainDataClient combinedChainDataClient, + final DataColumnSidecarCustody dataColumnSidecarCustody, final RecentChainData recentChainData, final MetricsSystem metricsSystem, final Eth2PeerFactory eth2PeerFactory, @@ -91,6 +93,7 @@ public class Eth2PeerManager implements PeerLookup, PeerHandler { asyncRunner, this, combinedChainDataClient, + dataColumnSidecarCustody, recentChainData, metricsSystem, statusMessageFactory, @@ -105,6 +108,7 @@ public class Eth2PeerManager implements PeerLookup, PeerHandler { public static Eth2PeerManager create( final AsyncRunner asyncRunner, final CombinedChainDataClient combinedChainDataClient, + final DataColumnSidecarCustody dataColumnSidecarCustody, final MetricsSystem metricsSystem, final SubnetSubscriptionService attestationSubnetService, final SubnetSubscriptionService syncCommitteeSubnetService, @@ -131,6 +135,7 @@ public static Eth2PeerManager create( spec, asyncRunner, combinedChainDataClient, + dataColumnSidecarCustody, combinedChainDataClient.getRecentChainData(), metricsSystem, new Eth2PeerFactory( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java index 97e9322e345..7f3f2b283fa 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/BeaconChainMethods.java @@ -67,6 +67,7 @@ import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.metadata.MetadataMessage; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsDeneb; import tech.pegasys.teku.spec.schemas.SchemaDefinitionsEip7594; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; import tech.pegasys.teku.storage.client.CombinedChainDataClient; import tech.pegasys.teku.storage.client.RecentChainData; @@ -129,6 +130,7 @@ public static BeaconChainMethods create( final AsyncRunner asyncRunner, final PeerLookup peerLookup, final CombinedChainDataClient combinedChainDataClient, + final DataColumnSidecarCustody dataColumnSidecarCustody, final RecentChainData recentChainData, final MetricsSystem metricsSystem, final StatusMessageFactory statusMessageFactory, @@ -168,6 +170,7 @@ public static BeaconChainMethods create( metricsSystem, asyncRunner, combinedChainDataClient, + dataColumnSidecarCustody, peerLookup, rpcEncoding, recentChainData), @@ -384,6 +387,7 @@ private static Eth2RpcMethod createGoodBye( final MetricsSystem metricsSystem, final AsyncRunner asyncRunner, final CombinedChainDataClient combinedChainDataClient, + final DataColumnSidecarCustody dataColumnSidecarCustody, final PeerLookup peerLookup, final RpcEncoding rpcEncoding, final RecentChainData recentChainData) { @@ -396,7 +400,8 @@ private static Eth2RpcMethod createGoodBye( spec, recentChainData, ForkDigestPayloadContext.DATA_COLUMN_SIDECAR); final DataColumnSidecarsByRootMessageHandler dataColumnSidecarsByRootMessageHandler = - new DataColumnSidecarsByRootMessageHandler(spec, metricsSystem, combinedChainDataClient); + new DataColumnSidecarsByRootMessageHandler( + spec, metricsSystem, combinedChainDataClient, dataColumnSidecarCustody); final DataColumnSidecarsByRootRequestMessageSchema dataColumnSidecarsByRootRequestMessageSchema = SchemaDefinitionsEip7594.required( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java index f9222910501..a32eac17772 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootMessageHandler.java @@ -38,6 +38,7 @@ import tech.pegasys.teku.spec.datastructures.blocks.SignedBeaconBlock; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnIdentifier; import tech.pegasys.teku.spec.datastructures.networking.libp2p.rpc.DataColumnSidecarsByRootRequestMessage; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; import tech.pegasys.teku.storage.client.CombinedChainDataClient; /** @@ -54,6 +55,7 @@ public class DataColumnSidecarsByRootMessageHandler private final Spec spec; private final CombinedChainDataClient combinedChainDataClient; + private final DataColumnSidecarCustody dataColumnSidecarCustody; private final LabelledMetric requestCounter; private final Counter totalDataColumnSidecarsRequestedCounter; @@ -61,7 +63,8 @@ public class DataColumnSidecarsByRootMessageHandler public DataColumnSidecarsByRootMessageHandler( final Spec spec, final MetricsSystem metricsSystem, - final CombinedChainDataClient combinedChainDataClient) { + final CombinedChainDataClient combinedChainDataClient, + final DataColumnSidecarCustody dataColumnSidecarCustody) { this.spec = spec; this.combinedChainDataClient = combinedChainDataClient; requestCounter = @@ -75,6 +78,7 @@ public DataColumnSidecarsByRootMessageHandler( TekuMetricCategory.NETWORK, "rpc_data_column_sidecars_by_root_requested_blob_sidecars_total", "Total number of data column sidecars requested in accepted data column sidecars by root requests from peers"); + this.dataColumnSidecarCustody = dataColumnSidecarCustody; } @Override @@ -197,7 +201,7 @@ private SafeFuture validateMinimumRequestEpoch( private SafeFuture> retrieveDataColumnSidecar( final DataColumnIdentifier identifier) { - return combinedChainDataClient.getSidecar(identifier); + return dataColumnSidecarCustody.getCustodyDataColumnSidecar(identifier); } private void handleError( diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java index 693caf5b1e3..dc0dbea5b60 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/rpc/beaconchain/methods/DataColumnSidecarsByRootValidator.java @@ -50,7 +50,7 @@ public DataColumnSidecarsByRootValidator( public void validate(final DataColumnSidecar dataColumnSidecar) { final DataColumnIdentifier dataColumnIdentifier = - new DataColumnIdentifier(dataColumnSidecar.getBlockRoot(), dataColumnSidecar.getIndex()); + DataColumnIdentifier.createFromSidecar(dataColumnSidecar); if (!expectedDataColumnIdentifiers.remove(dataColumnIdentifier)) { throw new DataColumnSidecarsResponseInvalidResponseException( peer, InvalidResponseType.DATA_COLUMN_SIDECAR_UNEXPECTED_IDENTIFIER); diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index 60a8f3a5d96..c353b15d9db 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -138,12 +138,7 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; -import tech.pegasys.teku.statetransition.datacolumns.DasCustodySync; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustody; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustodyImpl; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarDBImpl; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManager; -import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManagerImpl; +import tech.pegasys.teku.statetransition.datacolumns.*; import tech.pegasys.teku.statetransition.datacolumns.retriever.DasPeerCustodyCountSupplier; import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnPeerSearcher; import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqResp; @@ -295,7 +290,9 @@ public class BeaconChainController extends Service implements BeaconChainControl protected volatile KZG kzg; protected volatile BlobSidecarManager blobSidecarManager; protected volatile DataColumnSidecarManager dataColumnSidecarManager; - protected volatile DataColumnSidecarCustody dataColumnSidecarCustody; + // protected volatile DataColumnSidecarCustody dataColumnSidecarCustody; + protected volatile LateInitDataColumnSidecarCustody dataColumnSidecarCustody = + new LateInitDataColumnSidecarCustody(); protected volatile DasCustodySync dasCustodySync; protected volatile Optional terminalPowBlockMonitor = Optional.empty(); protected volatile ProposersDataManager proposersDataManager; @@ -646,12 +643,19 @@ protected void initDasCustody() { DataColumnSidecarCustodyImpl dataColumnSidecarCustodyImpl = new DataColumnSidecarCustodyImpl( - spec, blockRootResolver, sidecarDB, nodeId, totalMyCustodySubnets); + spec, + blockRootResolver, + sidecarDB, + nodeId, + totalMyCustodySubnets, + Duration.ofSeconds(5)); eventChannels.subscribe(SlotEventsChannel.class, dataColumnSidecarCustodyImpl); eventChannels.subscribe(FinalizedCheckpointChannel.class, dataColumnSidecarCustodyImpl); dataColumnSidecarManager.subscribeToValidDataColumnSidecars( dataColumnSidecarCustodyImpl::onNewValidatedDataColumnSidecar); - this.dataColumnSidecarCustody = dataColumnSidecarCustodyImpl; + // TODO fix this dirty hack + // This is to resolve the initialization loop Network <--> DAS Custody + this.dataColumnSidecarCustody.init(dataColumnSidecarCustodyImpl); DataColumnPeerManagerImpl dasPeerManager = new DataColumnPeerManagerImpl(); p2pNetwork.subscribeConnect(dasPeerManager); @@ -1222,6 +1226,7 @@ protected void initP2PNetwork() { .config(beaconConfig.p2pConfig()) .eventChannels(eventChannels) .combinedChainDataClient(combinedChainDataClient) + .dataColumnSidecarCustody(dataColumnSidecarCustody) .gossipedBlockProcessor(blockManager::validateAndImportBlock) .gossipedBlobSidecarProcessor(blobSidecarManager::validateAndPrepareForBlockImport) .gossipedDataColumnSidecarOperationProcessor( From 0295ebdafc173db228f7997e623f81013d810c36 Mon Sep 17 00:00:00 2001 From: Dmitrii Shmatko Date: Fri, 17 May 2024 17:17:08 +0300 Subject: [PATCH 70/70] Build fix + minor name fixes --- .../teku/statetransition/MappedOperationPoolTest.java | 1 - .../eth2/peers/GossipTopicDasPeerCustodyTracker.java | 10 +++++----- .../services/beaconchain/BeaconChainController.java | 7 ++++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java index 1c1753f1af1..d993cac30f9 100644 --- a/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java +++ b/ethereum/statetransition/src/test/java/tech/pegasys/teku/statetransition/MappedOperationPoolTest.java @@ -24,7 +24,6 @@ import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.ACCEPT; import static tech.pegasys.teku.statetransition.validation.InternalValidationResult.IGNORE; -import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; diff --git a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java index fc20135fe78..2c9b070a9f6 100644 --- a/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java +++ b/networking/eth2/src/main/java/tech/pegasys/teku/networking/eth2/peers/GossipTopicDasPeerCustodyTracker.java @@ -46,7 +46,7 @@ public class GossipTopicDasPeerCustodyTracker private final GossipEncoding gossipEncoding; private final Supplier> currentForkInfoSupplier; - private final Map connectedPeerExtraSubnets = new ConcurrentHashMap<>(); + private final Map connectedPeerSubnets = new ConcurrentHashMap<>(); public GossipTopicDasPeerCustodyTracker( Spec spec, @@ -66,14 +66,14 @@ public GossipTopicDasPeerCustodyTracker( @Override public void onConnected(Eth2Peer peer) { - connectedPeerExtraSubnets.put( + connectedPeerSubnets.put( peer.getDiscoveryNodeId(), new Entry(peer.getId(), NO_SUBNET_COUNT_INFO)); peer.subscribeDisconnect((__, ___) -> peerDisconnected(peer)); refreshExistingSubscriptions(); } private void peerDisconnected(Eth2Peer peer) { - connectedPeerExtraSubnets.remove(peer.getDiscoveryNodeId()); + connectedPeerSubnets.remove(peer.getDiscoveryNodeId()); } private Set getCurrentDasTopics() { @@ -98,7 +98,7 @@ record NodeTopic(NodeId nodeId, String topic) {} entry.getValue().stream().map(nodeId -> new NodeTopic(nodeId, entry.getKey()))) .filter(entry -> dasTopics.contains(entry.topic())) .collect(Collectors.groupingBy(NodeTopic::nodeId, Collectors.counting())); - connectedPeerExtraSubnets.replaceAll( + connectedPeerSubnets.replaceAll( (nodeId, entry) -> { Long maybeCount = nodeToSubnetCount.get(entry.libp2pPeerId()); int count = maybeCount == null ? NO_SUBNET_COUNT_INFO : maybeCount.intValue(); @@ -108,7 +108,7 @@ record NodeTopic(NodeId nodeId, String topic) {} @Override public int getCustodyCountForPeer(UInt256 nodeId) { - Entry entry = connectedPeerExtraSubnets.get(nodeId); + Entry entry = connectedPeerSubnets.get(nodeId); return entry != null ? entry.subnetCount() : 0; } diff --git a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java index c353b15d9db..97730e25788 100644 --- a/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java +++ b/services/beaconchain/src/main/java/tech/pegasys/teku/services/beaconchain/BeaconChainController.java @@ -138,7 +138,12 @@ import tech.pegasys.teku.statetransition.block.BlockManager; import tech.pegasys.teku.statetransition.block.FailedExecutionPool; import tech.pegasys.teku.statetransition.block.ReceivedBlockEventsChannel; -import tech.pegasys.teku.statetransition.datacolumns.*; +import tech.pegasys.teku.statetransition.datacolumns.DasCustodySync; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarCustodyImpl; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarDBImpl; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManager; +import tech.pegasys.teku.statetransition.datacolumns.DataColumnSidecarManagerImpl; +import tech.pegasys.teku.statetransition.datacolumns.LateInitDataColumnSidecarCustody; import tech.pegasys.teku.statetransition.datacolumns.retriever.DasPeerCustodyCountSupplier; import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnPeerSearcher; import tech.pegasys.teku.statetransition.datacolumns.retriever.DataColumnReqResp;