From 293966497105d39d58745c98f0c4a2dab831d708 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 10 Sep 2024 15:15:25 +0200 Subject: [PATCH 01/16] chore: remove hapiProtoVersion (#15399) Signed-off-by: Jendrik Johannes --- hedera-node/hedera-app/build.gradle.kts | 5 ++++- .../hedera-network-admin-service-impl/build.gradle.kts | 2 +- settings.gradle.kts | 4 ---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/hedera-node/hedera-app/build.gradle.kts b/hedera-node/hedera-app/build.gradle.kts index e8ea30e9bf45..44776cc290de 100644 --- a/hedera-node/hedera-app/build.gradle.kts +++ b/hedera-node/hedera-app/build.gradle.kts @@ -165,7 +165,10 @@ val cleanRun = tasks.clean { dependsOn(cleanRun) } -tasks.register("showHapiVersion") { doLast { println(libs.versions.hapi.proto.get()) } } +tasks.register("showHapiVersion") { + inputs.property("version", project.version) + doLast { println(inputs.properties["version"]) } +} var updateDockerEnvTask = tasks.register("updateDockerEnv") { diff --git a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts index ef0b562a944b..07590e13359a 100644 --- a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts @@ -23,7 +23,7 @@ description = "Default Hedera Network Admin Service Implementation" val writeSemanticVersionProperties = tasks.register("writeSemanticVersionProperties") { - property("hapi.proto.version", libs.versions.hapi.proto.get()) + property("hapi.proto.version", project.version) property("hedera.services.version", project.version) destinationFile.set( diff --git a/settings.gradle.kts b/settings.gradle.kts index 1af074a0ae1f..015ca3a49c55 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -86,15 +86,11 @@ javaModules { versions("hedera-dependency-versions") } -// The HAPI API version to use for Protobuf sources. -val hapiProtoVersion = "0.54.0" - dependencyResolutionManagement { // Protobuf tool versions versionCatalogs.create("libs") { version("google-proto", "3.25.4") version("grpc-proto", "1.66.0") - version("hapi-proto", hapiProtoVersion) plugin("pbj", "com.hedera.pbj.pbj-compiler").version("0.9.2") } From eba7d6931b01bf59cb0b1ca1fb98b57aaf683c72 Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Tue, 10 Sep 2024 11:08:10 -0500 Subject: [PATCH 02/16] chore: implement and test indirect block proofs (#15387) Signed-off-by: Michael Tinker --- .../main/java/com/hedera/node/app/Hedera.java | 51 ++- .../node/app/HederaInjectionComponent.java | 9 + .../com/hedera/node/app/ServicesMain.java | 4 +- .../node/app/blocks/BlockStreamManager.java | 21 +- .../node/app/blocks/BlockStreamService.java | 23 +- .../node/app/blocks/impl/BlockImplUtils.java | 16 + .../blocks/impl/BlockStreamManagerImpl.java | 229 ++++++---- .../schemas/V0540BlockStreamSchema.java | 45 +- .../hedera/node/app/tss/TssBaseService.java | 61 +++ .../tss/impl/PlaceholderTssBaseService.java | 94 ++++ .../hedera-app/src/main/java/module-info.java | 3 + .../app/blocks/BlockStreamServiceTest.java | 85 ++-- .../impl/BlockStreamManagerImplTest.java | 432 ++++++++++++++++++ .../schemas/V0540BlockStreamSchemaTest.java | 116 +++-- .../app/components/IngestComponentTest.java | 5 + .../node/app/tss/TssBaseServiceTest.java | 32 ++ .../impl/PlaceholderTssBaseServiceTest.java | 90 ++++ .../services/bdd/junit/RepeatableReason.java | 4 + .../embedded/AbstractEmbeddedHedera.java | 11 +- .../junit/hedera/embedded/EmbeddedHedera.java | 8 +- .../embedded/fakes/FakeTssBaseService.java | 106 +++++ .../bdd/junit/support/BlockStreamAccess.java | 19 +- .../junit/support/BlockStreamValidator.java | 8 +- .../junit/support/RecordStreamValidator.java | 2 +- .../bdd/junit/support/StreamDataListener.java | 8 + ...treamAccess.java => StreamFileAccess.java} | 86 ++-- ...java => StreamFileAlterationListener.java} | 22 +- .../support/translators/BaseTranslator.java | 38 +- .../BlockTransactionalUnitTranslator.java | 3 +- .../impl/FileUpdateTranslator.java | 61 +++ .../TransactionRecordParityValidator.java | 8 +- .../hedera/services/bdd/spec/HapiSpec.java | 88 ++-- .../bdd/spec/utilops/EmbeddedVerbs.java | 20 +- .../services/bdd/spec/utilops/TssVerbs.java | 49 ++ .../services/bdd/spec/utilops/UtilVerbs.java | 38 +- .../spec/utilops/records/SnapshotModeOp.java | 8 +- .../streams/EventualAssertionResult.java | 9 +- .../utilops/streams/StreamValidationOp.java | 13 +- .../AbstractEventualStreamAssertion.java | 87 ++++ .../assertions/BlockStreamAssertion.java | 49 ++ .../streams/assertions/EventualAssertion.java | 39 -- .../EventualBlockStreamAssertion.java | 111 +++++ .../EventualRecordStreamAssertion.java | 99 +--- .../assertions/IndirectProofsAssertion.java | 73 +++ .../assertions/RecordStreamAssertion.java | 3 +- .../traceability/SidecarWatcher.java | 8 +- .../suites/compose/PerpetualLocalCalls.java | 104 ----- .../contract/hapi/ContractCallSuite.java | 18 +- .../ContractKeysStillWorkAsExpectedSuite.java | 33 +- .../hip993/NaturalDispatchOrderingTest.java | 16 +- .../suites/hip993/SystemFileExportsTest.java | 18 +- .../bdd/suites/tss/RepeatableTssTests.java | 70 +++ 52 files changed, 2031 insertions(+), 622 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseService.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseService.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceTest.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseServiceTest.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java rename hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/{RecordStreamAccess.java => StreamFileAccess.java} (79%) rename hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/{BroadcastingRecordStreamListener.java => StreamFileAlterationListener.java} (82%) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/impl/FileUpdateTranslator.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/AbstractEventualStreamAssertion.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/BlockStreamAssertion.java delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualAssertion.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualBlockStreamAssertion.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/IndirectProofsAssertion.java delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/tss/RepeatableTssTests.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java index f06d69f3c321..2f936bddc0aa 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/Hedera.java @@ -16,6 +16,7 @@ package com.hedera.node.app; +import static com.hedera.node.app.blocks.BlockStreamService.FAKE_RESTART_BLOCK_HASH; import static com.hedera.node.app.info.UnavailableNetworkInfo.UNAVAILABLE_NETWORK_INFO; import static com.hedera.node.app.records.schemas.V0490BlockRecordSchema.BLOCK_INFO_STATE_KEY; import static com.hedera.node.app.state.merkle.VersionUtils.isSoOrdered; @@ -36,6 +37,7 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.node.state.blockrecords.BlockInfo; import com.hedera.hapi.util.HapiUtils; +import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamService; import com.hedera.node.app.blocks.impl.BoundaryStateChangeListener; import com.hedera.node.app.blocks.impl.KVStateChangeListener; @@ -69,6 +71,8 @@ import com.hedera.node.app.statedumpers.MerkleStateChild; import com.hedera.node.app.store.ReadableStoreFactory; import com.hedera.node.app.throttle.CongestionThrottleService; +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.node.app.tss.impl.PlaceholderTssBaseService; import com.hedera.node.app.version.HederaSoftwareVersion; import com.hedera.node.app.version.ServicesSoftwareVersion; import com.hedera.node.app.workflows.handle.HandleWorkflow; @@ -190,6 +194,11 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener { */ private final InstantSource instantSource; + /** + * The supplier for the TSS base service. + */ + private final Supplier tssBaseServiceSupplier; + /** * The contract service singleton, kept as a field here to avoid constructing twice * (once in constructor to register schemas, again inside Dagger component). @@ -202,6 +211,12 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener { */ private final FileServiceImpl fileServiceImpl; + /** + * The block stream service singleton, kept as a field here to reuse information learned + * during the state migration phase in the later initialization phase. + */ + private final BlockStreamService blockStreamService; + /** * The bootstrap configuration provider for the network. */ @@ -268,14 +283,17 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener { * @param constructableRegistry the registry to register {@link RuntimeConstructable} factories with * @param registryFactory the factory to use for creating the services registry * @param migrator the migrator to use with the services + * @param tssBaseServiceSupplier the supplier for the TSS base service */ public Hedera( @NonNull final ConstructableRegistry constructableRegistry, @NonNull final ServicesRegistry.Factory registryFactory, @NonNull final ServiceMigrator migrator, - @NonNull final InstantSource instantSource) { + @NonNull final InstantSource instantSource, + @NonNull final Supplier tssBaseServiceSupplier) { requireNonNull(registryFactory); requireNonNull(constructableRegistry); + this.tssBaseServiceSupplier = requireNonNull(tssBaseServiceSupplier); this.serviceMigrator = requireNonNull(migrator); this.instantSource = requireNonNull(instantSource); logger.info( @@ -306,6 +324,7 @@ public Hedera( new SignatureExpanderImpl(), new SignatureVerifierImpl(CryptographyHolder.get()))); contractServiceImpl = new ContractServiceImpl(appContext); + blockStreamService = new BlockStreamService(bootstrapConfig); // Register all service schema RuntimeConstructable factories before platform init Set.of( new EntityIdService(), @@ -318,7 +337,7 @@ public Hedera( new UtilServiceImpl(), new RecordCacheService(), new BlockRecordService(), - new BlockStreamService(bootstrapConfig), + blockStreamService, new FeeService(), new CongestionThrottleService(), new NetworkServiceImpl(), @@ -775,6 +794,7 @@ private void initializeDagger( @NonNull final InitTrigger trigger, @NonNull final List migrationStateChanges) { final var notifications = platform.getNotificationEngine(); + final var blockStreamEnabled = isBlockStreamEnabled(); // The Dagger component should be constructed every time we reach this point, even if // it exists (this avoids any problems with mutable singleton state by reconstructing // everything); but we must ensure the gRPC server in the old component is fully stopped, @@ -784,6 +804,9 @@ private void initializeDagger( notifications.unregister(PlatformStatusChangeListener.class, this); notifications.unregister(ReconnectCompleteListener.class, daggerApp.reconnectListener()); notifications.unregister(StateWriteToDiskCompleteListener.class, daggerApp.stateWriteToDiskListener()); + if (blockStreamEnabled) { + daggerApp.tssBaseService().unregisterLedgerSignatureConsumer(daggerApp.blockStreamManager()); + } } // Fully qualified so as to not confuse javadoc daggerApp = com.hedera.node.app.DaggerHederaInjectionComponent.builder() @@ -804,12 +827,36 @@ private void initializeDagger( .kvStateChangeListener(kvStateChangeListener) .boundaryStateChangeListener(boundaryStateChangeListener) .migrationStateChanges(migrationStateChanges) + .tssBaseService(tssBaseServiceSupplier.get()) .build(); // Initialize infrastructure for fees, exchange rates, and throttles from the working state daggerApp.initializer().accept(state); notifications.register(PlatformStatusChangeListener.class, this); notifications.register(ReconnectCompleteListener.class, daggerApp.reconnectListener()); notifications.register(StateWriteToDiskCompleteListener.class, daggerApp.stateWriteToDiskListener()); + if (blockStreamEnabled) { + daggerApp + .blockStreamManager() + .initLastBlockHash( + switch (trigger) { + case GENESIS -> BlockStreamManager.ZERO_BLOCK_HASH; + // FUTURE - get the actual last block hash from e.g. a reconnect teacher or disk + default -> blockStreamService + .migratedLastBlockHash() + .orElse(FAKE_RESTART_BLOCK_HASH); + }); + daggerApp.tssBaseService().registerLedgerSignatureConsumer(daggerApp.blockStreamManager()); + if (daggerApp.tssBaseService() instanceof PlaceholderTssBaseService placeholderTssBaseService) { + daggerApp.inject(placeholderTssBaseService); + } + } + } + + private boolean isBlockStreamEnabled() { + return bootstrapConfigProvider + .getConfiguration() + .getConfigData(BlockStreamConfig.class) + .streamBlocks(); } private static ServicesSoftwareVersion getNodeStartupVersion(@NonNull final Configuration config) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java index d956e8fd7936..6895096b58a7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/HederaInjectionComponent.java @@ -47,6 +47,8 @@ import com.hedera.node.app.state.WorkingStateAccessor; import com.hedera.node.app.throttle.ThrottleServiceManager; import com.hedera.node.app.throttle.ThrottleServiceModule; +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.node.app.tss.impl.PlaceholderTssBaseService; import com.hedera.node.app.workflows.FacilityInitModule; import com.hedera.node.app.workflows.WorkflowsInjectionModule; import com.hedera.node.app.workflows.handle.HandleWorkflow; @@ -132,6 +134,10 @@ public interface HederaInjectionComponent { StoreMetricsService storeMetricsService(); + TssBaseService tssBaseService(); + + void inject(PlaceholderTssBaseService placeholderTssBaseService); + @Component.Builder interface Builder { @BindsInstance @@ -185,6 +191,9 @@ interface Builder { @BindsInstance Builder migrationStateChanges(List migrationStateChanges); + @BindsInstance + Builder tssBaseService(TssBaseService tssBaseService); + HederaInjectionComponent build(); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java index 459128029091..aeea283a7a0d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java @@ -30,6 +30,7 @@ import com.hedera.node.app.services.OrderedServiceMigrator; import com.hedera.node.app.services.ServicesRegistryImpl; +import com.hedera.node.app.tss.impl.PlaceholderTssBaseService; import com.swirlds.base.time.Time; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.RuntimeConstructable; @@ -301,6 +302,7 @@ private static Hedera newHedera() { ConstructableRegistry.getInstance(), ServicesRegistryImpl::new, new OrderedServiceMigrator(), - InstantSource.system()); + InstantSource.system(), + PlaceholderTssBaseService::new); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java index 6f156bfd349c..6b052a3aaf0d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamManager.java @@ -22,6 +22,7 @@ import com.swirlds.platform.system.Round; import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.BiConsumer; /** * Maintains the state and process objects needed to produce the block stream. @@ -34,11 +35,22 @@ * Items written to the stream will be produced in the order they are written. The leaves of the input and output item * Merkle trees will be in the order they are written. */ -public interface BlockStreamManager extends BlockRecordInfo { +public interface BlockStreamManager extends BlockRecordInfo, BiConsumer { + Bytes ZERO_BLOCK_HASH = Bytes.wrap(new byte[48]); + + /** + * Initializes the block stream manager after a restart with the hash of the last block incorporated + * in the state used in the restart. If the restart was from genesis, this hash should be the + * {@link #ZERO_BLOCK_HASH}. + * @param blockHash the hash of the last block + */ + void initLastBlockHash(@NonNull Bytes blockHash); + /** * Updates the internal state of the block stream manager to reflect the start of a new round. * @param round the round that has just started * @param state the state of the network at the beginning of the round + * @throws IllegalStateException if the last block hash was not explicitly initialized */ void startRound(@NonNull Round round, @NonNull State state); @@ -57,11 +69,4 @@ public interface BlockStreamManager extends BlockRecordInfo { * @throws IllegalStateException if the stream is closed */ void writeItem(@NonNull BlockItem item); - - /** - * Completes the block proof for the given block with the given signature. - * @param blockNumber the number of the block to finish - * @param signature the signature to use in the block proof - */ - void finishBlockProof(long blockNumber, @NonNull Bytes signature); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamService.java index c9ea329603a6..91eb6ed62279 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/BlockStreamService.java @@ -21,20 +21,28 @@ import com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema; import com.hedera.node.config.data.BlockStreamConfig; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import com.swirlds.state.spi.SchemaRegistry; import com.swirlds.state.spi.Service; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.Optional; /** * Service for BlockStreams implementation responsible for tracking state changes * and writing them to a block */ public class BlockStreamService implements Service { + public static final Bytes FAKE_RESTART_BLOCK_HASH = Bytes.fromHex("abcd".repeat(24)); + public static final String NAME = "BlockStreamService"; private final boolean enabled; + @Nullable + private Bytes migratedLastBlockHash; + /** * Service constructor. */ @@ -52,7 +60,20 @@ public String getServiceName() { public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); if (enabled) { - registry.register(new V0540BlockStreamSchema()); + registry.register(new V0540BlockStreamSchema(this::setMigratedLastBlockHash)); } } + + /** + * Returns the last block hash as migrated from a state that used record streams, or empty + * if there was no such hash observed during migration. + * @return the last block hash + */ + public Optional migratedLastBlockHash() { + return Optional.ofNullable(migratedLastBlockHash); + } + + private void setMigratedLastBlockHash(@NonNull final Bytes migratedLastBlockHash) { + this.migratedLastBlockHash = requireNonNull(migratedLastBlockHash); + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockImplUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockImplUtils.java index 834aaa8ce3e2..050bc0e045ca 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockImplUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockImplUtils.java @@ -215,6 +215,22 @@ public static Bytes appendHash(@NonNull final Bytes hash, @NonNull final Bytes h return Bytes.wrap(newBytes); } + /** + * Hashes the given left and right hashes. + * @param leftHash the left hash + * @param rightHash the right hash + * @return the combined hash + */ + public static Bytes combine(@NonNull final Bytes leftHash, @NonNull final Bytes rightHash) { + return Bytes.wrap(combine(leftHash.toByteArray(), rightHash.toByteArray())); + } + + /** + * Hashes the given left and right hashes. + * @param leftHash the left hash + * @param rightHash the right hash + * @return the combined hash + */ public static byte[] combine(final byte[] leftHash, final byte[] rightHash) { try { final var digest = MessageDigest.getInstance(DigestType.SHA_384.algorithmName()); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImpl.java index ebf4e495684f..8211b2fe68a7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImpl.java @@ -17,6 +17,8 @@ package com.hedera.node.app.blocks.impl; import static com.hedera.hapi.node.base.BlockHashAlgorithm.SHA2_384; +import static com.hedera.hapi.util.HapiUtils.asInstant; +import static com.hedera.node.app.blocks.impl.BlockImplUtils.appendHash; import static com.hedera.node.app.blocks.impl.BlockImplUtils.combine; import static com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema.BLOCK_STREAM_INFO_KEY; import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; @@ -28,25 +30,27 @@ import com.hedera.hapi.block.stream.BlockItem; import com.hedera.hapi.block.stream.BlockProof; +import com.hedera.hapi.block.stream.MerkleSiblingHash; import com.hedera.hapi.block.stream.output.BlockHeader; import com.hedera.hapi.block.stream.output.TransactionResult; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; +import com.hedera.hapi.platform.state.PlatformState; import com.hedera.node.app.blocks.BlockItemWriter; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.BlockStreamService; import com.hedera.node.app.blocks.StreamingTreeHasher; import com.hedera.node.app.records.impl.BlockRecordInfoUtils; +import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.BlockRecordStreamConfig; import com.hedera.node.config.data.BlockStreamConfig; -import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.VersionConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import com.swirlds.platform.state.service.PlatformStateService; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; +import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.Round; import com.swirlds.state.State; import com.swirlds.state.spi.CommittableWritableStates; @@ -69,26 +73,25 @@ public class BlockStreamManagerImpl implements BlockStreamManager { private static final Logger log = LogManager.getLogger(BlockStreamManagerImpl.class); - private static final Bytes MOCK_HASH = Bytes.wrap(new byte[48]); private static final int CHUNK_SIZE = 8; private static final CompletableFuture MOCK_START_STATE_ROOT_HASH_FUTURE = completedFuture(Bytes.wrap(new byte[48])); private final int roundsPerBlock; + private final TssBaseService tssBaseService; private final SemanticVersion hapiVersion; - private final SemanticVersion nodeVersion; private final ExecutorService executor; - private final BlockHashManager blockHashManager; - private final RunningHashManager runningHashManager; private final Supplier writerSupplier; private final BoundaryStateChangeListener boundaryStateChangeListener; - // All this state is scoped to producing the block for the last-started round + private final BlockHashManager blockHashManager; + private final RunningHashManager runningHashManager; + + // All this state is scoped to producing the current block private long blockNumber; // Set to the round number of the last round handled before entering a freeze period private long freezeRoundNumber = -1; - // FUTURE - initialize to the actual last block hash (this is only correct at genesis) - private Bytes lastBlockHash = Bytes.wrap(new byte[48]); + private Bytes lastBlockHash; private Instant blockTimestamp; private BlockItemWriter writer; private List pendingItems; @@ -101,15 +104,24 @@ public class BlockStreamManagerImpl implements BlockStreamManager { */ private CompletableFuture writeFuture = completedFuture(null); + // (FUTURE) Remove this once reconnect protocol also transmits the last block hash + private boolean appendRealHashes = false; + /** * Represents a block pending completion by the block hash signature needed for its block proof. * - * @param blockNumber the block number + * @param number the block number + * @param blockHash the block hash * @param proofBuilder the block proof builder * @param writer the block item writer + * @param siblingHashes the sibling hashes needed for an indirect block proof of an earlier block */ private record PendingBlock( - long blockNumber, @NonNull BlockProof.Builder proofBuilder, @NonNull BlockItemWriter writer) {} + long number, + @NonNull Bytes blockHash, + @NonNull BlockProof.Builder proofBuilder, + @NonNull BlockItemWriter writer, + @NonNull MerkleSiblingHash... siblingHashes) {} /** * A queue of blocks pending completion by the block hash signature needed for their block proofs. @@ -121,23 +133,36 @@ public BlockStreamManagerImpl( @NonNull final Supplier writerSupplier, @NonNull final ExecutorService executor, @NonNull final ConfigProvider configProvider, + @NonNull final TssBaseService tssBaseService, @NonNull final BoundaryStateChangeListener boundaryStateChangeListener) { this.writerSupplier = requireNonNull(writerSupplier); this.executor = requireNonNull(executor); + this.tssBaseService = requireNonNull(tssBaseService); this.boundaryStateChangeListener = requireNonNull(boundaryStateChangeListener); - final var config = requireNonNull(configProvider).getConfiguration(); + requireNonNull(configProvider); + final var config = configProvider.getConfiguration(); this.hapiVersion = hapiVersionFrom(config); - this.nodeVersion = nodeVersionFrom(config); this.roundsPerBlock = config.getConfigData(BlockStreamConfig.class).roundsPerBlock(); this.blockHashManager = new BlockHashManager(config); this.runningHashManager = new RunningHashManager(); } + @Override + public void initLastBlockHash(@NonNull final Bytes blockHash) { + lastBlockHash = requireNonNull(blockHash); + } + @Override public void startRound(@NonNull final Round round, @NonNull final State state) { - // We will always close the block at the end of the freeze round, even if - // its number would not otherwise trigger a block closing - if (isFreezeRound(state, round)) { + if (lastBlockHash == null) { + throw new IllegalStateException("Last block hash must be initialized before starting a round"); + } + final var platformState = state.getReadableStates(PlatformStateService.NAME) + .getSingleton(V0540PlatformStateSchema.PLATFORM_STATE_KEY) + .get(); + requireNonNull(platformState); + if (isFreezeRound(platformState, round)) { + // Track freeze round numbers because they always end a block freezeRoundNumber = round.getRoundNum(); } if (writer == null) { @@ -159,7 +184,7 @@ public void startRound(@NonNull final Round round, @NonNull final State state) { .number(blockNumber) .previousBlockHash(lastBlockHash) .hashAlgorithm(SHA2_384) - .softwareVersion(nodeVersion) + .softwareVersion(platformState.creationSoftwareVersionOrThrow()) .hapiProtoVersion(hapiVersion)) .build()); @@ -172,7 +197,8 @@ public void endRound(@NonNull final State state, final long roundNum) { if (shouldCloseBlock(roundNum, roundsPerBlock)) { final var writableState = state.getWritableStates(BlockStreamService.NAME); final var blockStreamInfoState = writableState.getSingleton(BLOCK_STREAM_INFO_KEY); - // Ensure all runningHashManager futures are complete + // Ensure runningHashManager futures include all result items and are completed + schedulePendingWork(); writeFuture.join(); // Commit the block stream info to state before flushing the boundary state changes blockStreamInfoState.put(new BlockStreamInfo( @@ -184,53 +210,28 @@ public void endRound(@NonNull final State state, final long roundNum) { schedulePendingWork(); writeFuture.join(); - final var inputRootHash = inputTreeHasher.rootHash().join(); - final var outputRootHash = outputTreeHasher.rootHash().join(); + final var inputHash = inputTreeHasher.rootHash().join(); + final var outputHash = outputTreeHasher.rootHash().join(); final var blockStartStateHash = MOCK_START_STATE_ROOT_HASH_FUTURE.join(); - final var blockHash = computeBlockHash(lastBlockHash, inputRootHash, outputRootHash, blockStartStateHash); - // FUTURE: sign the block hash and gossip our signature - - final var blockProofBuilder = BlockProof.newBuilder() + final var leftParent = combine(lastBlockHash, inputHash); + final var rightParent = combine(outputHash, blockStartStateHash); + final var blockHash = combine(leftParent, rightParent); + final var pendingProof = BlockProof.newBuilder() .block(blockNumber) .previousBlockRootHash(lastBlockHash) .startOfBlockStateRootHash(blockStartStateHash); - pendingBlocks.add(new PendingBlock(blockNumber, blockProofBuilder, writer)); + pendingBlocks.add(new PendingBlock( + blockNumber, + blockHash, + pendingProof, + writer, + new MerkleSiblingHash(false, inputHash), + new MerkleSiblingHash(false, rightParent))); // Update in-memory state to prepare for the next block lastBlockHash = blockHash; writer = null; - // Simulate the completion of the block proof - final long blockNumberToComplete = this.blockNumber; - CompletableFuture.runAsync( - () -> { - try { - finishBlockProof(blockNumberToComplete, Bytes.wrap(new byte[48])); - } catch (Exception e) { - log.error("Failed to finish proof for block {}", blockNumberToComplete, e); - } - }, - executor); - } - } - - /** - * {@inheritDoc} - * Synchronized to ensure that block proofs are always written in order, even in edge cases where multiple - * pending block proofs become available at the same time. - * @param blockNumber the number of the block to finish - * @param signature the signature to use in the block proof - */ - @Override - public synchronized void finishBlockProof(final long blockNumber, @NonNull final Bytes signature) { - requireNonNull(signature); - while (!pendingBlocks.isEmpty() && pendingBlocks.peek().blockNumber() <= blockNumber) { - final var block = pendingBlocks.poll(); - // Note the actual proof for an earlier block number awaiting proof will be more complicated than this - final var proof = block.proofBuilder().blockSignature(signature).build(); - block.writer() - .writeItem(BlockItem.PROTOBUF.toBytes( - BlockItem.newBuilder().blockProof(proof).build())) - .closeBlock(); + tssBaseService.requestLedgerSignature(blockHash.toByteArray()); } } @@ -244,6 +245,10 @@ public void writeItem(@NonNull final BlockItem item) { @Override public @Nullable Bytes prngSeed() { + // Incorporate all pending results before returning the seed to guarantee + // no two consecutive transactions ever get the same seed + schedulePendingWork(); + writeFuture.join(); final var seed = runningHashManager.nMinus3HashFuture.join(); return seed == null ? null : Bytes.wrap(seed); } @@ -263,6 +268,59 @@ public long blockNo() { return blockHashManager.hashOfBlock(blockNo); } + /** + * Synchronized to ensure that block proofs are always written in order, even in edge cases where multiple + * pending block proofs become available at the same time. + * + * @param message the number of the block to finish + * @param signature the signature to use in the block proof + */ + @Override + public synchronized void accept(@NonNull final byte[] message, @NonNull final byte[] signature) { + // Find the block whose hash as the signed message, tracking any sibling hashes + // needed for indirect proofs of earlier blocks along the way + long blockNumber = Long.MIN_VALUE; + boolean impliesIndirectProof = false; + final List> siblingHashes = new ArrayList<>(); + final var blockHash = Bytes.wrap(message); + for (final var block : pendingBlocks) { + if (impliesIndirectProof) { + siblingHashes.add(List.of(block.siblingHashes())); + } + if (block.blockHash().equals(blockHash)) { + blockNumber = block.number(); + break; + } + impliesIndirectProof = true; + } + if (blockNumber == Long.MIN_VALUE) { + log.info("Ignoring signature on already proven block hash '{}'", blockHash); + return; + } + // Write proofs for all pending blocks up to and including the signed block number + final var blockSignature = Bytes.wrap(signature); + while (!pendingBlocks.isEmpty() && pendingBlocks.peek().number() <= blockNumber) { + final var block = pendingBlocks.poll(); + final var proof = block.proofBuilder() + .blockSignature(blockSignature) + .siblingHashes(siblingHashes.stream().flatMap(List::stream).toList()); + block.writer() + .writeItem(BlockItem.PROTOBUF.toBytes( + BlockItem.newBuilder().blockProof(proof).build())) + .closeBlock(); + if (block.number() != blockNumber) { + siblingHashes.removeFirst(); + } + } + } + + /** + * (FUTURE) Remove this after reconnect protocol also transmits the last block hash. + */ + public void appendRealHashes() { + this.appendRealHashes = true; + } + private void schedulePendingWork() { final var scheduledWork = new ScheduledWork(pendingItems); final var pendingSerialization = CompletableFuture.supplyAsync(scheduledWork::serializeItems, executor); @@ -270,16 +328,6 @@ private void schedulePendingWork() { pendingItems = new ArrayList<>(); } - private Bytes computeBlockHash( - @NonNull final Bytes prevBlockHash, - @NonNull final Bytes inputRootHash, - @NonNull final Bytes outputRootHash, - @NonNull final Bytes stateRootHash) { - final var leftParent = combine(prevBlockHash.toByteArray(), inputRootHash.toByteArray()); - final var rightParent = combine(outputRootHash.toByteArray(), stateRootHash.toByteArray()); - return Bytes.wrap(combine(leftParent, rightParent)); - } - private @NonNull BlockStreamInfo blockStreamInfoFrom(@NonNull final State state) { final var blockStreamInfoState = state.getReadableStates(BlockStreamService.NAME).getSingleton(BLOCK_STREAM_INFO_KEY); @@ -290,10 +338,11 @@ private boolean shouldCloseBlock(final long roundNumber, final int roundsPerBloc return roundNumber % roundsPerBlock == 0 || roundNumber == freezeRoundNumber; } - private boolean isFreezeRound(@NonNull final State state, @NonNull final Round round) { - final var platformState = new ReadablePlatformStateStore(state.getReadableStates(PlatformStateService.NAME)); + private boolean isFreezeRound(@NonNull final PlatformState platformState, @NonNull final Round round) { return isInFreezePeriod( - round.getConsensusTimestamp(), platformState.getFreezeTime(), platformState.getLastFrozenTime()); + round.getConsensusTimestamp(), + platformState.freezeTime() == null ? null : asInstant(platformState.freezeTime()), + platformState.lastFrozenTime() == null ? null : asInstant(platformState.lastFrozenTime())); } /** @@ -361,18 +410,6 @@ public Void combineSerializedItems(@Nullable Void ignore, @NonNull final List + *
  • We never know the hash of the {@code N+1} block currently being created.
  • + *
  • We start every block {@code N} by concatenating the {@code N-1} block hash to the trailing + * hashes up to block {@code N-2} that were in state at the end of block {@code N-1}. + * * * @param blockNo the block number * @return the hash of the block with the given number, or null if it is not available diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchema.java index 2f248cf545d6..5659885d460b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchema.java @@ -17,6 +17,7 @@ package com.hedera.node.app.blocks.schemas; import static com.hedera.node.app.blocks.impl.BlockImplUtils.appendHash; +import static com.hedera.node.app.records.impl.BlockRecordInfoUtils.blockHashByBlockNumber; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.SemanticVersion; @@ -30,25 +31,20 @@ import com.swirlds.state.spi.StateDefinition; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.Set; +import java.util.function.Consumer; /** - * Defines the schema for two forms of state, + * Defines the schema for state with two notable properties: *
      - *
    1. State needed for a new or reconnected node to construct the next block exactly as will + *
    2. It is needed for a new or reconnected node to construct the next block exactly as will * nodes already in the network.
    3. - *
    4. State derived from the block stream, and hence the natural provenance of the same service + *
    5. It is derived from the block stream, and hence the natural provenance of the same service * that is managing and producing blocks.
    6. *
    *

    - * The two pieces of state in the first category are, + * The particular items with these properties are, *

      *
    1. The number of the last completed block, which each node must increment in the next block.
    2. - *
    3. The hash of the last completed block, which each node must include in the header and proof - * of the next block.
    4. - *
    - *

    - * State in the second category has three parts, - *

      *
    1. The first consensus time of the last finished block, for comparison with the consensus * time at the start of the current block. Depending on the elapsed period between these times, * the network may deterministically choose to purge expired entities, adjust node stakes and @@ -69,11 +65,14 @@ public class V0540BlockStreamSchema extends Schema { private static final SemanticVersion VERSION = SemanticVersion.newBuilder().major(0).minor(54).patch(0).build(); + private final Consumer migratedBlockHashConsumer; + /** * Schema constructor. */ - public V0540BlockStreamSchema() { + public V0540BlockStreamSchema(@NonNull final Consumer migratedBlockHashConsumer) { super(VERSION); + this.migratedBlockHashConsumer = requireNonNull(migratedBlockHashConsumer); } @Override @@ -94,10 +93,22 @@ public void migrate(@NonNull final MigrationContext ctx) { (BlockInfo) requireNonNull(ctx.sharedValues().get(SHARED_BLOCK_RECORD_INFO)); final RunningHashes runningHashes = (RunningHashes) requireNonNull(ctx.sharedValues().get(SHARED_RUNNING_HASHES)); + // Note that it is impossible to put the hash of block N into a state that includes + // the state changes from block N, because the hash of block N is a function of exactly + // those state changes---so act of putting the hash in state would change it; as a result, + // the correct way to migrate from a record stream-based state is to save its last + // block hash as the last block hash of the new state; and create a BlockStreamInfo with + // the remaining block hashes + final var lastBlockHash = + requireNonNull(blockHashByBlockNumber(blockInfo, blockInfo.lastBlockNumber())); + migratedBlockHashConsumer.accept(lastBlockHash); + final var trailingBlockHashes = blockInfo + .blockHashes() + .slice(lastBlockHash.length(), blockInfo.blockHashes().length() - lastBlockHash.length()); state.put(BlockStreamInfo.newBuilder() .blockTime(blockInfo.firstConsTimeOfLastBlock()) .blockNumber(blockInfo.lastBlockNumber()) - .trailingBlockHashes(blockInfo.blockHashes()) + .trailingBlockHashes(trailingBlockHashes) .trailingOutputHashes(appendedHashes(runningHashes)) .build()); } @@ -105,10 +116,10 @@ public void migrate(@NonNull final MigrationContext ctx) { } private Bytes appendedHashes(final RunningHashes runningHashes) { - Bytes appendedHashes = Bytes.EMPTY; - appendedHashes = appendHash(runningHashes.nMinus3RunningHash(), appendedHashes, 4); - appendedHashes = appendHash(runningHashes.nMinus2RunningHash(), appendedHashes, 4); - appendedHashes = appendHash(runningHashes.nMinus1RunningHash(), appendedHashes, 4); - return appendHash(runningHashes.runningHash(), appendedHashes, 4); + var hashes = Bytes.EMPTY; + hashes = appendHash(runningHashes.nMinus3RunningHash(), hashes, 4); + hashes = appendHash(runningHashes.nMinus2RunningHash(), hashes, 4); + hashes = appendHash(runningHashes.nMinus1RunningHash(), hashes, 4); + return appendHash(runningHashes.runningHash(), hashes, 4); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseService.java new file mode 100644 index 000000000000..b131df78c200 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseService.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.tss; + +import com.swirlds.state.spi.Service; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.BiConsumer; + +/** + * The TssBaseService will attempt to generate TSS key material for any set candidate roster, giving it a ledger id and + * the ability to generate ledger signatures that can be verified by the ledger id. Once the candidate roster has + * received its full TSS key material, it can be made available for adoption by the platform. + *

      + * The TssBaseService will also attempt to generate ledger signatures by aggregating share signatures produced by + * calling {@link #requestLedgerSignature(byte[])}. + */ +public interface TssBaseService extends Service { + String NAME = "TssBaseService"; + + @NonNull + @Override + default String getServiceName() { + return NAME; + } + + /** + * Requests a ledger signature on a message hash. The ledger signature is computed asynchronously and returned + * to all consumers that have been registered through {@link #registerLedgerSignatureConsumer}. + * + * @param messageHash The hash of the message to be signed by the ledger. + */ + void requestLedgerSignature(@NonNull byte[] messageHash); + + /** + * Registers a consumer of the message hash and the ledger signature on the message hash. + * + * @param consumer the consumer of ledger signatures and message hashes. + */ + void registerLedgerSignatureConsumer(@NonNull BiConsumer consumer); + + /** + * Unregisters a consumer of the message hash and the ledger signature on the message hash. + * + * @param consumer the consumer of ledger signatures and message hashes to unregister. + */ + void unregisterLedgerSignatureConsumer(@NonNull BiConsumer consumer); +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseService.java new file mode 100644 index 000000000000..6c0017c91a1d --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseService.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.tss.impl; + +import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.tss.TssBaseService; +import com.swirlds.common.utility.CommonUtils; +import com.swirlds.state.spi.SchemaRegistry; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ExecutorService; +import java.util.function.BiConsumer; +import javax.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Placeholder for the TSS base service, added to support testing production of indirect block proofs, + * c.f. this issue. + */ +public class PlaceholderTssBaseService implements TssBaseService { + private static final Logger log = LogManager.getLogger(PlaceholderTssBaseService.class); + + /** + * Copy-on-write list to avoid concurrent modification exceptions if a consumer unregisters + * itself in its callback. + */ + private final List> consumers = new CopyOnWriteArrayList<>(); + + private ExecutorService executor; + + @Inject + public void setExecutor(@NonNull final ExecutorService executor) { + this.executor = requireNonNull(executor); + } + + @Override + public void registerSchemas(@NonNull final SchemaRegistry registry) { + // FUTURE - add required schemas + } + + @Override + public void requestLedgerSignature(@NonNull final byte[] messageHash) { + requireNonNull(messageHash); + requireNonNull(executor); + // The "signature" is a hash of the message hash + final var mockSignature = noThrowSha384HashOf(messageHash); + // Simulate asynchronous completion of the ledger signature + CompletableFuture.runAsync( + () -> consumers.forEach(consumer -> { + try { + consumer.accept(messageHash, mockSignature); + } catch (Exception e) { + log.error( + "Failed to provide signature {} on message {} to consumer {}", + CommonUtils.hex(mockSignature), + CommonUtils.hex(messageHash), + consumer, + e); + } + }), + executor); + } + + @Override + public void registerLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { + requireNonNull(consumer); + consumers.add(consumer); + } + + @Override + public void unregisterLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { + requireNonNull(consumer); + consumers.remove(consumer); + } +} diff --git a/hedera-node/hedera-app/src/main/java/module-info.java b/hedera-node/hedera-app/src/main/java/module-info.java index dc8c5c2fcab5..930afde3c8e0 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -41,6 +41,7 @@ requires com.swirlds.merkledb; requires com.swirlds.virtualmap; requires com.google.common; + requires com.google.errorprone.annotations; requires com.google.protobuf; requires io.grpc.netty; requires io.grpc; @@ -106,6 +107,8 @@ exports com.hedera.node.app.blocks.impl; exports com.hedera.node.app.workflows.handle.metric; exports com.hedera.node.app.roster; + exports com.hedera.node.app.tss; + exports com.hedera.node.app.tss.impl; provides ConfigurationExtension with ServicesConfigExtension; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/BlockStreamServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/BlockStreamServiceTest.java index 28e6cc7d06e0..d164fc526ab2 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/BlockStreamServiceTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/BlockStreamServiceTest.java @@ -16,83 +16,62 @@ package com.hedera.node.app.blocks; -import static com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema.BLOCK_STREAM_INFO_KEY; +import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoInteractions; -import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; import com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.swirlds.config.api.Configuration; -import com.swirlds.state.spi.MigrationContext; -import com.swirlds.state.spi.Schema; import com.swirlds.state.spi.SchemaRegistry; -import com.swirlds.state.spi.StateDefinition; -import com.swirlds.state.spi.WritableSingletonState; -import com.swirlds.state.spi.WritableStates; -import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -@SuppressWarnings({"rawtypes", "unchecked"}) @ExtendWith(MockitoExtension.class) final class BlockStreamServiceTest { - @Mock(strictness = LENIENT) + @Mock private SchemaRegistry schemaRegistry; - @Mock(strictness = LENIENT) - private MigrationContext migrationContext; + private BlockStreamService subject; - @Mock(strictness = LENIENT) - private WritableSingletonState blockStreamState; - - @Mock(strictness = LENIENT) - private WritableStates writableStates; + @Test + void serviceNameAsExpected() { + givenDisabledSubject(); - public static final Configuration DEFAULT_CONFIG = HederaTestConfigBuilder.createConfig(); + assertThat(subject.getServiceName()).isEqualTo("BlockStreamService"); + } @Test - void testGetServiceName() { - BlockStreamService blockRecordService = new BlockStreamService(DEFAULT_CONFIG); - assertEquals(BlockStreamService.NAME, blockRecordService.getServiceName()); + void enabledSubjectRegistersV0540Schema() { + givenEnabledSubject(); + + subject.registerSchemas(schemaRegistry); + + verify(schemaRegistry).register(argThat(s -> s instanceof V0540BlockStreamSchema)); } @Test - void testRegisterSchemas() { - when(schemaRegistry.register(any())).then(invocation -> { - Object[] args = invocation.getArguments(); - assertEquals(1, args.length); - Schema schema = (Schema) args[0]; - assertThat(schema).isInstanceOf(V0540BlockStreamSchema.class); - Set states = schema.statesToCreate(DEFAULT_CONFIG); - assertEquals(1, states.size()); - assertTrue(states.contains(StateDefinition.singleton(BLOCK_STREAM_INFO_KEY, BlockStreamInfo.PROTOBUF))); - - when(migrationContext.newStates()).thenReturn(writableStates); - when(migrationContext.previousVersion()).thenReturn(null); - when(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)).thenReturn(blockStreamState); - - // FINISH: - ArgumentCaptor blockInfoCapture = ArgumentCaptor.forClass(BlockStreamInfo.class); - - schema.migrate(migrationContext); - - verify(blockStreamState).put(blockInfoCapture.capture()); - assertEquals(BlockStreamInfo.DEFAULT, blockInfoCapture.getValue()); - return null; - }); + void disabledSubjectDoesNotRegisterSchema() { + givenDisabledSubject(); + + subject.registerSchemas(schemaRegistry); + + verifyNoInteractions(schemaRegistry); + + assertThat(subject.migratedLastBlockHash()).isEmpty(); + } + + private void givenEnabledSubject() { final var testConfig = HederaTestConfigBuilder.create() .withValue("blockStream.streamMode", "BOTH") .getOrCreateConfig(); - BlockStreamService blockStreamService = new BlockStreamService(testConfig); - blockStreamService.registerSchemas(schemaRegistry); + subject = new BlockStreamService(testConfig); + } + + private void givenDisabledSubject() { + subject = new BlockStreamService(DEFAULT_CONFIG); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java new file mode 100644 index 000000000000..723c40667b4f --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/impl/BlockStreamManagerImplTest.java @@ -0,0 +1,432 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.blocks.impl; + +import static com.hedera.hapi.util.HapiUtils.asTimestamp; +import static com.hedera.node.app.blocks.BlockStreamManager.ZERO_BLOCK_HASH; +import static com.hedera.node.app.blocks.BlockStreamService.FAKE_RESTART_BLOCK_HASH; +import static com.hedera.node.app.blocks.impl.BlockImplUtils.appendHash; +import static com.hedera.node.app.blocks.impl.BlockImplUtils.combine; +import static com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema.BLOCK_STREAM_INFO_KEY; +import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; +import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema.PLATFORM_STATE_KEY; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.withSettings; + +import com.hedera.hapi.block.stream.BlockItem; +import com.hedera.hapi.block.stream.RecordFileItem; +import com.hedera.hapi.block.stream.output.StateChanges; +import com.hedera.hapi.block.stream.output.TransactionResult; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; +import com.hedera.hapi.platform.event.EventTransaction; +import com.hedera.hapi.platform.state.PlatformState; +import com.hedera.node.app.blocks.BlockItemWriter; +import com.hedera.node.app.blocks.BlockStreamService; +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.node.config.ConfigProvider; +import com.hedera.node.config.VersionedConfigImpl; +import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.ParseException; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.state.service.PlatformStateService; +import com.swirlds.platform.system.Round; +import com.swirlds.state.State; +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.ReadableStates; +import com.swirlds.state.spi.WritableSingletonStateBase; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Instant; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class BlockStreamManagerImplTest { + private static final SemanticVersion CREATION_VERSION = new SemanticVersion(1, 2, 3, "alpha.1", "2"); + private static final long ROUND_NO = 123L; + private static final long N_MINUS_2_BLOCK_NO = 664L; + private static final long N_MINUS_1_BLOCK_NO = 665L; + private static final long N_BLOCK_NO = 666L; + private static final Instant CONSENSUS_NOW = Instant.ofEpochSecond(1_234_567L); + private static final Bytes N_MINUS_2_BLOCK_HASH = Bytes.wrap(noThrowSha384HashOf(new byte[] {(byte) 0xAA})); + private static final Bytes FIRST_FAKE_SIGNATURE = Bytes.fromHex("ff".repeat(48)); + private static final Bytes SECOND_FAKE_SIGNATURE = Bytes.fromHex("ee".repeat(48)); + private static final BlockItem FAKE_EVENT_TRANSACTION = + BlockItem.newBuilder().eventTransaction(EventTransaction.DEFAULT).build(); + private static final BlockItem FAKE_TRANSACTION_RESULT = + BlockItem.newBuilder().transactionResult(TransactionResult.DEFAULT).build(); + private static final Bytes FAKE_RESULT_HASH = noThrowSha384HashOfItem(FAKE_TRANSACTION_RESULT); + private static final BlockItem FAKE_STATE_CHANGES = + BlockItem.newBuilder().stateChanges(StateChanges.DEFAULT).build(); + private static final BlockItem FAKE_RECORD_FILE_ITEM = + BlockItem.newBuilder().recordFile(RecordFileItem.DEFAULT).build(); + + @Mock + private TssBaseService tssBaseService; + + @Mock + private ConfigProvider configProvider; + + @Mock + private BoundaryStateChangeListener boundaryStateChangeListener; + + @Mock + private BlockItemWriter aWriter; + + @Mock + private BlockItemWriter bWriter; + + @Mock + private ReadableStates readableStates; + + private WritableStates writableStates; + + @Mock + private Round round; + + @Mock + private State state; + + private final AtomicReference lastAItem = new AtomicReference<>(); + private final AtomicReference lastBItem = new AtomicReference<>(); + private final AtomicReference stateRef = new AtomicReference<>(); + private final AtomicReference infoRef = new AtomicReference<>(); + + private WritableSingletonStateBase blockStreamInfoState; + + private BlockStreamManagerImpl subject; + + @BeforeEach + void setUp() { + writableStates = mock(WritableStates.class, withSettings().extraInterfaces(CommittableWritableStates.class)); + } + + @Test + void requiresLastHashToBeInitialized() { + given(configProvider.getConfiguration()).willReturn(new VersionedConfigImpl(DEFAULT_CONFIG, 1)); + subject = new BlockStreamManagerImpl( + () -> aWriter, ForkJoinPool.commonPool(), configProvider, tssBaseService, boundaryStateChangeListener); + assertThrows(IllegalStateException.class, () -> subject.startRound(round, state)); + } + + @Test + void startsAndEndsBlockWithSingleRoundPerBlockAsExpected() throws ParseException { + givenSubjectWith( + 1, + blockStreamInfoWith(N_MINUS_1_BLOCK_NO, N_MINUS_2_BLOCK_HASH, Bytes.EMPTY), + platformStateWith(null), + aWriter); + givenEndOfRoundSetup(); + final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); + + // Initialize the last (N-1) block hash + subject.initLastBlockHash(FAKE_RESTART_BLOCK_HASH); + + // Start the round that will be block N + subject.startRound(round, state); + + // Assert the internal state of the subject has changed as expected and the writer has been opened + verify(boundaryStateChangeListener).setLastUsedConsensusTime(CONSENSUS_NOW); + verify(aWriter).openBlock(N_BLOCK_NO); + assertEquals(N_MINUS_2_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_2_BLOCK_NO)); + assertEquals(FAKE_RESTART_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_1_BLOCK_NO)); + assertNull(subject.prngSeed()); + assertEquals(N_BLOCK_NO, subject.blockNo()); + + // Write some items to the block + subject.writeItem(FAKE_EVENT_TRANSACTION); + subject.writeItem(FAKE_TRANSACTION_RESULT); + subject.writeItem(FAKE_STATE_CHANGES); + subject.writeItem(FAKE_RECORD_FILE_ITEM); + + // End the round + subject.endRound(state, ROUND_NO); + + // Assert the internal state of the subject has changed as expected and the writer has been closed + final var expectedBlockInfo = new BlockStreamInfo( + N_BLOCK_NO, + asTimestamp(CONSENSUS_NOW), + appendHash(combine(ZERO_BLOCK_HASH, FAKE_RESULT_HASH), appendHash(ZERO_BLOCK_HASH, Bytes.EMPTY, 4), 4), + appendHash(FAKE_RESTART_BLOCK_HASH, appendHash(N_MINUS_2_BLOCK_HASH, Bytes.EMPTY, 256), 256)); + final var actualBlockInfo = infoRef.get(); + assertEquals(expectedBlockInfo, actualBlockInfo); + verify(tssBaseService).requestLedgerSignature(blockHashCaptor.capture()); + + // Provide the ledger signature to the subject + subject.accept(blockHashCaptor.getValue(), FIRST_FAKE_SIGNATURE.toByteArray()); + + // Assert the block proof was written + final var proofItem = lastAItem.get(); + assertNotNull(proofItem); + final var item = BlockItem.PROTOBUF.parse(proofItem); + assertTrue(item.hasBlockProof()); + final var proof = item.blockProofOrThrow(); + assertEquals(N_BLOCK_NO, proof.block()); + assertEquals(FIRST_FAKE_SIGNATURE, proof.blockSignature()); + } + + @Test + void doesNotEndBlockWithMultipleRoundPerBlockIfNotModZero() { + givenSubjectWith( + 7, + blockStreamInfoWith(N_MINUS_1_BLOCK_NO, N_MINUS_2_BLOCK_HASH, Bytes.EMPTY), + platformStateWith(null), + aWriter); + + // Initialize the last (N-1) block hash + subject.initLastBlockHash(FAKE_RESTART_BLOCK_HASH); + + // Start the round that will be block N + subject.startRound(round, state); + + // Assert the internal state of the subject has changed as expected and the writer has been opened + verify(boundaryStateChangeListener).setLastUsedConsensusTime(CONSENSUS_NOW); + verify(aWriter).openBlock(N_BLOCK_NO); + assertEquals(N_MINUS_2_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_2_BLOCK_NO)); + assertEquals(FAKE_RESTART_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_1_BLOCK_NO)); + + // Write some items to the block + subject.writeItem(FAKE_EVENT_TRANSACTION); + subject.writeItem(FAKE_TRANSACTION_RESULT); + subject.writeItem(FAKE_STATE_CHANGES); + subject.writeItem(FAKE_RECORD_FILE_ITEM); + + // End the round + subject.endRound(state, ROUND_NO); + + // Assert the internal state of the subject has changed as expected and the writer has been closed + verify(tssBaseService, never()).requestLedgerSignature(any()); + } + + @Test + void alwaysEndsBlockOnFreezeRoundPerBlockAsExpected() throws ParseException { + final var resultHashes = Bytes.fromHex("aa".repeat(48) + "bb".repeat(48) + "cc".repeat(48) + "dd".repeat(48)); + givenSubjectWith( + 7, + blockStreamInfoWith(N_MINUS_1_BLOCK_NO, N_MINUS_2_BLOCK_HASH, resultHashes), + platformStateWith(CONSENSUS_NOW.minusSeconds(1)), + aWriter); + givenEndOfRoundSetup(); + given(round.getRoundNum()).willReturn(ROUND_NO); + final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); + + // Initialize the last (N-1) block hash + subject.initLastBlockHash(FAKE_RESTART_BLOCK_HASH); + + // Start the round that will be block N + subject.startRound(round, state); + + // Assert the internal state of the subject has changed as expected and the writer has been opened + verify(boundaryStateChangeListener).setLastUsedConsensusTime(CONSENSUS_NOW); + verify(aWriter).openBlock(N_BLOCK_NO); + assertEquals(N_MINUS_2_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_2_BLOCK_NO)); + assertEquals(FAKE_RESTART_BLOCK_HASH, subject.blockHashByBlockNumber(N_MINUS_1_BLOCK_NO)); + assertEquals(N_BLOCK_NO, subject.blockNo()); + + // Write some items to the block + subject.writeItem(FAKE_EVENT_TRANSACTION); + assertEquals(Bytes.fromHex("aa".repeat(48)), subject.prngSeed()); + subject.writeItem(FAKE_TRANSACTION_RESULT); + assertEquals(Bytes.fromHex("bb".repeat(48)), subject.prngSeed()); + subject.writeItem(FAKE_STATE_CHANGES); + for (int i = 0; i < 8; i++) { + subject.writeItem(FAKE_RECORD_FILE_ITEM); + } + + // End the round + subject.endRound(state, ROUND_NO); + + // Assert the internal state of the subject has changed as expected and the writer has been closed + final var expectedBlockInfo = new BlockStreamInfo( + N_BLOCK_NO, + asTimestamp(CONSENSUS_NOW), + appendHash(combine(Bytes.fromHex("dd".repeat(48)), FAKE_RESULT_HASH), resultHashes, 4), + appendHash(FAKE_RESTART_BLOCK_HASH, appendHash(N_MINUS_2_BLOCK_HASH, Bytes.EMPTY, 256), 256)); + final var actualBlockInfo = infoRef.get(); + assertEquals(expectedBlockInfo, actualBlockInfo); + verify(tssBaseService).requestLedgerSignature(blockHashCaptor.capture()); + + // Provide the ledger signature to the subject + subject.accept(blockHashCaptor.getValue(), FIRST_FAKE_SIGNATURE.toByteArray()); + + // Assert the block proof was written + final var proofItem = lastAItem.get(); + assertNotNull(proofItem); + final var item = BlockItem.PROTOBUF.parse(proofItem); + assertTrue(item.hasBlockProof()); + final var proof = item.blockProofOrThrow(); + assertEquals(N_BLOCK_NO, proof.block()); + assertEquals(FIRST_FAKE_SIGNATURE, proof.blockSignature()); + } + + @Test + void supportsMultiplePendingBlocksWithIndirectProofAsExpected() throws ParseException { + givenSubjectWith( + 1, + blockStreamInfoWith(N_MINUS_1_BLOCK_NO, N_MINUS_2_BLOCK_HASH, Bytes.EMPTY), + platformStateWith(null), + aWriter, + bWriter); + givenEndOfRoundSetup(); + doAnswer(invocationOnMock -> { + lastBItem.set(invocationOnMock.getArgument(0)); + return bWriter; + }) + .when(bWriter) + .writeItem(any()); + final ArgumentCaptor blockHashCaptor = ArgumentCaptor.forClass(byte[].class); + + // Initialize the last (N-1) block hash + subject.initLastBlockHash(FAKE_RESTART_BLOCK_HASH); + + // Start the round that will be block N + subject.startRound(round, state); + // Write some items to the block + subject.writeItem(FAKE_EVENT_TRANSACTION); + subject.writeItem(FAKE_TRANSACTION_RESULT); + subject.writeItem(FAKE_STATE_CHANGES); + subject.writeItem(FAKE_RECORD_FILE_ITEM); + // End the round in block N + subject.endRound(state, ROUND_NO); + + // Start the round that will be block N+1 + subject.startRound(round, state); + // Write some items to the block + subject.writeItem(FAKE_EVENT_TRANSACTION); + subject.writeItem(FAKE_TRANSACTION_RESULT); + subject.writeItem(FAKE_STATE_CHANGES); + subject.writeItem(FAKE_RECORD_FILE_ITEM); + // End the round in block N+1 + subject.endRound(state, ROUND_NO + 1); + + verify(tssBaseService, times(2)).requestLedgerSignature(blockHashCaptor.capture()); + final var allBlockHashes = blockHashCaptor.getAllValues(); + assertEquals(2, allBlockHashes.size()); + + // Provide the N+1 ledger signature to the subject first + subject.accept(allBlockHashes.getLast(), FIRST_FAKE_SIGNATURE.toByteArray()); + subject.accept(allBlockHashes.getFirst(), SECOND_FAKE_SIGNATURE.toByteArray()); + + // Assert both block proofs were written, but with the proof for N using an indirect proof + final var aProofItem = lastAItem.get(); + assertNotNull(aProofItem); + final var aItem = BlockItem.PROTOBUF.parse(aProofItem); + assertTrue(aItem.hasBlockProof()); + final var aProof = aItem.blockProofOrThrow(); + assertEquals(N_BLOCK_NO, aProof.block()); + assertEquals(FIRST_FAKE_SIGNATURE, aProof.blockSignature()); + assertEquals(2, aProof.siblingHashes().size()); + // And the proof for N+1 using a direct proof + final var bProofItem = lastBItem.get(); + assertNotNull(bProofItem); + final var bItem = BlockItem.PROTOBUF.parse(bProofItem); + assertTrue(bItem.hasBlockProof()); + final var bProof = bItem.blockProofOrThrow(); + assertEquals(N_BLOCK_NO + 1, bProof.block()); + assertEquals(FIRST_FAKE_SIGNATURE, bProof.blockSignature()); + assertTrue(bProof.siblingHashes().isEmpty()); + } + + private void givenSubjectWith( + final int roundsPerBlock, + @NonNull final BlockStreamInfo blockStreamInfo, + @NonNull final PlatformState platformState, + @NonNull final BlockItemWriter... writers) { + given(round.getConsensusTimestamp()).willReturn(CONSENSUS_NOW); + final AtomicInteger nextWriter = new AtomicInteger(0); + final var config = HederaTestConfigBuilder.create() + .withValue("blockStream.roundsPerBlock", roundsPerBlock) + .getOrCreateConfig(); + given(configProvider.getConfiguration()).willReturn(new VersionedConfigImpl(config, 1L)); + subject = new BlockStreamManagerImpl( + () -> writers[nextWriter.getAndIncrement()], + ForkJoinPool.commonPool(), + configProvider, + tssBaseService, + boundaryStateChangeListener); + subject.appendRealHashes(); + given(state.getReadableStates(BlockStreamService.NAME)).willReturn(readableStates); + given(state.getReadableStates(PlatformStateService.NAME)).willReturn(readableStates); + infoRef.set(blockStreamInfo); + stateRef.set(platformState); + blockStreamInfoState = new WritableSingletonStateBase<>(BLOCK_STREAM_INFO_KEY, infoRef::get, infoRef::set); + given(readableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) + .willReturn(blockStreamInfoState); + given(readableStates.getSingleton(PLATFORM_STATE_KEY)) + .willReturn(new WritableSingletonStateBase<>(PLATFORM_STATE_KEY, stateRef::get, stateRef::set)); + } + + private void givenEndOfRoundSetup() { + given(boundaryStateChangeListener.flushChanges()).willReturn(FAKE_STATE_CHANGES); + doAnswer(invocationOnMock -> { + lastAItem.set(invocationOnMock.getArgument(0)); + return aWriter; + }) + .when(aWriter) + .writeItem(any()); + given(state.getWritableStates(BlockStreamService.NAME)).willReturn(writableStates); + given(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) + .willReturn(blockStreamInfoState); + doAnswer(invocationOnMock -> { + blockStreamInfoState.commit(); + return null; + }) + .when((CommittableWritableStates) writableStates) + .commit(); + } + + private BlockStreamInfo blockStreamInfoWith( + final long blockNumber, @NonNull final Bytes nMinus2Hash, @NonNull final Bytes resultHashes) { + return BlockStreamInfo.newBuilder() + .blockNumber(blockNumber) + .trailingBlockHashes(appendHash(nMinus2Hash, Bytes.EMPTY, 256)) + .trailingOutputHashes(resultHashes) + .build(); + } + + private PlatformState platformStateWith(@Nullable final Instant freezeTime) { + return PlatformState.newBuilder() + .creationSoftwareVersion(CREATION_VERSION) + .freezeTime(freezeTime == null ? null : asTimestamp(freezeTime)) + .build(); + } + + private static Bytes noThrowSha384HashOfItem(@NonNull final BlockItem item) { + return Bytes.wrap(noThrowSha384HashOf(BlockItem.PROTOBUF.toBytes(item).toByteArray())); + } +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchemaTest.java index 7371a94270f3..2679d1b72ed9 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchemaTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0540BlockStreamSchemaTest.java @@ -17,75 +17,121 @@ package com.hedera.node.app.blocks.schemas; import static com.hedera.node.app.blocks.schemas.V0540BlockStreamSchema.BLOCK_STREAM_INFO_KEY; +import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mock.Strictness.LENIENT; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoInteractions; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.base.Timestamp; +import com.hedera.hapi.node.state.blockrecords.BlockInfo; +import com.hedera.hapi.node.state.blockrecords.RunningHashes; import com.hedera.hapi.node.state.blockstream.BlockStreamInfo; -import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.swirlds.config.api.Configuration; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.MigrationContext; -import com.swirlds.state.spi.StateDefinition; import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; -import java.util.Set; +import java.util.Map; +import java.util.function.Consumer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) public class V0540BlockStreamSchemaTest { - @Mock(strictness = LENIENT) - private MigrationContext mockCtx; + @Mock + private MigrationContext migrationContext; - @Mock(strictness = LENIENT) - private WritableSingletonState mockBlockStreamInfo; + @Mock + private WritableStates writableStates; - @Mock(strictness = LENIENT) - private WritableStates mockWritableStates; + @Mock + private Consumer migratedBlockHashConsumer; - public static final Configuration DEFAULT_CONFIG = HederaTestConfigBuilder.createConfig(); + @Mock + private WritableSingletonState state; - private V0540BlockStreamSchema schema; + private V0540BlockStreamSchema subject; @BeforeEach void setUp() { - schema = new V0540BlockStreamSchema(); - when(mockCtx.newStates()).thenReturn(mockWritableStates); - when(mockWritableStates.getSingleton(BLOCK_STREAM_INFO_KEY)).thenReturn(mockBlockStreamInfo); + subject = new V0540BlockStreamSchema(migratedBlockHashConsumer); } @Test - void testVersion() { - assertEquals(0, schema.getVersion().major()); - assertEquals(54, schema.getVersion().minor()); - assertEquals(0, schema.getVersion().patch()); + void versionIsV0540() { + assertEquals(new SemanticVersion(0, 54, 0, "", ""), subject.getVersion()); } @Test - void testStatesToCreate() { - Set statesToCreate = schema.statesToCreate(DEFAULT_CONFIG); - assertNotNull(statesToCreate); - assertEquals(1, statesToCreate.size()); - assertTrue(statesToCreate.stream().anyMatch(state -> state.stateKey().equals(BLOCK_STREAM_INFO_KEY))); + void createsOneSingleton() { + final var stateDefs = subject.statesToCreate(DEFAULT_CONFIG); + assertEquals(1, stateDefs.size()); + final var def = stateDefs.iterator().next(); + assertTrue(def.singleton()); + assertEquals(BLOCK_STREAM_INFO_KEY, def.stateKey()); } @Test - void testMigration() { - when(mockCtx.previousVersion()).thenReturn(null); + void createsDefaultInfoAtGenesis() { + given(migrationContext.newStates()).willReturn(writableStates); + given(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) + .willReturn(state); - schema.migrate(mockCtx); + subject.migrate(migrationContext); - ArgumentCaptor captor = ArgumentCaptor.forClass(BlockStreamInfo.class); - verify(mockBlockStreamInfo).put(captor.capture()); + verify(state).put(BlockStreamInfo.DEFAULT); + } + + @Test + void assumesMigrationIfNotGenesisAndStateIsNull() { + final var blockInfo = new BlockInfo( + 666L, + new Timestamp(1_234_567L, 0), + Bytes.fromHex("abcd".repeat(24 * 256)), + new Timestamp(1_234_567L, 890), + false, + new Timestamp(1_234_567L, 123)); + final var sharedValues = Map.of( + "SHARED_BLOCK_RECORD_INFO", + blockInfo, + "SHARED_RUNNING_HASHES", + new RunningHashes( + Bytes.fromHex("aa".repeat(48)), + Bytes.fromHex("bb".repeat(48)), + Bytes.fromHex("cc".repeat(48)), + Bytes.fromHex("dd".repeat(48)))); + given(migrationContext.newStates()).willReturn(writableStates); + given(migrationContext.previousVersion()).willReturn(SemanticVersion.DEFAULT); + given(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) + .willReturn(state); + given(migrationContext.sharedValues()).willReturn(sharedValues); + + subject.migrate(migrationContext); + + verify(migratedBlockHashConsumer).accept(Bytes.fromHex("abcd".repeat(24))); + final var expectedInfo = new BlockStreamInfo( + blockInfo.lastBlockNumber(), + blockInfo.firstConsTimeOfLastBlock(), + Bytes.fromHex("dd".repeat(48) + "cc".repeat(48) + "bb".repeat(48) + "aa".repeat(48)), + Bytes.fromHex("abcd".repeat(24 * 255))); + verify(state).put(expectedInfo); + } + + @Test + void migrationIsNoopIfNotGenesisAndInfoIsNonNull() { + given(migrationContext.newStates()).willReturn(writableStates); + given(migrationContext.previousVersion()).willReturn(SemanticVersion.DEFAULT); + given(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) + .willReturn(state); + given(state.get()).willReturn(BlockStreamInfo.DEFAULT); + + subject.migrate(migrationContext); - BlockStreamInfo blockInfoCapture = captor.getValue(); - assertEquals(BlockStreamInfo.DEFAULT, blockInfoCapture); + verifyNoInteractions(migratedBlockHashConsumer); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java index dd5ff90e2b29..3c4c27a32850 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/components/IngestComponentTest.java @@ -39,6 +39,7 @@ import com.hedera.node.app.signature.impl.SignatureExpanderImpl; import com.hedera.node.app.signature.impl.SignatureVerifierImpl; import com.hedera.node.app.state.recordcache.RecordCacheService; +import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -66,6 +67,9 @@ class IngestComponentTest { @Mock private Platform platform; + @Mock + private TssBaseService tssBaseService; + private HederaInjectionComponent app; @BeforeEach @@ -114,6 +118,7 @@ void setUp() { .kvStateChangeListener(new KVStateChangeListener()) .boundaryStateChangeListener(new BoundaryStateChangeListener()) .migrationStateChanges(List.of()) + .tssBaseService(tssBaseService) .build(); final var state = new FakeState(); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceTest.java new file mode 100644 index 000000000000..45b095dfdc83 --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceTest.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.tss; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doCallRealMethod; +import static org.mockito.Mockito.mock; + +import org.junit.jupiter.api.Test; + +class TssBaseServiceTest { + @Test + void nameIsAsExpected() { + final var subject = mock(TssBaseService.class); + doCallRealMethod().when(subject).getServiceName(); + assertEquals(TssBaseService.NAME, subject.getServiceName()); + } +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseServiceTest.java new file mode 100644 index 000000000000..a5f5ffdc64e1 --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/impl/PlaceholderTssBaseServiceTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.tss.impl; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import com.swirlds.state.spi.SchemaRegistry; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class PlaceholderTssBaseServiceTest { + private CountDownLatch latch; + private final List receivedMessageHashes = new ArrayList<>(); + private final List receivedSignatures = new ArrayList<>(); + private final BiConsumer trackingConsumer = (a, b) -> { + receivedMessageHashes.add(a); + receivedSignatures.add(b); + latch.countDown(); + }; + + @Mock + private BiConsumer mockConsumer; + + @Mock + private SchemaRegistry registry; + + private final PlaceholderTssBaseService subject = new PlaceholderTssBaseService(); + + @BeforeEach + void setUp() { + subject.setExecutor(ForkJoinPool.commonPool()); + } + + @Test + void onlyRegisteredConsumerReceiveCallbacks() throws InterruptedException { + final var firstMessage = new byte[] {(byte) 0x01}; + final var secondMessage = new byte[] {(byte) 0x02}; + latch = new CountDownLatch(1); + + subject.registerLedgerSignatureConsumer(trackingConsumer); + subject.registerLedgerSignatureConsumer(mockConsumer); + + subject.requestLedgerSignature(firstMessage); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + subject.unregisterLedgerSignatureConsumer(mockConsumer); + latch = new CountDownLatch(1); + subject.requestLedgerSignature(secondMessage); + assertTrue(latch.await(1, TimeUnit.SECONDS)); + + assertEquals(2, receivedMessageHashes.size()); + assertEquals(2, receivedSignatures.size()); + assertArrayEquals(firstMessage, receivedMessageHashes.getFirst()); + assertArrayEquals(secondMessage, receivedMessageHashes.getLast()); + verify(mockConsumer).accept(firstMessage, receivedSignatures.getFirst()); + verifyNoMoreInteractions(mockConsumer); + } + + @Test + void placeholderRegistersNoSchemasYet() { + subject.registerSchemas(registry); + verifyNoInteractions(registry); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RepeatableReason.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RepeatableReason.java index 35f671274748..778cbdc5471e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RepeatableReason.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/RepeatableReason.java @@ -33,4 +33,8 @@ public enum RepeatableReason { * The test needs the handle workflow to be synchronous. */ NEEDS_SYNCHRONOUS_HANDLE_WORKFLOW, + /** + * The test needs to control behavior of the TSS subsystem. + */ + NEEDS_TSS_CONTROL, } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/AbstractEmbeddedHedera.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/AbstractEmbeddedHedera.java index e6890865ca81..ccb3046321db 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/AbstractEmbeddedHedera.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/AbstractEmbeddedHedera.java @@ -36,6 +36,7 @@ import com.hedera.pbj.runtime.io.buffer.BufferedData; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.embedded.fakes.AbstractFakePlatform; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; @@ -93,6 +94,7 @@ public abstract class AbstractEmbeddedHedera implements EmbeddedHedera { protected final AtomicInteger nextNano = new AtomicInteger(0); protected final Hedera hedera; protected final ServicesSoftwareVersion version; + protected final FakeTssBaseService tssBaseService; protected final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { @@ -104,11 +106,13 @@ protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { .collect(toMap(Address::getNodeId, address -> parseAccount(address.getMemo()))); defaultNodeId = addressBook.getNodeId(0); defaultNodeAccountId = fromPbj(accountIds.get(defaultNodeId)); + tssBaseService = new FakeTssBaseService(); hedera = new Hedera( ConstructableRegistry.getInstance(), FakeServicesRegistry.FACTORY, new FakeServiceMigrator(), - this::now); + this::now, + () -> tssBaseService); version = (ServicesSoftwareVersion) hedera.getSoftwareVersion(); Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdownNow)); } @@ -133,6 +137,11 @@ public FakeState state() { return state; } + @Override + public FakeTssBaseService tssBaseService() { + return tssBaseService; + } + @Override public SoftwareVersion version() { return version; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java index 06db83141fdc..1ba65a9d44c3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedHedera.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.junit.hedera.embedded; import com.hedera.node.app.fixtures.state.FakeState; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.Query; import com.hederahashgraph.api.proto.java.Response; @@ -42,11 +43,16 @@ public interface EmbeddedHedera { /** * Returns the fake state of the embedded Hedera node. - * * @return the fake state of the embedded Hedera node */ FakeState state(); + /** + * Returns the fake TSS base service of the embedded Hedera node. + * @return the fake TSS base service of the embedded Hedera node + */ + FakeTssBaseService tssBaseService(); + /** * Returns the software version of the embedded Hedera node. * @return the software version of the embedded Hedera node diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java new file mode 100644 index 000000000000..0ce0657779e5 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/FakeTssBaseService.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.junit.hedera.embedded.fakes; + +import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.services.bdd.junit.HapiTest; +import com.swirlds.common.utility.CommonUtils; +import com.swirlds.state.spi.SchemaRegistry; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.BiConsumer; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * A fake implementation of the {@link TssBaseService} that, + *
        + *
      • Lets the author of an embedded {@link HapiTest} control whether the TSS base service ignores + * signature requests; and, when requests are not ignored,
      • + *
      • "Signs" messages by scheduling callback to its consumers using the SHA-384 hash of the + * message as the signature.
      • + *
      + */ +public class FakeTssBaseService implements TssBaseService { + private static final Logger log = LogManager.getLogger(FakeTssBaseService.class); + + /** + * Copy-on-write list to avoid concurrent modification exceptions if a consumer unregisters + * itself in its callback. + */ + private final List> consumers = new CopyOnWriteArrayList<>(); + + private boolean ignoreRequests = false; + + /** + * When called, will start ignoring any requests for ledger signatures. + */ + public void startIgnoringRequests() { + ignoreRequests = true; + } + + /** + * When called, will stop ignoring any requests for ledger signatures. + */ + public void stopIgnoringRequests() { + ignoreRequests = false; + } + + @Override + public void registerSchemas(@NonNull final SchemaRegistry registry) { + // No-op for now + } + + @Override + public void requestLedgerSignature(@NonNull final byte[] messageHash) { + requireNonNull(messageHash); + if (ignoreRequests) { + return; + } + final var mockSignature = noThrowSha384HashOf(messageHash); + // Simulate asynchronous completion of the ledger signature + CompletableFuture.runAsync(() -> consumers.forEach(consumer -> { + try { + consumer.accept(messageHash, mockSignature); + } catch (Exception e) { + log.error( + "Failed to provide signature {} on message {} to consumer {}", + CommonUtils.hex(mockSignature), + CommonUtils.hex(messageHash), + consumer, + e); + } + })); + } + + @Override + public void registerLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { + requireNonNull(consumer); + consumers.add(consumer); + } + + @Override + public void unregisterLedgerSignatureConsumer(@NonNull final BiConsumer consumer) { + requireNonNull(consumer); + consumers.remove(consumer); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamAccess.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamAccess.java index 6e67899419bb..5dbcb4867f89 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamAccess.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamAccess.java @@ -34,6 +34,7 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -219,23 +220,29 @@ private Block blockFrom(@NonNull final Path path) { private List orderedBlocksFrom(@NonNull final Path path) throws IOException { try (final var stream = Files.walk(path)) { - return stream.filter(this::isBlockFile) - .sorted(comparing(this::extractBlockNumber)) + return stream.filter(BlockStreamAccess::isBlockFile) + .sorted(comparing(BlockStreamAccess::extractBlockNumber)) .toList(); } } - private boolean isBlockFile(@NonNull final Path path) { + private static boolean isBlockFile(@NonNull final Path path) { return path.toFile().isFile() && extractBlockNumber(path) != -1; } - private long extractBlockNumber(@NonNull final Path path) { - final var fileName = path.getFileName().toString(); + private static long extractBlockNumber(@NonNull final Path path) { + return extractBlockNumber(path.getFileName().toString()); + } + + public static boolean isBlockFile(@NonNull final File file) { + return file.isFile() && extractBlockNumber(file.getName()) != -1; + } + + private static long extractBlockNumber(@NonNull final String fileName) { try { final var blockNumber = fileName.substring(0, fileName.indexOf(UNCOMPRESSED_FILE_EXT)); return Long.parseLong(blockNumber); } catch (Exception ignore) { - log.info("Ignoring non-block file {}", path); } return -1; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamValidator.java index 2a1b400ebc0c..f6d9924430f6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BlockStreamValidator.java @@ -46,14 +46,14 @@ default boolean appliesTo(@NonNull final HapiSpec spec) { } /** - * Validate the given {@link Block}s in the context of the given {@link RecordStreamAccess.Data} and + * Validate the given {@link Block}s in the context of the given {@link StreamFileAccess.RecordStreamData} and * returns a {@link Stream} of {@link Throwable}s representing any validation errors. * @param blocks the blocks to validate * @param data the record stream data * @return a stream of validation errors */ default Stream validationErrorsIn( - @NonNull final List blocks, @NonNull final RecordStreamAccess.Data data) { + @NonNull final List blocks, @NonNull final StreamFileAccess.RecordStreamData data) { try { validateBlockVsRecords(blocks, data); } catch (final Throwable t) { @@ -63,12 +63,12 @@ default Stream validationErrorsIn( } /** - * Validate the given {@link Block}s in the context of the given {@link RecordStreamAccess.Data}. + * Validate the given {@link Block}s in the context of the given {@link StreamFileAccess.RecordStreamData}. * @param blocks the blocks to validate * @param data the record stream data */ default void validateBlockVsRecords( - @NonNull final List blocks, @NonNull final RecordStreamAccess.Data data) { + @NonNull final List blocks, @NonNull final StreamFileAccess.RecordStreamData data) { validateBlocks(blocks); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java index a59c46ba6507..493eb2171f6c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamValidator.java @@ -22,7 +22,7 @@ import java.util.stream.Stream; public interface RecordStreamValidator { - default Stream validationErrorsIn(@NonNull final RecordStreamAccess.Data data) { + default Stream validationErrorsIn(@NonNull final StreamFileAccess.RecordStreamData data) { try { validateFiles(data.files()); validateRecordsAndSidecars(data.records()); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java index ed78d084d62e..5df8d3dbbe80 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamDataListener.java @@ -16,13 +16,21 @@ package com.hedera.services.bdd.junit.support; +import com.hedera.hapi.block.stream.Block; import com.hedera.services.stream.proto.RecordStreamItem; import com.hedera.services.stream.proto.TransactionSidecarRecord; +import edu.umd.cs.findbugs.annotations.NonNull; /** * A listener that receives record stream items and transaction sidecar records. */ public interface StreamDataListener { + /** + * Called when a new block is received. + * @param block the new block + */ + default void onNewBlock(@NonNull final Block block) {} + default void onNewItem(RecordStreamItem item) {} default void onNewSidecar(TransactionSidecarRecord sidecar) {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAccess.java similarity index 79% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAccess.java index eb8945986591..651f522949c1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/RecordStreamAccess.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAccess.java @@ -50,24 +50,26 @@ * A singleton that provides near real-time access to the record stream files for all concurrently * executing {@link com.hedera.services.bdd.spec.HapiSpec}'s. */ -public enum RecordStreamAccess { - RECORD_STREAM_ACCESS; +public enum StreamFileAccess { + STREAM_FILE_ACCESS; - private static final Logger log = LogManager.getLogger(RecordStreamAccess.class); + private static final Logger log = LogManager.getLogger(StreamFileAccess.class); private static final int MONITOR_INTERVAL_MS = 250; /** - * A map of record stream file locations to the listeners that are watching them. (In general we - * only validate records from the single node0, but this could change?) + * A map of stream file locations to the listeners that are watching them. + *

      + * Note that in general we only validate stream files from {@code node0}, since if other nodes are producing + * different files they are certain to hit an ISS in very short order. */ - private final Map validatingListeners = new ConcurrentHashMap<>(); + private final Map locationListeners = new ConcurrentHashMap<>(); /** A bit of infrastructure that runs the polling loop for all the listeners. */ private final FileAlterationMonitor monitor = new FileAlterationMonitor(MONITOR_INTERVAL_MS); - public record Data(List records, List files) { - public static Data EMPTY_DATA = new Data(List.of(), List.of()); + public record RecordStreamData(List records, List files) { + public static RecordStreamData EMPTY_DATA = new RecordStreamData(List.of(), List.of()); } /** @@ -82,9 +84,9 @@ public synchronized Runnable subscribe(@NonNull final Path path, @NonNull final requireNonNull(path); requireNonNull(listener); try { - final var unsubscribe = getValidatingListener( - path.toAbsolutePath().normalize().toString()) - .subscribe(listener); + final var alterationListener = + getOrCreateListener(path.toAbsolutePath().normalize().toString()); + final var unsubscribe = alterationListener.subscribe(listener); return () -> { try { unsubscribe.run(); @@ -103,14 +105,14 @@ public synchronized Runnable subscribe(@NonNull final Path path, @NonNull final */ public synchronized void stopMonitorIfNoSubscribers() { // Count the number of subscribers (could derive from more than one concurrent HapiSpec) - final var numSubscribers = validatingListeners.values().stream() - .mapToInt(BroadcastingRecordStreamListener::numListeners) + final var numSubscribers = locationListeners.values().stream() + .mapToInt(StreamFileAlterationListener::numListeners) .sum(); if (numSubscribers == 0) { try { - if (!validatingListeners.isEmpty()) { - log.info("Stopping record stream access monitor (locations were {})", validatingListeners.keySet()); - validatingListeners.clear(); + if (!locationListeners.isEmpty()) { + log.info("Stopping record stream access monitor (locations were {})", locationListeners.keySet()); + locationListeners.clear(); } // Remove all observers and stop the monitor monitor.getObservers().forEach(monitor::removeObserver); @@ -121,26 +123,6 @@ public synchronized void stopMonitorIfNoSubscribers() { } } - /** - * If the given location is not already being watched, starts a new listener for it and returns - * the listener. - * - * @param loc the record stream file location to watch - * @return the listener for the given location - * @throws Exception if there is an error starting the listener - */ - public synchronized BroadcastingRecordStreamListener getValidatingListener(final String loc) throws Exception { - if (!validatingListeners.containsKey(loc)) { - var fAtLoc = relocatedIfNotPresentWithCurrentPathPrefix(new File(loc), "..", TEST_CLIENTS_PREFIX); - if (!fAtLoc.exists()) { - Files.createDirectories(fAtLoc.toPath()); - } - validatingListeners.put(loc, newValidatingListener(fAtLoc.getAbsolutePath())); - log.info("Started record stream listener for {}", loc); - } - return validatingListeners.get(loc); - } - /** * Reads the record and sidecar stream files from a given directory. * @@ -149,7 +131,7 @@ public synchronized BroadcastingRecordStreamListener getValidatingListener(final * @return the list of record and sidecar files * @throws IOException if there is an error reading the files */ - public Data readStreamDataFrom(String loc, final String relativeSidecarLoc) throws IOException { + public RecordStreamData readStreamDataFrom(String loc, final String relativeSidecarLoc) throws IOException { return readStreamDataFrom(loc, relativeSidecarLoc, f -> true); } @@ -163,7 +145,7 @@ public Data readStreamDataFrom(String loc, final String relativeSidecarLoc) thro * @return the list of record and sidecar files * @throws IOException if there is an error reading the files */ - public Data readStreamDataFrom( + public RecordStreamData readStreamDataFrom( @NonNull String loc, @NonNull final String relativeSidecarLoc, @NonNull final Predicate inclusionTest) @@ -191,11 +173,11 @@ public Data readStreamDataFrom( sidecarFilesByRecordFile .getOrDefault(parseRecordFileConsensusTime(f), Collections.emptyList()) .stream() - .map(RecordStreamAccess::ensurePresentSidecarFile) + .map(StreamFileAccess::ensurePresentSidecarFile) .toList()); }) .toList(); - return new Data(recordsWithSideCars, fullRecordFiles); + return new RecordStreamData(recordsWithSideCars, fullRecordFiles); } public static RecordStreamFile ensurePresentRecordFile(final String f) { @@ -218,9 +200,29 @@ public static SidecarFile ensurePresentSidecarFile(final String f) { } } - private BroadcastingRecordStreamListener newValidatingListener(final String loc) throws Exception { + /** + * If the given location is not already being watched, starts a new listener for it and returns + * the listener. + * + * @param loc the record stream file location to watch + * @return the listener for the given location + * @throws Exception if there is an error starting the listener + */ + private StreamFileAlterationListener getOrCreateListener(final String loc) throws Exception { + if (!locationListeners.containsKey(loc)) { + final var fAtLoc = relocatedIfNotPresentWithCurrentPathPrefix(new File(loc), "..", TEST_CLIENTS_PREFIX); + if (!fAtLoc.exists()) { + Files.createDirectories(fAtLoc.toPath()); + } + locationListeners.put(loc, newValidatingListener(fAtLoc.getAbsolutePath())); + log.info("Started stream file listener for {}", loc); + } + return locationListeners.get(loc); + } + + private StreamFileAlterationListener newValidatingListener(final String loc) throws Exception { final var observer = new FileAlterationObserver(loc); - final var listener = new BroadcastingRecordStreamListener(); + final var listener = new StreamFileAlterationListener(); observer.addListener(listener); monitor.addObserver(observer); try { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAlterationListener.java similarity index 82% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAlterationListener.java index d68400d72884..b3259c7f6967 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/BroadcastingRecordStreamListener.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/StreamFileAlterationListener.java @@ -18,11 +18,11 @@ import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isRecordFile; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.isSidecarFile; +import static com.hedera.services.bdd.junit.support.BlockStreamAccess.isBlockFile; import static java.util.concurrent.TimeUnit.MILLISECONDS; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.File; -import java.io.UncheckedIOException; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Consumer; @@ -34,8 +34,8 @@ * A small utility class that listens for record stream files and provides them to any subscribed * listeners. */ -public class BroadcastingRecordStreamListener extends FileAlterationListenerAdaptor { - private static final Logger log = LogManager.getLogger(BroadcastingRecordStreamListener.class); +public class StreamFileAlterationListener extends FileAlterationListenerAdaptor { + private static final Logger log = LogManager.getLogger(StreamFileAlterationListener.class); private static final int NUM_RETRIES = 32; private static final long RETRY_BACKOFF_MS = 500L; @@ -60,6 +60,7 @@ public Runnable subscribe(final StreamDataListener listener) { enum FileType { RECORD_STREAM_FILE, SIDE_CAR_FILE, + BLOCK_FILE, OTHER } @@ -68,6 +69,7 @@ public void onFileCreate(final File file) { switch (typeOf(file)) { case RECORD_STREAM_FILE -> retryExposingVia(this::exposeItems, "record", file); case SIDE_CAR_FILE -> retryExposingVia(this::exposeSidecars, "sidecar", file); + case BLOCK_FILE -> retryExposingVia(this::exposeBlock, "block", file); case OTHER -> { // Nothing to expose } @@ -87,7 +89,7 @@ private void retryExposingVia( fileType, f.getAbsolutePath()); return; - } catch (UncheckedIOException e) { + } catch (Exception e) { if (retryCount < NUM_RETRIES) { try { MILLISECONDS.sleep(RETRY_BACKOFF_MS); @@ -103,13 +105,19 @@ private void retryExposingVia( } } + private void exposeBlock(@NonNull final File file) { + final var block = + BlockStreamAccess.BLOCK_STREAM_ACCESS.readBlocks(file.toPath()).getFirst(); + listeners.forEach(l -> l.onNewBlock(block)); + } + private void exposeSidecars(final File file) { - final var contents = RecordStreamAccess.ensurePresentSidecarFile(file.getAbsolutePath()); + final var contents = StreamFileAccess.ensurePresentSidecarFile(file.getAbsolutePath()); contents.getSidecarRecordsList().forEach(sidecar -> listeners.forEach(l -> l.onNewSidecar(sidecar))); } private void exposeItems(final File file) { - final var contents = RecordStreamAccess.ensurePresentRecordFile(file.getAbsolutePath()); + final var contents = StreamFileAccess.ensurePresentRecordFile(file.getAbsolutePath()); contents.getRecordStreamItemsList().forEach(item -> listeners.forEach(l -> l.onNewItem(item))); } @@ -122,6 +130,8 @@ private FileType typeOf(final File file) { return FileType.RECORD_STREAM_FILE; } else if (isSidecarFile(file.getName())) { return FileType.SIDE_CAR_FILE; + } else if (isBlockFile(file)) { + return FileType.BLOCK_FILE; } else { return FileType.OTHER; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java index d7b8f63edec2..a6c67d3de324 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java @@ -27,6 +27,7 @@ import static com.hedera.node.config.types.EntityType.SCHEDULE; import static com.hedera.node.config.types.EntityType.TOKEN; import static com.hedera.node.config.types.EntityType.TOPIC; +import static com.hedera.services.bdd.junit.support.translators.impl.FileUpdateTranslator.EXCHANGE_RATES_FILE_NUM; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; @@ -77,8 +78,6 @@ public class BaseTranslator { private static final Logger log = LogManager.getLogger(BaseTranslator.class); - private static final long EXCHANGE_RATES_FILE_NUM = 112L; - /** * These fields are context maintained for the full lifetime of the translator. */ @@ -350,6 +349,29 @@ public SingleTransactionRecord recordFrom(@NonNull final BlockTransactionParts p new SingleTransactionRecord.TransactionOutputs(null)); } + /** + * Updates the active exchange rates with the contents of the given state change. + * @param change the state change to update from + */ + public void updateActiveRates(@NonNull final StateChange change) { + final var contents = + change.mapUpdateOrThrow().valueOrThrow().fileValueOrThrow().contents(); + try { + activeRates = ExchangeRateSet.PROTOBUF.parse(contents); + log.info("Updated active exchange rates to {}", activeRates); + } catch (ParseException e) { + throw new IllegalStateException("Rates file updated with unparseable contents", e); + } + } + + /** + * Returns the active exchange rates. + * @return the active exchange rates + */ + public ExchangeRateSet activeRates() { + return activeRates; + } + /** * Returns the modified schedule id for the ongoing transactional unit. * @@ -389,8 +411,6 @@ private void scanUnit(@NonNull final BlockTransactionalUnit unit) { nextCreatedNums .computeIfAbsent(FILE, ignore -> new LinkedList<>()) .add(num); - } else if (num == EXCHANGE_RATES_FILE_NUM) { - updateActiveRates(stateChange); } } else if (key.hasScheduleIdKey()) { final var num = key.scheduleIdKeyOrThrow().scheduleNum(); @@ -448,16 +468,6 @@ private void scanUnit(@NonNull final BlockTransactionalUnit unit) { }); } - private void updateActiveRates(@NonNull final StateChange change) { - final var contents = - change.mapUpdateOrThrow().valueOrThrow().fileValueOrThrow().contents(); - try { - activeRates = ExchangeRateSet.PROTOBUF.parse(contents); - } catch (ParseException e) { - throw new IllegalStateException("Rates file updated with unparseable contents", e); - } - } - private static boolean isContractOp(@NonNull final BlockTransactionParts parts) { final var function = parts.functionality(); return function == CONTRACT_CALL || function == CONTRACT_CREATE || function == ETHEREUM_TRANSACTION; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BlockTransactionalUnitTranslator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BlockTransactionalUnitTranslator.java index c395f67a6d0e..e434f90e0fff 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BlockTransactionalUnitTranslator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BlockTransactionalUnitTranslator.java @@ -83,6 +83,7 @@ import com.hedera.services.bdd.junit.support.translators.impl.CryptoUpdateTranslator; import com.hedera.services.bdd.junit.support.translators.impl.EthereumTransactionTranslator; import com.hedera.services.bdd.junit.support.translators.impl.FileCreateTranslator; +import com.hedera.services.bdd.junit.support.translators.impl.FileUpdateTranslator; import com.hedera.services.bdd.junit.support.translators.impl.NodeCreateTranslator; import com.hedera.services.bdd.junit.support.translators.impl.ScheduleCreateTranslator; import com.hedera.services.bdd.junit.support.translators.impl.ScheduleDeleteTranslator; @@ -142,7 +143,7 @@ public class BlockTransactionalUnitTranslator { put(FILE_APPEND, NO_EXPLICIT_SIDE_EFFECTS_TRANSLATOR); put(FILE_CREATE, new FileCreateTranslator()); put(FILE_DELETE, NO_EXPLICIT_SIDE_EFFECTS_TRANSLATOR); - put(FILE_UPDATE, NO_EXPLICIT_SIDE_EFFECTS_TRANSLATOR); + put(FILE_UPDATE, new FileUpdateTranslator()); put(FREEZE, NO_EXPLICIT_SIDE_EFFECTS_TRANSLATOR); put(NODE_CREATE, new NodeCreateTranslator()); put(NODE_DELETE, NO_EXPLICIT_SIDE_EFFECTS_TRANSLATOR); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/impl/FileUpdateTranslator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/impl/FileUpdateTranslator.java new file mode 100644 index 000000000000..0c7462c18b08 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/impl/FileUpdateTranslator.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.junit.support.translators.impl; + +import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.block.stream.output.StateChange; +import com.hedera.node.app.state.SingleTransactionRecord; +import com.hedera.services.bdd.junit.support.translators.BaseTranslator; +import com.hedera.services.bdd.junit.support.translators.BlockTransactionPartsTranslator; +import com.hedera.services.bdd.junit.support.translators.inputs.BlockTransactionParts; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * Translates a file update transaction into a {@link SingleTransactionRecord}, updating + * {@link BaseTranslator} context when a special file is changed. + */ +public class FileUpdateTranslator implements BlockTransactionPartsTranslator { + public static final long EXCHANGE_RATES_FILE_NUM = 112L; + + @Override + public SingleTransactionRecord translate( + @NonNull final BlockTransactionParts parts, + @NonNull final BaseTranslator baseTranslator, + @NonNull final List remainingStateChanges) { + requireNonNull(parts); + requireNonNull(baseTranslator); + requireNonNull(remainingStateChanges); + return baseTranslator.recordFrom(parts, (receiptBuilder, recordBuilder) -> { + if (parts.status() == SUCCESS) { + for (final var stateChange : remainingStateChanges) { + if (stateChange.hasMapUpdate() + && stateChange.mapUpdateOrThrow().keyOrThrow().hasFileIdKey()) { + final var fileId = + stateChange.mapUpdateOrThrow().keyOrThrow().fileIdKeyOrThrow(); + if (fileId.fileNum() == EXCHANGE_RATES_FILE_NUM) { + baseTranslator.updateActiveRates(stateChange); + receiptBuilder.exchangeRate(baseTranslator.activeRates()); + } + } + } + } + }); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/TransactionRecordParityValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/TransactionRecordParityValidator.java index ecb003cc38cc..9465c10628ed 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/TransactionRecordParityValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/TransactionRecordParityValidator.java @@ -30,7 +30,7 @@ import com.hedera.node.app.state.SingleTransactionRecord; import com.hedera.services.bdd.junit.support.BlockStreamAccess; import com.hedera.services.bdd.junit.support.BlockStreamValidator; -import com.hedera.services.bdd.junit.support.RecordStreamAccess; +import com.hedera.services.bdd.junit.support.StreamFileAccess; import com.hedera.services.bdd.junit.support.translators.BlockTransactionalUnitTranslator; import com.hedera.services.bdd.junit.support.translators.BlockUnitSplit; import com.hedera.services.bdd.spec.HapiSpec; @@ -87,15 +87,15 @@ public static void main(@NonNull final String[] args) throws IOException { final var blocks = BlockStreamAccess.BLOCK_STREAM_ACCESS.readBlocks(blocksLoc); final var recordsLoc = node0Data.resolve("recordStreams/record0.0.3").toAbsolutePath().normalize(); - final var records = - RecordStreamAccess.RECORD_STREAM_ACCESS.readStreamDataFrom(recordsLoc.toString(), "sidecar"); + final var records = StreamFileAccess.STREAM_FILE_ACCESS.readStreamDataFrom(recordsLoc.toString(), "sidecar"); final var validator = new TransactionRecordParityValidator(); validator.validateBlockVsRecords(blocks, records); } @Override - public void validateBlockVsRecords(@NonNull final List blocks, @NonNull final RecordStreamAccess.Data data) { + public void validateBlockVsRecords( + @NonNull final List blocks, @NonNull final StreamFileAccess.RecordStreamData data) { requireNonNull(blocks); requireNonNull(data); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java index 19479fd5f5ca..1cc569862fac 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/HapiSpec.java @@ -22,7 +22,7 @@ import static com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension.REPEATABLE_KEY_GENERATOR; import static com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension.SHARED_NETWORK; import static com.hedera.services.bdd.junit.hedera.ExternalPath.RECORD_STREAMS_DIR; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.ERROR; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.FAILED; import static com.hedera.services.bdd.spec.HapiSpec.SpecStatus.FAILED_AS_EXPECTED; @@ -75,6 +75,7 @@ import com.hedera.services.bdd.junit.hedera.HederaNetwork; import com.hedera.services.bdd.junit.hedera.HederaNode; import com.hedera.services.bdd.junit.hedera.NodeSelector; +import com.hedera.services.bdd.junit.hedera.embedded.EmbeddedHedera; import com.hedera.services.bdd.junit.hedera.embedded.EmbeddedNetwork; import com.hedera.services.bdd.junit.hedera.remote.RemoteNetwork; import com.hedera.services.bdd.junit.support.TestLifecycle; @@ -93,7 +94,7 @@ import com.hedera.services.bdd.spec.utilops.records.AutoSnapshotModeOp; import com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode; import com.hedera.services.bdd.spec.utilops.records.SnapshotModeOp; -import com.hedera.services.bdd.spec.utilops.streams.assertions.EventualRecordStreamAssertion; +import com.hedera.services.bdd.spec.utilops.streams.assertions.AbstractEventualStreamAssertion; import com.hedera.services.bdd.spec.verification.traceability.SidecarWatcher; import com.hederahashgraph.api.proto.java.Key; import com.hederahashgraph.api.proto.java.ResponseCodeEnum; @@ -117,6 +118,7 @@ import java.util.SplittableRandom; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; @@ -459,6 +461,15 @@ public SidecarWatcher getSidecarWatcher() { } } + /** + * Returns the {@link EmbeddedHedera} for a spec in embedded mode, or throws if the spec is not in embedded mode. + * @return the embedded Hedera + * @throws IllegalStateException if the spec is not in embedded mode + */ + public EmbeddedHedera embeddedHederaOrThrow() { + return embeddedNetworkOrThrow().embeddedHederaOrThrow(); + } + /** * Sleeps for the approximate wall clock time it will take for the spec's target * network to advance consensus time by the given duration. @@ -725,7 +736,7 @@ private void exec(@NonNull List ops) { if (!autoScheduled.isEmpty()) { log.info("Auto-scheduling {}", autoScheduled); } - @Nullable List assertions = null; + @Nullable List streamAssertions = null; var snapshotOp = AutoSnapshotModeOp.from(this); if (snapshotOp != null) { // Ensure a mutable list @@ -736,11 +747,11 @@ private void exec(@NonNull List ops) { if (!autoScheduled.isEmpty() && op.shouldSkipWhenAutoScheduling(autoScheduled)) { continue; } - if (op instanceof EventualRecordStreamAssertion recordStreamAssertion) { - if (assertions == null) { - assertions = new ArrayList<>(); + if (op instanceof AbstractEventualStreamAssertion streamAssertion) { + if (streamAssertions == null) { + streamAssertions = new ArrayList<>(); } - assertions.add(recordStreamAssertion); + streamAssertions.add(streamAssertion); } else if (op instanceof HapiTxnOp txn && autoScheduled.contains(txn.type())) { op = autoScheduledSequenceFor(txn); } else if (op instanceof SnapshotModeOp snapshotModeOp) { @@ -799,10 +810,10 @@ private void exec(@NonNull List ops) { failure = new Failure(t, "Record snapshot fuzzy-match"); } } - final var maybeRecordStreamError = checkRecordStream(assertions); - if (maybeRecordStreamError.isPresent()) { + final var maybeStreamFileError = checkStream(streamAssertions); + if (maybeStreamFileError.isPresent()) { status = FAILED; - failure = maybeRecordStreamError.get(); + failure = maybeStreamFileError.get(); } if (sidecarWatcher != null) { try { @@ -813,9 +824,9 @@ private void exec(@NonNull List ops) { failure = new Failure(t, "Sidecar assertion"); } } - } else if (assertions != null) { - assertions.forEach(EventualRecordStreamAssertion::unsubscribe); - RECORD_STREAM_ACCESS.stopMonitorIfNoSubscribers(); + } else if (streamAssertions != null) { + streamAssertions.forEach(AbstractEventualStreamAssertion::unsubscribe); + STREAM_FILE_ACCESS.stopMonitorIfNoSubscribers(); } tearDown(); @@ -950,30 +961,34 @@ private static List createAndSignIndicesGiven(final int numKeys, final return endIndices; } - private Optional checkRecordStream(@Nullable final List assertions) { - if (assertions == null) { + private Optional checkStream(@Nullable final List streamAssertions) { + if (streamAssertions == null) { return Optional.empty(); } if (!quietMode) { - log.info("Checking record stream for {} assertions", assertions.size()); + log.info("Checking stream files for {} assertions", streamAssertions.size()); } + final var needsTraffic = + streamAssertions.stream().anyMatch(AbstractEventualStreamAssertion::needsBackgroundTraffic); Optional answer = Optional.empty(); - // Keep submitting transactions to close record files (in almost every case, just + // Keep submitting transactions to close stream files (in almost every case, just // one file will need to be closed, since it's very rare to have a long-running spec) - final var backgroundTraffic = THREAD_POOL.submit(() -> { - while (true) { - try { - TxnUtils.triggerAndCloseAtLeastOneFile(this); - if (!quietMode) { - log.info("Closed at least one record file via background traffic"); + final Future backgroundTraffic = needsTraffic + ? THREAD_POOL.submit(() -> { + while (true) { + try { + TxnUtils.triggerAndCloseAtLeastOneFile(this); + if (!quietMode) { + log.info("Closed at least one record file via background traffic"); + } + } catch (final InterruptedException ignore) { + Thread.currentThread().interrupt(); + return; + } } - } catch (final InterruptedException ignore) { - Thread.currentThread().interrupt(); - return; - } - } - }); - for (final var assertion : assertions) { + }) + : null; + for (final var assertion : streamAssertions) { if (!quietMode) { log.info("Checking record stream for {}", assertion); } @@ -987,8 +1002,10 @@ private Optional checkRecordStream(@Nullable final List observer) { return new ViewNodeOp(name, observer); } /*** - * `ViewPendingAirdropOp` is an operation that allows the test author to view the pending airdrop of an account. - * @param tokenName - * @param senderName - * @param receiverName - * @param observer - * @return + * Returns an operation that allows the test author to view the pending airdrop of an account. + * @param tokenName the name of the token + * @param senderName the name of the sender + * @param receiverName the name of the receiver + * @param observer the observer to apply to the account + * @return the operation that will expose the pending airdrop of the account */ public static ViewPendingAirdropOp viewAccountPendingAirdrop( @NonNull final String tokenName, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java new file mode 100644 index 000000000000..ed7e53e865aa --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/TssVerbs.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.spec.utilops; + +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doingContextual; + +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.services.bdd.spec.SpecOperation; + +/** + * Factory for spec operations that support exercising TSS, especially in embedded mode. + */ +public class TssVerbs { + private TssVerbs() { + throw new UnsupportedOperationException("Utility class"); + } + + /** + * Returns an operation that instructs the embedded {@link TssBaseService} to ignoring TSS signature requests. + * @return the operation that will ignore TSS signature requests + */ + public static SpecOperation startIgnoringTssSignatureRequests() { + return doingContextual( + spec -> spec.embeddedHederaOrThrow().tssBaseService().startIgnoringRequests()); + } + + /** + * Returns an operation that instructs the embedded {@link TssBaseService} to stop ignoring TSS signature requests. + * @return the operation that will stop ignoring TSS signature requests + */ + public static SpecOperation stopIgnoringTssSignatureRequests() { + return doingContextual( + spec -> spec.embeddedHederaOrThrow().tssBaseService().stopIgnoringRequests()); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java index fb7c907e9c87..2e2e943bac9f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/UtilVerbs.java @@ -159,8 +159,10 @@ import com.hedera.services.bdd.spec.utilops.streams.LogContainmentOp; import com.hedera.services.bdd.spec.utilops.streams.LogValidationOp; import com.hedera.services.bdd.spec.utilops.streams.StreamValidationOp; +import com.hedera.services.bdd.spec.utilops.streams.assertions.AbstractEventualStreamAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.AssertingBiConsumer; -import com.hedera.services.bdd.spec.utilops.streams.assertions.EventualAssertion; +import com.hedera.services.bdd.spec.utilops.streams.assertions.BlockStreamAssertion; +import com.hedera.services.bdd.spec.utilops.streams.assertions.EventualBlockStreamAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.EventualRecordStreamAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.RecordStreamAssertion; import com.hedera.services.bdd.spec.utilops.streams.assertions.SelectedItemsAssertion; @@ -1065,20 +1067,38 @@ public static HapiSpecOperation remembering(final Map props, fin } /* Stream validation. */ - public static EventualAssertion streamMustInclude(final Function assertion) { - return new EventualRecordStreamAssertion(assertion); - } - - public static EventualAssertion streamMustIncludeNoFailuresFrom( - final Function assertion) { + public static EventualRecordStreamAssertion recordStreamMustIncludeNoFailuresFrom( + @NonNull final Function assertion) { return EventualRecordStreamAssertion.eventuallyAssertingNoFailures(assertion); } - public static EventualAssertion streamMustIncludePassFrom( - final Function assertion) { + public static EventualRecordStreamAssertion recordStreamMustIncludePassFrom( + @NonNull final Function assertion) { return EventualRecordStreamAssertion.eventuallyAssertingExplicitPass(assertion); } + /** + * Returns an operation that asserts that the block stream must include no failures from the given assertion + * before its timeout elapses. + * @param assertion the assertion to apply to the block stream + * @return the operation that asserts no block stream problems + */ + public static EventualBlockStreamAssertion blockStreamMustIncludeNoFailuresFrom( + @NonNull final Function assertion) { + return EventualBlockStreamAssertion.eventuallyAssertingNoFailures(assertion); + } + + /** + * Returns an operation that asserts that the block stream must include a pass from the given assertion + * before its timeout elapses. + * @param assertion the assertion to apply to the block stream + * @return the operation that asserts a passing block stream + */ + public static AbstractEventualStreamAssertion blockStreamMustIncludePassFrom( + @NonNull final Function assertion) { + return EventualBlockStreamAssertion.eventuallyAssertingExplicitPass(assertion); + } + public static RunnableOp verify(@NonNull final Runnable runnable) { return new RunnableOp(runnable); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java index b696a528dfcb..5416f24ea31f 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/records/SnapshotModeOp.java @@ -17,7 +17,7 @@ package com.hedera.services.bdd.spec.utilops.records; import static com.hedera.node.app.hapi.utils.exports.recordstreaming.RecordStreamingUtils.parseRecordFileConsensusTime; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; @@ -43,7 +43,7 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.GeneratedMessageV3; import com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener; -import com.hedera.services.bdd.junit.support.RecordStreamAccess; +import com.hedera.services.bdd.junit.support.StreamFileAccess; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.utilops.UtilOp; import com.hedera.services.bdd.spec.utilops.domain.ParsedItem; @@ -260,11 +260,11 @@ public void finishLifecycle(@NonNull final HapiSpec spec) { return; } try { - RecordStreamAccess.Data data = RecordStreamAccess.Data.EMPTY_DATA; + StreamFileAccess.RecordStreamData data = StreamFileAccess.RecordStreamData.EMPTY_DATA; for (final var recordLoc : recordLocs) { try { log.info("Trying to read post-placeholder items from {}", recordLoc); - data = RECORD_STREAM_ACCESS.readStreamDataFrom(recordLoc, "sidecar", f -> { + data = STREAM_FILE_ACCESS.readStreamDataFrom(recordLoc, "sidecar", f -> { final var fileConsTime = parseRecordFileConsensusTime(f); return fileConsTime.isAfter(lowerBoundConsensusStartTime) && new File(f).length() > MIN_GZIP_SIZE_IN_BYTES; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/EventualAssertionResult.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/EventualAssertionResult.java index cdb042f87405..6609c698cc76 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/EventualAssertionResult.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/EventualAssertionResult.java @@ -16,7 +16,10 @@ package com.hedera.services.bdd.spec.utilops.streams; +import static java.util.Objects.requireNonNull; + import com.hedera.services.bdd.spec.utilops.streams.assertions.AssertionResult; +import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -29,13 +32,13 @@ public class EventualAssertionResult { private AssertionResult result; - public EventualAssertionResult(final Duration timeout) { + public EventualAssertionResult(@NonNull final Duration timeout) { this(false, timeout); } - public EventualAssertionResult(boolean hasPassedIfNothingFailed, final Duration timeout) { + public EventualAssertionResult(final boolean hasPassedIfNothingFailed, @NonNull final Duration timeout) { this.hasPassedIfNothingFailed = hasPassedIfNothingFailed; - this.timeout = timeout; + this.timeout = requireNonNull(timeout); } public AssertionResult get() throws InterruptedException { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/StreamValidationOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/StreamValidationOp.java index 0486a5fe9f85..b3ebd6cea1ce 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/StreamValidationOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/StreamValidationOp.java @@ -19,7 +19,7 @@ import static com.hedera.services.bdd.junit.hedera.ExternalPath.BLOCK_STREAMS_DIR; import static com.hedera.services.bdd.junit.hedera.ExternalPath.RECORD_STREAMS_DIR; import static com.hedera.services.bdd.junit.support.BlockStreamAccess.BLOCK_STREAM_ACCESS; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.freezeOnly; @@ -30,8 +30,8 @@ import com.hedera.hapi.block.stream.Block; import com.hedera.services.bdd.junit.support.BlockStreamValidator; -import com.hedera.services.bdd.junit.support.RecordStreamAccess; import com.hedera.services.bdd.junit.support.RecordStreamValidator; +import com.hedera.services.bdd.junit.support.StreamFileAccess; import com.hedera.services.bdd.junit.support.validators.BalanceReconciliationValidator; import com.hedera.services.bdd.junit.support.validators.BlockNoValidator; import com.hedera.services.bdd.junit.support.validators.ExpiryRecordsValidator; @@ -88,7 +88,7 @@ protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { // Wait for the final record file to be created sleepFor(2 * BUFFER_MS)); // Validate the record streams - final AtomicReference dataRef = new AtomicReference<>(); + final AtomicReference dataRef = new AtomicReference<>(); readMaybeRecordStreamDataFor(spec) .ifPresentOrElse( data -> { @@ -154,8 +154,9 @@ private static Optional> readMaybeBlockStreamsFor(@NonNull final Hap return Optional.ofNullable(blocks); } - private static Optional readMaybeRecordStreamDataFor(@NonNull final HapiSpec spec) { - RecordStreamAccess.Data data = null; + private static Optional readMaybeRecordStreamDataFor( + @NonNull final HapiSpec spec) { + StreamFileAccess.RecordStreamData data = null; final var streamLocs = spec.getNetworkNodes().stream() .map(node -> node.getExternalPath(RECORD_STREAMS_DIR)) .map(Path::toAbsolutePath) @@ -164,7 +165,7 @@ private static Optional readMaybeRecordStreamDataFor(@N for (final var loc : streamLocs) { try { log.info("Trying to read record files from {}", loc); - data = RECORD_STREAM_ACCESS.readStreamDataFrom( + data = STREAM_FILE_ACCESS.readStreamDataFrom( loc, "sidecar", f -> new File(f).length() > MIN_GZIP_SIZE_IN_BYTES); log.info("Read {} record files from {}", data.records().size(), loc); } catch (Exception ignore) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/AbstractEventualStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/AbstractEventualStreamAssertion.java new file mode 100644 index 000000000000..abdd80dfee49 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/AbstractEventualStreamAssertion.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2020-2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.spec.utilops.streams.assertions; + +import com.hedera.services.bdd.spec.utilops.UtilOp; +import com.hedera.services.bdd.spec.utilops.streams.EventualAssertionResult; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.time.Duration; +import org.junit.jupiter.api.Assertions; + +/** + * Important: {@code HapiSpec#exec()} recognizes {@link AbstractEventualStreamAssertion} + * operations as a special case, in two ways. + *

        + *
      1. If a spec includes at least one {@link AbstractEventualStreamAssertion}, and all other + * operations have passed, it starts running "background traffic" to ensure record stream + * files are being written. + *
      2. For each {@link AbstractEventualStreamAssertion}, the spec then calls its + * {@link #assertHasPassed()}, method which blocks until the assertion has either passed or timed + * out. (The default timeout is 5 seconds, since generally we expect the assertion to apply to + * the contents of a single record stream file, which are created every 2 seconds given steady + * background traffic.) + *
      + */ +public abstract class AbstractEventualStreamAssertion extends UtilOp { + private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5L); + + protected final EventualAssertionResult result; + + /** + * Once this op is submitted, the function to unsubscribe from the stream. + */ + @Nullable + protected Runnable unsubscribe; + + protected AbstractEventualStreamAssertion(final boolean hasPassedIfNothingFailed) { + result = new EventualAssertionResult(hasPassedIfNothingFailed, DEFAULT_TIMEOUT); + } + + /** + * Returns true if this assertion needs background traffic to be running in order to pass. + * @return true if this assertion needs background traffic + */ + public boolean needsBackgroundTraffic() { + return true; + } + + /** + * If this assertion has subscribed to a stream, this method unsubscribes from it. + */ + public void unsubscribe() { + if (unsubscribe != null) { + unsubscribe.run(); + } + } + + /** + * Blocks until the assertion has passed, fail, or timed out. + * @throws AssertionError if the assertion has failed + */ + public void assertHasPassed() { + try { + final var eventualResult = result.get(); + unsubscribe(); + if (!eventualResult.passed()) { + Assertions.fail(eventualResult.getErrorDetails()); + } + } catch (final InterruptedException e) { + Thread.currentThread().interrupt(); + Assertions.fail("Interrupted while waiting for " + this + " to pass"); + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/BlockStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/BlockStreamAssertion.java new file mode 100644 index 000000000000..54b7e2d9bb04 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/BlockStreamAssertion.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.spec.utilops.streams.assertions; + +import com.hedera.hapi.block.stream.Block; +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * Implements an assertion about one or more {@link com.hedera.hapi.block.stream.Block}'s that should appear in the + * block stream during---or shortly after---execution of a {@link HapiSpec}. + * + *

      Typical implementations will be stateful, and will be constructed with their "parent" {@link HapiSpec}. + */ +public interface BlockStreamAssertion { + /** + * Updates the assertion's state based on a relevant {@link Block}, throwing an {@link AssertionError} if a + * failure state is reached; or returning true if the assertion has reached a success state. + * + * @param block the block to test + * @throws AssertionError if the assertion has failed + * @return true if the assertion has succeeded + */ + default boolean test(@NonNull final Block block) throws AssertionError { + return true; + } + + /** + * Hint to implementers to return a string that describes the assertion. + * + * @return a string that describes the assertion + */ + @Override + String toString(); +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualAssertion.java deleted file mode 100644 index 96213f9ae17a..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualAssertion.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * 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 com.hedera.services.bdd.spec.utilops.streams.assertions; - -import com.hedera.services.bdd.spec.utilops.UtilOp; -import com.hedera.services.bdd.spec.utilops.streams.EventualAssertionResult; -import java.time.Duration; - -public abstract class EventualAssertion extends UtilOp { - private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(5L); - - protected final EventualAssertionResult result; - - protected EventualAssertion() { - this(DEFAULT_TIMEOUT); - } - - protected EventualAssertion(final Duration timeout) { - result = new EventualAssertionResult(timeout); - } - - protected EventualAssertion(final boolean hasPassedIfNothingFailed) { - result = new EventualAssertionResult(hasPassedIfNothingFailed, DEFAULT_TIMEOUT); - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualBlockStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualBlockStreamAssertion.java new file mode 100644 index 000000000000..1434dd376e91 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualBlockStreamAssertion.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.spec.utilops.streams.assertions; + +import static com.hedera.services.bdd.junit.hedera.ExternalPath.BLOCK_STREAMS_DIR; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.block.stream.Block; +import com.hedera.services.bdd.junit.support.StreamDataListener; +import com.hedera.services.bdd.spec.HapiSpec; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.nio.file.Path; +import java.util.function.Function; + +public class EventualBlockStreamAssertion extends AbstractEventualStreamAssertion { + /** + * The factory for the assertion to be tested. + */ + private final Function assertionFactory; + /** + * Once this op is submitted, the assertion to be tested. + */ + @Nullable + private BlockStreamAssertion assertion; + + /** + * Returns an {@link EventualBlockStreamAssertion} that will pass as long as the given assertion does not + * throw an {@link AssertionError} before its timeout. + * @param assertionFactory the assertion factory + * @return the eventual block stream assertion that must not fail + */ + public static EventualBlockStreamAssertion eventuallyAssertingNoFailures( + @NonNull final Function assertionFactory) { + return new EventualBlockStreamAssertion(assertionFactory, true); + } + + /** + * Returns an {@link EventualBlockStreamAssertion} that will pass only if the given assertion explicitly + * passes within the default timeout. + * @param assertionFactory the assertion factory + * @return the eventual block stream assertion that must pass + */ + public static EventualBlockStreamAssertion eventuallyAssertingExplicitPass( + @NonNull final Function assertionFactory) { + return new EventualBlockStreamAssertion(assertionFactory, false); + } + + private EventualBlockStreamAssertion( + @NonNull final Function assertionFactory, + final boolean hasPassedIfNothingFailed) { + super(hasPassedIfNothingFailed); + this.assertionFactory = requireNonNull(assertionFactory); + } + + @Override + public boolean needsBackgroundTraffic() { + return false; + } + + @Override + protected boolean submitOp(@NonNull final HapiSpec spec) throws Throwable { + requireNonNull(spec); + assertion = requireNonNull(assertionFactory.apply(spec)); + unsubscribe = STREAM_FILE_ACCESS.subscribe(blockStreamLocFor(spec), new StreamDataListener() { + @Override + public void onNewBlock(@NonNull final Block block) { + requireNonNull(block); + try { + if (assertion.test(block)) { + result.pass(); + } + } catch (final AssertionError e) { + result.fail(e.getMessage()); + } + } + + @Override + public String name() { + return assertion.toString(); + } + }); + return false; + } + + /** + * Returns the block stream location for the first listed node in the network targeted + * by the given spec. + * + * @param spec the spec + * @return a record stream location for the first listed node in the network + */ + private static Path blockStreamLocFor(@NonNull final HapiSpec spec) { + return spec.targetNetworkOrThrow().nodes().getFirst().getExternalPath(BLOCK_STREAMS_DIR); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java index f388fe8f973d..5e63b339eba8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/EventualRecordStreamAssertion.java @@ -17,38 +17,25 @@ package com.hedera.services.bdd.spec.utilops.streams.assertions; import static com.hedera.services.bdd.junit.hedera.ExternalPath.RECORD_STREAMS_DIR; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; +import static java.util.Objects.requireNonNull; -import com.hedera.services.bdd.junit.support.RecordStreamAccess; import com.hedera.services.bdd.junit.support.StreamDataListener; +import com.hedera.services.bdd.junit.support.StreamFileAccess; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.stream.proto.RecordStreamItem; import com.hedera.services.stream.proto.TransactionSidecarRecord; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.Objects; +import java.nio.file.Path; import java.util.function.Function; -import org.junit.jupiter.api.Assertions; /** * A {@link com.hedera.services.bdd.spec.utilops.UtilOp} that registers itself with {@link - * RecordStreamAccess} and continually updates the {@link RecordStreamAssertion} yielded by a given + * StreamFileAccess} and continually updates the {@link RecordStreamAssertion} yielded by a given * factory with each new {@link RecordStreamItem}. - * - *

      Important: {@code HapiSpec#exec()} recognizes {@link EventualRecordStreamAssertion} - * operations as a special case, in two ways. - *

        - *
      1. If a spec includes at least one {@link EventualRecordStreamAssertion}, and all other - * operations have passed, it starts running "background traffic" to ensure record stream - * files are being written. - *
      2. For each {@link EventualRecordStreamAssertion}, the spec then calls its {@link - * #assertHasPassed()}, method which blocks until the assertion has either passed or timed - * out. (The default timeout is 3 seconds, since generally we expect the assertion to apply to - * the contents of a single record stream file, which are created every 2 seconds given steady - * background traffic.) - *
      */ -public class EventualRecordStreamAssertion extends EventualAssertion { +public class EventualRecordStreamAssertion extends AbstractEventualStreamAssertion { /** * The factory for the assertion to be tested. */ @@ -59,21 +46,6 @@ public class EventualRecordStreamAssertion extends EventualAssertion { */ @Nullable private RecordStreamAssertion assertion; - /** - * Once this op is submitted, the function to unsubscribe from the record stream. - */ - @Nullable - private Runnable unsubscribe; - - public EventualRecordStreamAssertion(final Function assertionFactory) { - this.assertionFactory = assertionFactory; - } - - private EventualRecordStreamAssertion( - final Function assertionFactory, final boolean hasPassedIfNothingFailed) { - super(hasPassedIfNothingFailed); - this.assertionFactory = assertionFactory; - } /** * Returns an {@link EventualRecordStreamAssertion} that will pass as long as the given assertion does not @@ -97,28 +69,17 @@ public static EventualRecordStreamAssertion eventuallyAssertingExplicitPass( return new EventualRecordStreamAssertion(assertionFactory, false); } - /** - * Returns the record stream location for the first listed node in the network targeted - * by the given spec. - * - * @param spec the spec - * @return a record stream location for the first listed node in the network - */ - public static String recordStreamLocFor(@NonNull final HapiSpec spec) { - Objects.requireNonNull(spec); - return spec.targetNetworkOrThrow() - .nodes() - .getFirst() - .getExternalPath(RECORD_STREAMS_DIR) - .toString(); + private EventualRecordStreamAssertion( + @NonNull final Function assertionFactory, + final boolean hasPassedIfNothingFailed) { + super(hasPassedIfNothingFailed); + this.assertionFactory = requireNonNull(assertionFactory); } @Override protected boolean submitOp(final HapiSpec spec) throws Throwable { - final var locToUse = recordStreamLocFor(spec); - final var validatingListener = RECORD_STREAM_ACCESS.getValidatingListener(locToUse); - assertion = Objects.requireNonNull(assertionFactory.apply(spec)); - unsubscribe = validatingListener.subscribe(new StreamDataListener() { + assertion = requireNonNull(assertionFactory.apply(spec)); + unsubscribe = STREAM_FILE_ACCESS.subscribe(recordStreamLocFor(spec), new StreamDataListener() { @Override public void onNewItem(RecordStreamItem item) { if (assertion.isApplicableTo(item)) { @@ -153,29 +114,19 @@ public String name() { return false; } - public void assertHasPassed() { - try { - final var eventualResult = result.get(); - if (unsubscribe != null) { - unsubscribe.run(); - } - if (!eventualResult.passed()) { - Assertions.fail(eventualResult.getErrorDetails()); - } - } catch (final InterruptedException e) { - Thread.currentThread().interrupt(); - Assertions.fail("Interrupted while waiting for " + assertion + " to pass"); - } - } - - public void unsubscribe() { - if (unsubscribe != null) { - unsubscribe.run(); - } - } - @Override public String toString() { - return "Eventually{" + assertion + "}"; + return "EventuallyRecordStream{" + assertion + "}"; + } + + /** + * Returns the record stream location for the first listed node in the network targeted + * by the given spec. + * + * @param spec the spec + * @return a record stream location for the first listed node in the network + */ + private static Path recordStreamLocFor(@NonNull final HapiSpec spec) { + return spec.targetNetworkOrThrow().nodes().getFirst().getExternalPath(RECORD_STREAMS_DIR); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/IndirectProofsAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/IndirectProofsAssertion.java new file mode 100644 index 000000000000..cf75149ccf04 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/IndirectProofsAssertion.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.spec.utilops.streams.assertions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.hedera.hapi.block.stream.Block; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A {@link BlockStreamAssertion} used to verify the presence of some number {@code n} of expected indirect proofs + * in the block stream. When constructed, it assumes proof construction is paused, and fails if any block + * is written in this stage. + *

      + * After {@link #startExpectingBlocks()} is called, the assertion will verify that the next {@code n} proofs are + * indirect proofs with the correct number of sibling hashes; and are followed by a direct proof, at which point + * it passes. + */ +public class IndirectProofsAssertion implements BlockStreamAssertion { + private boolean proofsArePaused; + private int remainingIndirectProofs; + + public IndirectProofsAssertion(final int remainingIndirectProofs) { + this.proofsArePaused = true; + this.remainingIndirectProofs = remainingIndirectProofs; + } + + /** + * Signals that the assertion should now expect proofs to be created, hence blocks to be written. + */ + public void startExpectingBlocks() { + proofsArePaused = false; + } + + @Override + public boolean test(@NonNull final Block block) throws AssertionError { + if (proofsArePaused) { + throw new AssertionError("No blocks should be written when proofs are unavailable"); + } else { + final var items = block.items(); + final var proofItem = items.getLast(); + assertTrue(proofItem.hasBlockProof(), "Block proof is expected as the last item"); + final var proof = proofItem.blockProofOrThrow(); + if (remainingIndirectProofs == 0) { + assertTrue(proof.siblingHashes().isEmpty(), "No sibling hashes should be present on a direct proof"); + return true; + } else { + assertEquals( + // Two sibling hashes per indirection level + 2 * remainingIndirectProofs, + proof.siblingHashes().size(), + "Wrong number of sibling hashes for indirect proof"); + } + remainingIndirectProofs--; + return false; + } + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/RecordStreamAssertion.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/RecordStreamAssertion.java index d7d5b0f718da..765f34afbaa4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/RecordStreamAssertion.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/streams/assertions/RecordStreamAssertion.java @@ -22,8 +22,7 @@ /** * Implements an assertion about one or more {@link RecordStreamItem}'s that should appear in the - * record stream during---or shortly after---execution of a {@link - * HapiSpec}. + * record stream during---or shortly after---execution of a {@link HapiSpec}. * *

      Typical implementations will be stateful, and will be constructed with their "parent" {@link HapiSpec}. */ diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java index 5e4035e95c4f..186f100cc4b6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/verification/traceability/SidecarWatcher.java @@ -17,13 +17,13 @@ package com.hedera.services.bdd.spec.verification.traceability; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.guaranteedExtantDir; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; import static com.hedera.services.bdd.spec.transactions.TxnUtils.triggerAndCloseAtLeastOneFileIfNotInterrupted; import static java.nio.charset.StandardCharsets.US_ASCII; import static org.junit.jupiter.api.Assertions.assertTrue; -import com.hedera.services.bdd.junit.support.RecordStreamAccess; import com.hedera.services.bdd.junit.support.StreamDataListener; +import com.hedera.services.bdd.junit.support.StreamFileAccess; import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.stream.proto.TransactionSidecarRecord; import edu.umd.cs.findbugs.annotations.NonNull; @@ -43,7 +43,7 @@ * A class that simultaneously, *

        *
      1. Listens for the actual sidecars written at the given location via - * the {@link RecordStreamAccess#RECORD_STREAM_ACCESS} utility; and,
      2. + * the {@link StreamFileAccess#STREAM_FILE_ACCESS} utility; and, *
      3. Registers expected sidecars.
      4. *
      * When a client has registered all its expectations with a {@link SidecarWatcher} @@ -71,7 +71,7 @@ public class SidecarWatcher { private record ConstructionDetails(String creatingThread, String stackTrace) {} public SidecarWatcher(@NonNull final Path path) { - this.unsubscribe = RECORD_STREAM_ACCESS.subscribe(guaranteedExtantDir(path), new StreamDataListener() { + this.unsubscribe = STREAM_FILE_ACCESS.subscribe(guaranteedExtantDir(path), new StreamDataListener() { @Override public void onNewSidecar(@NonNull final TransactionSidecarRecord sidecar) { actualSidecars.add(sidecar); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java deleted file mode 100644 index f559d6dbd46e..000000000000 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/compose/PerpetualLocalCalls.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2020-2024 Hedera Hashgraph, LLC - * - * 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 com.hedera.services.bdd.suites.compose; - -import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.isLiteralResult; -import static com.hedera.services.bdd.spec.assertions.ContractFnResultAsserts.resultWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.contractCallLocal; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.runWithProvider; -import static com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION; -import static com.hedera.services.bdd.suites.contract.Utils.getABIFor; -import static java.util.concurrent.TimeUnit.MINUTES; - -import com.hedera.services.bdd.spec.HapiSpec; -import com.hedera.services.bdd.spec.HapiSpecOperation; -import com.hedera.services.bdd.spec.SpecOperation; -import com.hedera.services.bdd.spec.infrastructure.OpProvider; -import com.hedera.services.bdd.suites.HapiSuite; -import java.math.BigInteger; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.stream.Stream; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.junit.jupiter.api.DynamicTest; - -public class PerpetualLocalCalls extends HapiSuite { - - private static final Logger log = LogManager.getLogger(PerpetualLocalCalls.class); - public static final String CHILD_STORAGE = "ChildStorage"; - - private AtomicLong duration = new AtomicLong(Long.MAX_VALUE); - private AtomicReference unit = new AtomicReference<>(MINUTES); - private AtomicInteger maxOpsPerSec = new AtomicInteger(100); - private AtomicInteger totalBeforeFailure = new AtomicInteger(0); - - public static void main(String... args) { - new PerpetualLocalCalls().runSuiteSync(); - } - - @Override - public List> getSpecsInSuite() { - return List.of(localCallsForever()); - } - - final Stream localCallsForever() { - return defaultHapiSpec("LocalCallsForever") - .given() - .when() - .then(runWithProvider(localCallsFactory()) - .lasting(duration::get, unit::get) - .maxOpsPerSec(maxOpsPerSec::get)); - } - - private Function localCallsFactory() { - return spec -> new OpProvider() { - @Override - public List suggestedInitializers() { - return List.of(uploadInitCode(CHILD_STORAGE), contractCreate(CHILD_STORAGE)); - } - - @Override - public Optional get() { - var op = contractCallLocal(CHILD_STORAGE, "getMyValue") - .noLogging() - .has(resultWith() - .resultThruAbi( - getABIFor(FUNCTION, "getMyValue", CHILD_STORAGE), - isLiteralResult(new Object[] {BigInteger.valueOf(73)}))); - var soFar = totalBeforeFailure.getAndIncrement(); - if (soFar % 1000 == 0) { - log.info("--- {}", soFar); - } - return Optional.of(op); - } - }; - } - - @Override - protected Logger getResultsLogger() { - return log; - } -} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java index 9b93a3a48dad..5a691d768d2d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractCallSuite.java @@ -17,6 +17,7 @@ package com.hedera.services.bdd.suites.contract.hapi; import static com.hedera.node.app.hapi.utils.EthSigsUtils.recoverAddressFromPubKey; +import static com.hedera.services.bdd.junit.TestTags.ADHOC; import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; import static com.hedera.services.bdd.spec.HapiPropertySource.asContract; import static com.hedera.services.bdd.spec.HapiPropertySource.asContractString; @@ -71,9 +72,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyListNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordStreamMustIncludeNoFailuresFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sidecarIdValidator; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustIncludeNoFailuresFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; @@ -164,8 +165,7 @@ import org.junit.jupiter.api.Tag; @Tag(SMART_CONTRACT) -// @Tag(ADHOC) -@Tag("ONEOFF") +@Tag(ADHOC) public class ContractCallSuite { public static final String TOKEN = "yahcliToken"; @@ -245,7 +245,7 @@ final Stream repeatedCreate2FailsWithInterpretableActionSidecars() final var secondCreation = "secondCreation"; return defaultHapiSpec("repeatedCreate2FailsWithInterpretableActionSidecars", NONDETERMINISTIC_TRANSACTION_FEES) .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), cryptoCreate(ACCOUNT).balance(ONE_MILLION_HBARS), uploadInitCode(contract), contractCreate(contract)) @@ -275,7 +275,7 @@ final Stream insufficientGasToPrecompileFailsWithInterpretableActio final var tokenInfoFn = new Function("getTokenInfo(address)"); return defaultHapiSpec("insufficientGasToPrecompileFailsWithInterpretableActionSidecars") .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), uploadInitCode(contract), contractCreate(contract)) .when(tokenCreate("someToken").exposingAddressTo(someTokenAddress::set)) @@ -307,7 +307,7 @@ final Stream hollowCreationFailsCleanly() { final var contract = "HollowAccountCreator"; return defaultHapiSpec("HollowCreationFailsCleanly", FULLY_NONDETERMINISTIC) .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), uploadInitCode(contract), contractCreate(contract)) .when(contractCall(contract, "testCallFoo", randomHeadlongAddress(), BigInteger.valueOf(500_000L)) @@ -1971,7 +1971,7 @@ final Stream hscsEvm010ReceiverMustSignContractTx() { NONDETERMINISTIC_FUNCTION_PARAMETERS, NONDETERMINISTIC_TRANSACTION_FEES) .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), newKeyNamed(RECEIVER_KEY), cryptoCreate(ACC) .balance(5 * ONE_HUNDRED_HBARS) @@ -2569,7 +2569,7 @@ final Stream callToNonExtantLongZeroAddressUsesTargetedAddress() { final var nonExtantMirrorAddress = asHeadlongAddress("0xE8D4A50FFF"); return defaultHapiSpec("callToNonExtantLongZeroAddressUsesTargetedAddress") .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), uploadInitCode(contract), contractCreate(contract)) .when() @@ -2583,7 +2583,7 @@ final Stream callToNonExtantEvmAddressUsesTargetedAddress() { final var nonExtantEvmAddress = asHeadlongAddress(TxnUtils.randomUtf8Bytes(20)); return defaultHapiSpec("callToNonExtantEvmAddressUsesTargetedAddress") .given( - streamMustIncludeNoFailuresFrom(sidecarIdValidator()), + recordStreamMustIncludeNoFailuresFrom(sidecarIdValidator()), uploadInitCode(contract), contractCreate(contract)) .when() diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java index c38ee70f02aa..cf9beecc0905 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractKeysStillWorkAsExpectedSuite.java @@ -44,9 +44,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.childRecordsCheck; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.noOp; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordStreamMustIncludePassFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordedChildBodyWithId; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustInclude; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_FUNCTION_PARAMETERS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_NONCE; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; @@ -85,21 +85,22 @@ final Stream approvalFallbacksRequiredWithoutTopLevelSigAccess() { final AtomicReference
      bReceiverAddr = new AtomicReference<>(); return hapiTest( - streamMustInclude(recordedChildBodyWithId(TOKEN_UNIT_FROM_TO_OTHERS_TXN, 1, (spec, txn) -> { - if (txn.hasNodeStakeUpdate()) { - // Avoid asserting something about an end-of-staking-period NodeStakeUpdate in CI - return; - } - final var tokenTransfers = txn.getCryptoTransfer().getTokenTransfersList(); - assertEquals(1, tokenTransfers.size()); - final var tokenTransfer = tokenTransfers.getFirst(); - for (final var adjust : tokenTransfer.getTransfersList()) { - if (adjust.getAmount() < 0) { - // The debit should have been automatically converted to an approval - assertTrue(adjust.getIsApproval()); - } - } - })), + recordStreamMustIncludePassFrom( + recordedChildBodyWithId(TOKEN_UNIT_FROM_TO_OTHERS_TXN, 1, (spec, txn) -> { + if (txn.hasNodeStakeUpdate()) { + // Avoid asserting something about an end-of-staking-period NodeStakeUpdate in CI + return; + } + final var tokenTransfers = txn.getCryptoTransfer().getTokenTransfersList(); + assertEquals(1, tokenTransfers.size()); + final var tokenTransfer = tokenTransfers.getFirst(); + for (final var adjust : tokenTransfer.getTransfersList()) { + if (adjust.getAmount() < 0) { + // The debit should have been automatically converted to an approval + assertTrue(adjust.getIsApproval()); + } + } + })), someWellKnownTokensAndAccounts( fungibleTokenMirrorAddr, nonFungibleTokenMirrorAddr, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java index a6418db0ea2e..004a47a4a454 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java @@ -18,7 +18,7 @@ import static com.hedera.services.bdd.junit.hedera.NodeSelector.byNodeId; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.guaranteedExtantDir; -import static com.hedera.services.bdd.junit.support.RecordStreamAccess.RECORD_STREAM_ACCESS; +import static com.hedera.services.bdd.junit.support.StreamFileAccess.STREAM_FILE_ACCESS; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.dsl.operations.transactions.TouchBalancesOperation.touchBalanceOf; import static com.hedera.services.bdd.spec.keys.TrieSigMapGenerator.uniqueWithFullPrefixesFor; @@ -30,7 +30,7 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.createHollow; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustIncludeNoFailuresFrom; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordStreamMustIncludeNoFailuresFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.visibleNonSyntheticItems; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; @@ -107,7 +107,7 @@ public class NaturalDispatchOrderingTest { @BeforeAll static void setUp(@NonNull final TestLifecycle testLifecycle) { testLifecycle.doAdhoc(withOpContext((spec, opLog) -> { - unsubscribe = RECORD_STREAM_ACCESS.subscribe( + unsubscribe = STREAM_FILE_ACCESS.subscribe( guaranteedExtantDir(spec.streamsLoc(byNodeId(0))), new StreamDataListener() {}); triggerAndCloseAtLeastOneFile(spec); })); @@ -133,7 +133,7 @@ static void cleanUp() { @DisplayName("reversible user stream items are as expected") final Stream reversibleUserItemsAsExpected() { return hapiTest( - streamMustIncludeNoFailuresFrom( + recordStreamMustIncludeNoFailuresFrom( visibleNonSyntheticItems(reversibleUserValidator(), "firstCreation", "duplicateCreation")), scheduleCreate( "scheduledTxn", @@ -172,7 +172,7 @@ final Stream reversibleChildAndRemovablePrecedingItemsAsExpected( @Contract(contract = "LowLevelCall") SpecContract lowLevelCallContract) { final var transferFunction = new Function("transferNFTThanRevertCall(address,address,address,int64)"); return hapiTest( - streamMustIncludeNoFailuresFrom(visibleNonSyntheticItems( + recordStreamMustIncludeNoFailuresFrom(visibleNonSyntheticItems( reversibleChildValidator(), "fullSuccess", "containedRevert", "fullRevert")), nonFungibleToken.treasury().authorizeContract(transferContract), transferContract @@ -231,7 +231,7 @@ final Stream reversibleScheduleAndRemovablePrecedingItemsAsExpected @Account(centBalance = 7, maxAutoAssociations = UNLIMITED_AUTO_ASSOCIATION_SLOTS) SpecAccount insolventPayer) { return hapiTest( - streamMustIncludeNoFailuresFrom( + recordStreamMustIncludeNoFailuresFrom( visibleNonSyntheticItems(reversibleScheduleValidator(), "committed", "rolledBack")), firstToken.treasury().transferUnitsTo(solventPayer, 10, firstToken), secondToken.treasury().transferUnitsTo(insolventPayer, 10, secondToken), @@ -281,7 +281,7 @@ final Stream removableChildItemsAsExpected( final var startChainFn = new Function("startChain(bytes)"); final var emptyMessage = new byte[0]; return hapiTest( - streamMustIncludeNoFailuresFrom( + recordStreamMustIncludeNoFailuresFrom( visibleNonSyntheticItems(removableChildValidator(), "nestedCreations", "revertedCreations")), outerCreatorContract.call("startChain", emptyMessage).with(txn -> txn.gas(2_000_000) .via("nestedCreations")), @@ -315,7 +315,7 @@ final Stream removableChildItemsAsExpected( @DisplayName("irreversible preceding stream items are as expected") final Stream irreversiblePrecedingItemsAsExpected() { return hapiTest( - streamMustIncludeNoFailuresFrom(visibleNonSyntheticItems( + recordStreamMustIncludeNoFailuresFrom(visibleNonSyntheticItems( irreversiblePrecedingValidator(), "finalizationBySuccess", "finalizationByFailure")), tokenCreate("unassociatedToken"), // Create two hollow accounts to finalize, first by a top-level success and second by failure diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java index 8ffad8f00109..ea11adb35e7b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/SystemFileExportsTest.java @@ -40,12 +40,12 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.nOps; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordStreamMustIncludeNoFailuresFrom; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.recordStreamMustIncludePassFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.selectedItems; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.simulatePostUpgradeTransaction; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcingContextual; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustIncludeNoFailuresFrom; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.streamMustIncludePassFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.visibleItems; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; @@ -124,7 +124,7 @@ final Stream syntheticNodeDetailsUpdateHappensAtUpgradeBoundary() { }; final AtomicReference> gossipCertificates = new AtomicReference<>(); return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( addressBookExportValidator(grpcCertHashes, gossipCertificates), 1, this::isSysFileUpdate)), given(() -> gossipCertificates.set(generateCertificates(CLASSIC_HAPI_TEST_NETWORK_SIZE))), // This is the genesis transaction @@ -148,7 +148,7 @@ final Stream syntheticFeeSchedulesUpdateHappensAtUpgradeBoundary() final var upgradeFeeSchedules = CurrentAndNextFeeSchedule.parseFrom(SYS_FILE_SERDES.get(111L).toRawFile(feeSchedulesJson, null)); return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.feeSchedules", upgradeFeeSchedules, SystemFileExportsTest::parseFeeSchedule), 2, @@ -191,7 +191,7 @@ final Stream syntheticThrottlesUpdateHappensAtUpgradeBoundary() thr final var upgradeThrottleDefs = ThrottleDefinitions.parseFrom(SYS_FILE_SERDES.get(123L).toRawFile(throttlesJson, null)); return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.throttleDefinitions", upgradeThrottleDefs, @@ -232,7 +232,7 @@ final Stream syntheticPropertyOverridesUpdateHappensAtUpgradeBounda final var upgradePropOverrides = ServicesConfigurationList.parseFrom(SYS_FILE_SERDES.get(121L).toRawFile(overrideProperties, null)); return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.networkProperties", upgradePropOverrides, @@ -267,7 +267,7 @@ final Stream syntheticPropertyOverridesUpdateHappensAtUpgradeBounda @GenesisHapiTest final Stream syntheticPropertyOverridesUpdateCanBeEmptyFile() { return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.networkProperties", ServicesConfigurationList.getDefaultInstance(), @@ -308,7 +308,7 @@ final Stream syntheticPermissionOverridesUpdateHappensAtUpgradeBoun final var upgradePermissionOverrides = ServicesConfigurationList.parseFrom(SYS_FILE_SERDES.get(122L).toRawFile(overridePermissions, null)); return hapiTest( - streamMustIncludePassFrom(selectedItems( + recordStreamMustIncludePassFrom(selectedItems( sysFileExportValidator( "files.hapiPermissions", upgradePermissionOverrides, @@ -347,7 +347,7 @@ final Stream syntheticPermissionOverridesUpdateHappensAtUpgradeBoun final Stream syntheticFileCreationsMatchQueries() { final AtomicReference> preGenesisContents = new AtomicReference<>(); return hapiTest( - streamMustIncludeNoFailuresFrom(visibleItems(validatorFor(preGenesisContents), "genesisTxn")), + recordStreamMustIncludeNoFailuresFrom(visibleItems(validatorFor(preGenesisContents), "genesisTxn")), getSystemFiles(preGenesisContents::set), cryptoCreate("firstUser").via("genesisTxn"), // Assert the first created entity still has the expected number diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/tss/RepeatableTssTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/tss/RepeatableTssTests.java new file mode 100644 index 000000000000..cec1d2c1e7f4 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/tss/RepeatableTssTests.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.suites.tss; + +import static com.hedera.services.bdd.junit.RepeatableReason.NEEDS_TSS_CONTROL; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.utilops.TssVerbs.startIgnoringTssSignatureRequests; +import static com.hedera.services.bdd.spec.utilops.TssVerbs.stopIgnoringTssSignatureRequests; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockStreamMustIncludePassFrom; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doAdhoc; + +import com.hedera.node.app.blocks.BlockStreamManager; +import com.hedera.services.bdd.junit.RepeatableHapiTest; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; +import com.hedera.services.bdd.spec.utilops.streams.assertions.IndirectProofsAssertion; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; + +/** + * TSS tests that require repeatable mode to run. + */ +public class RepeatableTssTests { + /** + * A test that simulates the behavior of the {@link FakeTssBaseService} under specific conditions + * related to signature requests and block creation. + * + *

      This test follows three main steps:

      + *
        + *
      • Instructs the {@link FakeTssBaseService} to start ignoring signature requests and + * produces several blocks. In this scenario, each transaction is placed into its own round + * since the service is operating in repeatable mode.
      • + *
      • Verifies that no blocks are written, as no block proofs are available, which is the + * expected behavior when the service is ignoring signature requests.
      • + *
      • Reactivates the {@link FakeTssBaseService}, creates another block, and verifies that + * the {@link BlockStreamManager} processes pending block proofs. It checks that the expected + * blocks are written within a brief period after the service resumes normal behavior.
      • + *
      + * + *

      The test ensures that block production halts when block proofs are unavailable and + * verifies that the system can catch up on pending proofs when the service resumes.

      + */ + @RepeatableHapiTest(NEEDS_TSS_CONTROL) + Stream blockStreamManagerCatchesUpWithIndirectProofs() { + final var indirectProofsAssertion = new IndirectProofsAssertion(2); + return hapiTest( + startIgnoringTssSignatureRequests(), + blockStreamMustIncludePassFrom(spec -> indirectProofsAssertion), + // Each transaction is placed into its own round and hence block with default config + cryptoCreate("firstIndirectProof"), + cryptoCreate("secondIndirectProof"), + stopIgnoringTssSignatureRequests(), + doAdhoc(indirectProofsAssertion::startExpectingBlocks), + cryptoCreate("directProof")); + } +} From 33a84b100883ce53ecccd030534ac86af75a9d4e Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Tue, 10 Sep 2024 12:04:27 -0500 Subject: [PATCH 03/16] fix: set active `ExchangeRateSet` on triggered txn receipts (#15396) Signed-off-by: Michael Tinker --- .../app/workflows/handle/HandleWorkflow.java | 3 ++- .../handle/stack/SavepointStackImpl.java | 22 ++++++++++++------- .../standalone/ExecutorComponent.java | 3 +++ .../standalone/TransactionExecutors.java | 5 ++++- .../support/translators/BaseTranslator.java | 2 +- .../hip993/NaturalDispatchOrderingTest.java | 4 ++++ 6 files changed, 28 insertions(+), 11 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java index 96e414535a47..738b27c3ce5d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/HandleWorkflow.java @@ -363,7 +363,8 @@ private HandleOutput execute(@NonNull final UserTxn userTxn) { dispatchProcessor.processDispatch(dispatch); updateWorkflowMetrics(userTxn); } - final var handleOutput = userTxn.stack().buildHandleOutput(userTxn.consensusNow()); + final var handleOutput = + userTxn.stack().buildHandleOutput(userTxn.consensusNow(), exchangeRateManager.exchangeRates()); // Note that we don't yet support producing ONLY blocks, because we haven't integrated // translators from block items to records for answering queries if (blockStreamConfig.streamRecords()) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java index 63c6ad4a8bba..78dd643535c2 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/stack/SavepointStackImpl.java @@ -29,6 +29,7 @@ import com.hedera.hapi.block.stream.BlockItem; import com.hedera.hapi.node.base.TransactionID; +import com.hedera.hapi.node.transaction.ExchangeRateSet; import com.hedera.node.app.blocks.impl.BoundaryStateChangeListener; import com.hedera.node.app.blocks.impl.KVStateChangeListener; import com.hedera.node.app.blocks.impl.PairedStreamBuilder; @@ -59,8 +60,6 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** * A stack of savepoints scoped to a dispatch. Each savepoint captures the state of the {@link State} at the time @@ -68,7 +67,6 @@ * the stream builders created in the savepoint. */ public class SavepointStackImpl implements HandleContext.SavepointStack, State { - private static final Logger log = LogManager.getLogger(SavepointStackImpl.class); private final State state; private final Deque stack = new ArrayDeque<>(); private final Map writableStatesMap = new HashMap<>(); @@ -444,9 +442,11 @@ Savepoint peek() { * Builds all the records for the user transaction. * * @param consensusTime consensus time of the transaction + * @param exchangeRates the active exchange rates * @return the stream of records */ - public HandleOutput buildHandleOutput(@NonNull final Instant consensusTime) { + public HandleOutput buildHandleOutput( + @NonNull final Instant consensusTime, @NonNull final ExchangeRateSet exchangeRates) { final List blockItems; Instant lastAssignedConsenusTime = consensusTime; if (streamMode == RECORDS) { @@ -481,10 +481,16 @@ public HandleOutput buildHandleOutput(@NonNull final Instant consensusTime) { final var consensusNow = consensusTime.plusNanos((long) i - indexOfUserRecord); lastAssignedConsenusTime = consensusNow; builder.consensusTimestamp(consensusNow); - if (i > indexOfUserRecord && builder.category() != SCHEDULED) { - // Only set exchange rates on transactions preceding the user transaction, since - // no subsequent child can change the exchange rate - builder.parentConsensus(consensusTime).exchangeRate(null); + if (i > indexOfUserRecord) { + if (builder.category() != SCHEDULED) { + // Only set exchange rates on transactions preceding the user transaction, since + // no subsequent child can change the exchange rate + builder.parentConsensus(consensusTime).exchangeRate(null); + } else { + // But for backward compatibility keep setting rates on scheduled receipts, c.f. + // https://github.com/hashgraph/hedera-services/issues/15393 + builder.exchangeRate(exchangeRates); + } } switch (streamMode) { case RECORDS -> records.add(((RecordStreamBuilder) builder).build()); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java index 7b50038e619f..1eb3dc722971 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/ExecutorComponent.java @@ -19,6 +19,7 @@ import com.hedera.node.app.authorization.AuthorizerInjectionModule; import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.config.ConfigProviderImpl; +import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.service.contract.impl.ContractServiceImpl; import com.hedera.node.app.service.file.impl.FileServiceImpl; import com.hedera.node.app.services.ServicesInjectionModule; @@ -81,5 +82,7 @@ interface Builder { StateNetworkInfo stateNetworkInfo(); + ExchangeRateManager exchangeRateManager(); + StandaloneDispatchFactory standaloneDispatchFactory(); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java index 7f09f43fb1dd..75772c5e0dc7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java @@ -52,11 +52,14 @@ public TransactionExecutor newExecutor(@NonNull final State state, @NonNull fina final var executor = newExecutorComponent(properties); executor.initializer().accept(state); executor.stateNetworkInfo().initFrom(state); + final var exchangeRateManager = executor.exchangeRateManager(); return (transactionBody, consensusNow, operationTracers) -> { final var dispatch = executor.standaloneDispatchFactory().newDispatch(state, transactionBody, consensusNow); OPERATION_TRACERS.set(List.of(operationTracers)); executor.dispatchProcessor().processDispatch(dispatch); - return dispatch.stack().buildHandleOutput(consensusNow).recordsOrThrow(); + return dispatch.stack() + .buildHandleOutput(consensusNow, exchangeRateManager.exchangeRates()) + .recordsOrThrow(); }; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java index a6c67d3de324..911d1ff70bc2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/translators/BaseTranslator.java @@ -329,7 +329,7 @@ public SingleTransactionRecord recordFrom(@NonNull final BlockTransactionParts p if (followsUserRecord && !parts.transactionIdOrThrow().scheduled()) { recordBuilder.parentConsensusTimestamp(asTimestamp(userTimestamp)); } - if (!followsUserRecord) { + if (!followsUserRecord || parts.transactionIdOrThrow().scheduled()) { // Only preceding and user transactions get exchange rates in their receipts; note that // auto-account creations are always preceding dispatches and so get exchange rates receiptBuilder.exchangeRate(activeRates); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java index 004a47a4a454..906f44b67b4b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip993/NaturalDispatchOrderingTest.java @@ -51,6 +51,7 @@ import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import com.esaulpaugh.headlong.abi.Function; import com.hedera.node.app.spi.workflows.HandleContext.TransactionCategory; @@ -455,6 +456,9 @@ private static void assertParentChildStructure( withNonce(userTransactionID, nextExpectedNonce++ - postTriggeredOffset), following.txnId()); assertEquals(userConsensusTime, following.parentConsensusTimestamp()); } + if (following.txnId().getScheduled()) { + assertTrue(following.txnRecord().getReceipt().hasExchangeRate()); + } } } From 8ac8b5880febff58421042da8775e4c462bab5ee Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Tue, 10 Sep 2024 20:55:06 +0200 Subject: [PATCH 04/16] chore: fix/disable hammer tests that are not working (#15370) Signed-off-by: Jendrik Johannes --- platform-sdk/swirlds-merkledb/build.gradle.kts | 1 + .../merkledb/files/DataFileCollectionCompactionHammerTest.java | 2 ++ .../com/swirlds/merkledb/files/DataFileReaderHammerTest.java | 2 ++ .../files/MemoryIndexDiskKeyValueStoreCompactionHammerTest.java | 2 ++ 4 files changed, 7 insertions(+) diff --git a/platform-sdk/swirlds-merkledb/build.gradle.kts b/platform-sdk/swirlds-merkledb/build.gradle.kts index a17fc9fa72ee..1c613517ef36 100644 --- a/platform-sdk/swirlds-merkledb/build.gradle.kts +++ b/platform-sdk/swirlds-merkledb/build.gradle.kts @@ -72,5 +72,6 @@ hammerModuleInfo { requires("org.apache.logging.log4j.core") requires("org.junit.jupiter.api") requires("org.junit.jupiter.params") + runtimeOnly("com.swirlds.common.test.fixtures") runtimeOnly("com.swirlds.config.impl") } diff --git a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileCollectionCompactionHammerTest.java b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileCollectionCompactionHammerTest.java index 9141662de5ff..ecd08443013a 100644 --- a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileCollectionCompactionHammerTest.java +++ b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileCollectionCompactionHammerTest.java @@ -39,6 +39,7 @@ import org.apache.logging.log4j.core.config.Configurator; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Tags; import org.junit.jupiter.api.Test; @@ -49,6 +50,7 @@ /** * Hammer the compaction subsystem with as many small compactions as possible to try to overwhelm it. */ +@Disabled("This test needs to be investigated") class DataFileCollectionCompactionHammerTest { @BeforeAll diff --git a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileReaderHammerTest.java b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileReaderHammerTest.java index 125a58dd770a..9361370f4aad 100644 --- a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileReaderHammerTest.java +++ b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/DataFileReaderHammerTest.java @@ -36,9 +36,11 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReferenceArray; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +@Disabled("This test needs to be investigated") public class DataFileReaderHammerTest { @Test diff --git a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/MemoryIndexDiskKeyValueStoreCompactionHammerTest.java b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/MemoryIndexDiskKeyValueStoreCompactionHammerTest.java index 382268ea1212..d2981e928992 100644 --- a/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/MemoryIndexDiskKeyValueStoreCompactionHammerTest.java +++ b/platform-sdk/swirlds-merkledb/src/hammer/java/com/swirlds/merkledb/files/MemoryIndexDiskKeyValueStoreCompactionHammerTest.java @@ -42,6 +42,7 @@ import org.apache.logging.log4j.core.config.Configurator; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.function.ThrowingSupplier; import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; @@ -51,6 +52,7 @@ * Hammers the {@link MemoryIndexDiskKeyValueStore} with a ton of concurrent changes to validate the * index and the data files are in sync and have all the data they should have. */ +@Disabled("This test needs to be investigated") class MemoryIndexDiskKeyValueStoreCompactionHammerTest { /** Temporary directory provided by JUnit */ From ff75da057f96d1b46d9b7046813ab5218727e13f Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Tue, 10 Sep 2024 14:01:55 -0500 Subject: [PATCH 05/16] chore: default `TransactionExecutor` simulator to no-op system contract authorization checks (#15392) Signed-off-by: Michael Tinker --- .../standalone/TransactionExecutors.java | 5 +- .../impl/NoopVerificationStrategies.java | 42 ++++++++++++++ .../hedera-app/src/main/java/module-info.java | 1 + .../impl/NoopVerificationStrategiesTest.java | 42 ++++++++++++++ .../impl/ContractServiceComponent.java | 2 + .../contract/impl/ContractServiceImpl.java | 10 +++- .../scope/DefaultVerificationStrategies.java | 58 +++++++++++++++++++ .../exec/scope/VerificationStrategies.java | 50 ++++------------ .../hts/create/ClassicCreatesCall.java | 2 +- 9 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/NoopVerificationStrategies.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/impl/NoopVerificationStrategiesTest.java create mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/DefaultVerificationStrategies.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java index 75772c5e0dc7..fd2d7df81f39 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/TransactionExecutors.java @@ -16,6 +16,8 @@ package com.hedera.node.app.workflows.standalone; +import static com.hedera.node.app.workflows.standalone.impl.NoopVerificationStrategies.NOOP_VERIFICATION_STRATEGIES; + import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.config.ConfigProviderImpl; import com.hedera.node.app.service.contract.impl.ContractServiceImpl; @@ -71,7 +73,8 @@ private ExecutorComponent newExecutorComponent(@NonNull final Map Decision.VALID; + + @Override + public VerificationStrategy activatingOnlyContractKeysFor( + @NonNull final Address sender, + final boolean requiresDelegatePermission, + @NonNull final HederaNativeOperations nativeOperations) { + return NOOP_VERIFICATION_STRATEGY; + } +} diff --git a/hedera-node/hedera-app/src/main/java/module-info.java b/hedera-node/hedera-app/src/main/java/module-info.java index 930afde3c8e0..eeeb435abd80 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -49,6 +49,7 @@ requires io.netty.transport.classes.epoll; requires io.netty.transport; requires org.apache.commons.lang3; + requires org.hyperledger.besu.datatypes; requires static com.github.spotbugs.annotations; requires static com.google.auto.service; requires static java.compiler; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/impl/NoopVerificationStrategiesTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/impl/NoopVerificationStrategiesTest.java new file mode 100644 index 000000000000..50a3cec3eb56 --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/impl/NoopVerificationStrategiesTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.workflows.standalone.impl; + +import static com.hedera.node.app.workflows.standalone.impl.NoopVerificationStrategies.NOOP_VERIFICATION_STRATEGIES; +import static org.junit.jupiter.api.Assertions.*; + +import com.hedera.hapi.node.base.Key; +import com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategy; +import org.hyperledger.besu.datatypes.Address; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class NoopVerificationStrategiesTest { + @Mock + private HederaNativeOperations nativeOperations; + + @Test + void allKeysAreValid() { + final var subject = NOOP_VERIFICATION_STRATEGIES.activatingOnlyContractKeysFor( + Address.ALTBN128_ADD, true, nativeOperations); + assertSame(VerificationStrategy.Decision.VALID, subject.decideForPrimitive(Key.DEFAULT)); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java index 678231f53943..a0c3510a09e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceComponent.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; import com.hedera.node.app.service.contract.impl.handlers.ContractHandlers; import com.hedera.node.app.spi.signatures.SignatureVerifier; import dagger.BindsInstance; @@ -35,6 +36,7 @@ interface Factory { ContractServiceComponent create( @BindsInstance InstantSource instantSource, @BindsInstance SignatureVerifier signatureVerifier, + @BindsInstance VerificationStrategies verificationStrategies, @BindsInstance @Nullable Supplier> addOnTracers); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java index 5cc0e1bc88bb..bcd75ced8f8d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/ContractServiceImpl.java @@ -19,6 +19,8 @@ import static java.util.Objects.requireNonNull; import com.hedera.node.app.service.contract.ContractService; +import com.hedera.node.app.service.contract.impl.exec.scope.DefaultVerificationStrategies; +import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; import com.hedera.node.app.service.contract.impl.handlers.ContractHandlers; import com.hedera.node.app.service.contract.impl.schemas.V0490ContractSchema; import com.hedera.node.app.service.contract.impl.schemas.V0500ContractSchema; @@ -27,6 +29,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.List; +import java.util.Optional; import java.util.function.Supplier; import org.hyperledger.besu.evm.tracing.OperationTracer; @@ -40,11 +43,13 @@ public class ContractServiceImpl implements ContractService { private final ContractServiceComponent component; public ContractServiceImpl(@NonNull final AppContext appContext) { - this(appContext, null); + this(appContext, null, null); } public ContractServiceImpl( - @NonNull final AppContext appContext, @Nullable final Supplier> addOnTracers) { + @NonNull final AppContext appContext, + @Nullable final VerificationStrategies verificationStrategies, + @Nullable final Supplier> addOnTracers) { requireNonNull(appContext); this.component = DaggerContractServiceComponent.factory() .create( @@ -52,6 +57,7 @@ public ContractServiceImpl( // (FUTURE) Inject the signature verifier instance into the IsAuthorizedSystemContract // C.f. https://github.com/hashgraph/hedera-services/issues/14248 appContext.signatureVerifier(), + Optional.ofNullable(verificationStrategies).orElseGet(DefaultVerificationStrategies::new), addOnTracers); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/DefaultVerificationStrategies.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/DefaultVerificationStrategies.java new file mode 100644 index 000000000000..6104cca14758 --- /dev/null +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/DefaultVerificationStrategies.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.node.app.service.contract.impl.exec.scope; + +import static com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations.MISSING_ENTITY_NUMBER; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.maybeMissingNumberOf; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; + +import com.hedera.hapi.node.base.ContractID; +import edu.umd.cs.findbugs.annotations.NonNull; +import org.hyperledger.besu.datatypes.Address; + +/** + * Implements the default {@link VerificationStrategies} for use in signature activation tests. + */ +public class DefaultVerificationStrategies implements VerificationStrategies { + /** + * Returns a {@link VerificationStrategy} that will activate only delegatable contract id and + * contract id keys (the latter if delegatable permissions are not required). + * + *

      This is the standard strategy under the approval-based security model, where a contract gains + * authorization for an entity only by having its id or address added to that entity's controlling + * key structure. + * + * @param sender the contract whose keys are to be activated + * @param requiresDelegatePermission whether the strategy should require a delegatable contract id key + * @param nativeOperations the operations to use for looking up the contract's number + * @return a {@link VerificationStrategy} that will activate only delegatable contract id and contract id keys + */ + public VerificationStrategy activatingOnlyContractKeysFor( + @NonNull final Address sender, + final boolean requiresDelegatePermission, + @NonNull final HederaNativeOperations nativeOperations) { + final var contractNum = maybeMissingNumberOf(sender, nativeOperations); + if (contractNum == MISSING_ENTITY_NUMBER) { + throw new IllegalArgumentException("Cannot verify against missing contract " + sender); + } + return new ActiveContractVerificationStrategy( + ContractID.newBuilder().contractNum(contractNum).build(), + tuweniToPbjBytes(sender), + requiresDelegatePermission, + ActiveContractVerificationStrategy.UseTopLevelSigs.NO); + } +} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategies.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategies.java index a9bfa7963466..3991e1e552ab 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategies.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/VerificationStrategies.java @@ -16,52 +16,24 @@ package com.hedera.node.app.service.contract.impl.exec.scope; -import static com.hedera.node.app.service.contract.impl.exec.scope.HederaNativeOperations.MISSING_ENTITY_NUMBER; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.maybeMissingNumberOf; -import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; - -import com.hedera.hapi.node.base.ContractID; -import com.hedera.node.app.service.contract.impl.exec.scope.ActiveContractVerificationStrategy.UseTopLevelSigs; import edu.umd.cs.findbugs.annotations.NonNull; -import javax.inject.Inject; -import javax.inject.Singleton; import org.hyperledger.besu.datatypes.Address; /** * Provides {@link VerificationStrategy} instances for use in signature activation tests. */ -@Singleton -public class VerificationStrategies { - @Inject - public VerificationStrategies() { - // Dagger2 - } - +public interface VerificationStrategies { /** - * Returns a {@link VerificationStrategy} that will activate only delegatable contract id and - * contract id keys (the latter if delegatable permissions are not required). - * - *

      This is the standard strategy under the approval-based security model, where a contract gains - * authorization for an entity only by having its id or address added to that entity's controlling - * key structure. + * Returns a {@link VerificationStrategy} to use based on the given sender address, delegate + * permissions requirements, and Hedera native operations. * - * @param sender the contract whose keys are to be activated - * @param requiresDelegatePermission whether the strategy should require a delegatable contract id key - * @param nativeOperations the operations to use for looking up the contract's number - * @return a {@link VerificationStrategy} that will activate only delegatable contract id and contract id keys + * @param sender the sender address + * @param requiresDelegatePermission whether the sender is using {@code DELEGATECALL} + * @param nativeOperations the native Hedera operations + * @return the {@link VerificationStrategy} to use */ - public VerificationStrategy activatingOnlyContractKeysFor( - @NonNull final Address sender, - final boolean requiresDelegatePermission, - @NonNull final HederaNativeOperations nativeOperations) { - final var contractNum = maybeMissingNumberOf(sender, nativeOperations); - if (contractNum == MISSING_ENTITY_NUMBER) { - throw new IllegalArgumentException("Cannot verify against missing contract " + sender); - } - return new ActiveContractVerificationStrategy( - ContractID.newBuilder().contractNum(contractNum).build(), - tuweniToPbjBytes(sender), - requiresDelegatePermission, - UseTopLevelSigs.NO); - } + VerificationStrategy activatingOnlyContractKeysFor( + @NonNull Address sender, + boolean requiresDelegatePermission, + @NonNull HederaNativeOperations nativeOperations); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 2fd1b17cf2e8..2586626c845d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -189,7 +189,7 @@ private VerificationStrategy verificationStrategyFor( ? new EitherOrVerificationStrategy( verificationStrategy, new SpecificCryptoVerificationStrategy(op.adminKeyOrThrow())) : verificationStrategy; - // And our final dispatch verification strategy must very depending on if + // And our final dispatch verification strategy must vary depending on if // a legacy activation address is active (somewhere on the stack) return stackIncludesActiveAddress(frame, legacyActivation.besuAddress()) ? new EitherOrVerificationStrategy( From 28920805efbc37ea6e8a5f4250e9adf32d04437e Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 10 Sep 2024 15:34:52 -0400 Subject: [PATCH 06/16] fix: 15385 Used `MerkleStateRoot.getReadablePlatformState` where possible to prevent race conditions (#15389) Signed-off-by: Ivan Malygin --- .../block/StateChangesValidator.java | 3 ++- .../platform/ReconnectStateLoader.java | 14 +++++----- .../swirlds/platform/StateInitializer.java | 3 ++- .../com/swirlds/platform/SwirldsPlatform.java | 16 +++++++----- .../platform/builder/PlatformBuilder.java | 4 +-- .../builder/PlatformBuildingBlocks.java | 2 +- .../builder/PlatformComponentBuilder.java | 12 +++++++-- .../cli/GenesisPlatformStateCommand.java | 2 +- .../cli/ValidateAddressBookStateCommand.java | 2 +- .../DefaultTransactionHandler.java | 4 +-- .../DefaultSignedStateValidator.java | 4 +-- .../platform/reconnect/ReconnectLearner.java | 2 +- .../recovery/EventRecoveryWorkflow.java | 25 +++++++++--------- .../state/BirthRoundStateMigration.java | 18 ++++++------- .../platform/state/GenesisStateBuilder.java | 2 +- .../swirlds/platform/state/MerkleRoot.java | 17 +++++++++--- .../platform/state/MerkleStateRoot.java | 18 ++++++++++--- .../com/swirlds/platform/state/State.java | 26 ++++++++++++------- .../platform/state/SwirldStateManager.java | 8 +++--- .../state/SwirldStateManagerUtils.java | 2 +- .../platform/state/TransactionHandler.java | 2 +- .../state/address/AddressBookInitializer.java | 3 ++- .../state/service/PlatformStateService.java | 5 ++-- .../platform/state/signed/SignedState.java | 10 ++++--- .../state/signed/StartupStateUtils.java | 2 +- .../state/snapshot/SavedStateMetadata.java | 2 +- .../state/snapshot/SignedStateFileWriter.java | 2 +- .../swirlds/platform/util/BootstrapUtils.java | 11 +++++--- .../platform/AddressBookInitializerTest.java | 2 +- .../platform/SavedStateMetadataTests.java | 2 +- .../consensus/RoundCalculationUtilsTest.java | 2 +- .../TransactionHandlerTester.java | 3 ++- .../DefaultSignedStateValidatorTests.java | 2 +- .../state/BirthRoundStateMigrationTests.java | 25 ++++++++++-------- .../platform/state/MerkleStateRootTest.java | 21 ++++++++------- .../platform/state/SignedStateTests.java | 6 +++-- .../platform/state/StateRegistryTests.java | 2 +- .../platform/state/StateSigningTests.java | 2 +- .../state/hashlogger/HashLoggerTest.java | 2 +- .../fixtures/state/BlockingSwirldState.java | 4 +-- .../state/RandomSignedStateGenerator.java | 2 +- .../com/swirlds/state/merkle/StateUtils.java | 4 --- .../platform/test/SignedStateUtils.java | 2 +- 43 files changed, 180 insertions(+), 122 deletions(-) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/StateChangesValidator.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/StateChangesValidator.java index d878531ac85f..cddecb100712 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/StateChangesValidator.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/support/validators/block/StateChangesValidator.java @@ -236,7 +236,8 @@ public StateChangesValidator( final var currentVersion = new ServicesSoftwareVersion(servicesVersion, configVersion); final var lifecycles = newPlatformInitLifecycle(bootstrapConfig, currentVersion, migrator, servicesRegistry); state = new MerkleStateRoot(lifecycles, version -> new ServicesSoftwareVersion(version, configVersion)); - state.getPlatformState(); + // initialize the platform state + state.getWritablePlatformState(); migrator.doMigrations( state, servicesRegistry, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ReconnectStateLoader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ReconnectStateLoader.java index 9b35b63716ba..d13802ff269c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ReconnectStateLoader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/ReconnectStateLoader.java @@ -107,7 +107,7 @@ public void loadReconnectState(@NonNull final SignedState signedState) { .init( platform, InitTrigger.RECONNECT, - signedState.getState().getPlatformState().getCreationSoftwareVersion()); + signedState.getState().getReadablePlatformState().getCreationSoftwareVersion()); if (!Objects.equals(signedState.getState().getHash(), reconnectHash)) { throw new IllegalStateException( "State hash is not permitted to change during a reconnect init() call. Previous hash was " @@ -136,13 +136,13 @@ public void loadReconnectState(@NonNull final SignedState signedState) { .getSignatureCollectorStateInput() .put(signedState.reserve("loading reconnect state into sig collector")); platformWiring.consensusSnapshotOverride(Objects.requireNonNull( - signedState.getState().getPlatformState().getSnapshot())); + signedState.getState().getReadablePlatformState().getSnapshot())); platformWiring .getAddressBookUpdateInput() .inject(new AddressBookUpdate( - signedState.getState().getPlatformState().getPreviousAddressBook(), - signedState.getState().getPlatformState().getAddressBook())); + signedState.getState().getReadablePlatformState().getPreviousAddressBook(), + signedState.getState().getReadablePlatformState().getAddressBook())); final AncientMode ancientMode = platformContext .getConfiguration() @@ -151,12 +151,12 @@ public void loadReconnectState(@NonNull final SignedState signedState) { platformWiring.updateEventWindow(new EventWindow( signedState.getRound(), - signedState.getState().getPlatformState().getAncientThreshold(), - signedState.getState().getPlatformState().getAncientThreshold(), + signedState.getState().getReadablePlatformState().getAncientThreshold(), + signedState.getState().getReadablePlatformState().getAncientThreshold(), ancientMode)); final RunningEventHashOverride runningEventHashOverride = new RunningEventHashOverride( - signedState.getState().getPlatformState().getLegacyRunningEventHash(), true); + signedState.getState().getReadablePlatformState().getLegacyRunningEventHash(), true); platformWiring.updateRunningHash(runningEventHashOverride); platformWiring.getPcesWriterRegisterDiscontinuityInput().inject(signedState.getRound()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateInitializer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateInitializer.java index 294343656360..743639e48fd4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateInitializer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/StateInitializer.java @@ -65,7 +65,8 @@ public static void initializeState( previousSoftwareVersion = NO_VERSION; trigger = GENESIS; } else { - previousSoftwareVersion = signedState.getState().getPlatformState().getCreationSoftwareVersion(); + previousSoftwareVersion = + signedState.getState().getReadablePlatformState().getCreationSoftwareVersion(); trigger = RESTART; } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java index bd100f9eb929..e72df9a6ad31 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/SwirldsPlatform.java @@ -200,7 +200,10 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { platformContext, blocks.selfId(), initialState.getRound(), - initialState.getState().getPlatformState().getLowestJudgeGenerationBeforeBirthRoundMode()); + initialState + .getState() + .getReadablePlatformState() + .getLowestJudgeGenerationBeforeBirthRoundMode()); } catch (final IOException e) { throw new UncheckedIOException("Birth round migration failed during PCES migration.", e); } @@ -288,9 +291,9 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { publisher); final Hash legacyRunningEventHash = - initialState.getState().getPlatformState().getLegacyRunningEventHash() == null + initialState.getState().getReadablePlatformState().getLegacyRunningEventHash() == null ? platformContext.getCryptography().getNullHash() - : initialState.getState().getPlatformState().getLegacyRunningEventHash(); + : initialState.getState().getReadablePlatformState().getLegacyRunningEventHash(); final RunningEventHashOverride runningEventHashOverride = new RunningEventHashOverride(legacyRunningEventHash, false); platformWiring.updateRunningHash(runningEventHashOverride); @@ -320,7 +323,8 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { startingRound = 0; platformWiring.updateEventWindow(EventWindow.getGenesisEventWindow(ancientMode)); } else { - initialAncientThreshold = initialState.getState().getPlatformState().getAncientThreshold(); + initialAncientThreshold = + initialState.getState().getReadablePlatformState().getAncientThreshold(); startingRound = initialState.getRound(); platformWiring.sendStateToHashLogger(initialState); @@ -331,7 +335,7 @@ public SwirldsPlatform(@NonNull final PlatformComponentBuilder builder) { savedStateController.registerSignedStateFromDisk(initialState); platformWiring.consensusSnapshotOverride(Objects.requireNonNull( - initialState.getState().getPlatformState().getSnapshot())); + initialState.getState().getReadablePlatformState().getSnapshot())); // We only load non-ancient events during start up, so the initial expired threshold will be // equal to the ancient threshold when the system first starts. Over time as we get more events, @@ -378,7 +382,7 @@ private BirthRoundMigrationShim buildBirthRoundMigrationShim( } final MerkleRoot state = initialState.getState(); - final PlatformStateAccessor platformState = state.getPlatformState(); + final PlatformStateAccessor platformState = state.getReadablePlatformState(); return new DefaultBirthRoundMigrationShim( platformContext, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index cf1efa69e374..41e6b4403965 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -563,7 +563,7 @@ public PlatformComponentBuilder buildComponentBuilder() { // Update the address book with the current address book read from config.txt. // Eventually we will not do this, and only transactions will be capable of // modifying the address book. - final PlatformStateAccessor platformState = state.getPlatformState(); + final PlatformStateAccessor platformState = state.getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setAddressBook(addressBookInitializer.getCurrentAddressBook().copy()); v.setPreviousAddressBook( @@ -577,7 +577,7 @@ public PlatformComponentBuilder buildComponentBuilder() { // At this point the initial state must have the current address book set. If not, something is wrong. final AddressBook addressBook = - initialState.get().getState().getPlatformState().getAddressBook(); + initialState.get().getState().getReadablePlatformState().getAddressBook(); if (addressBook == null) { throw new IllegalStateException("The current address book of the initial state is null."); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java index 498b07089d33..667b7b26928c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java @@ -129,6 +129,6 @@ public record PlatformBuildingBlocks( */ @NonNull public AddressBook initialAddressBook() { - return initialState.get().getState().getPlatformState().getAddressBook(); + return initialState.get().getState().getReadablePlatformState().getAddressBook(); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java index ca6289066c4a..88e9f3cd898e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformComponentBuilder.java @@ -360,7 +360,11 @@ public EventSignatureValidator buildEventSignatureValidator() { blocks.platformContext(), CryptoStatic::verifySignature, blocks.appVersion().getPbjSemanticVersion(), - blocks.initialState().get().getState().getPlatformState().getPreviousAddressBook(), + blocks.initialState() + .get() + .getState() + .getReadablePlatformState() + .getPreviousAddressBook(), blocks.initialAddressBook(), blocks.intakeEventCounter()); } @@ -859,7 +863,11 @@ public IssDetector buildIssDetector() { issDetector = new DefaultIssDetector( blocks.platformContext(), - blocks.initialState().get().getState().getPlatformState().getAddressBook(), + blocks.initialState() + .get() + .getState() + .getReadablePlatformState() + .getAddressBook(), blocks.appVersion().getPbjSemanticVersion(), ignorePreconsensusSignatures, roundToIgnore); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java index d6cd90976a94..572a55e2a720 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/GenesisPlatformStateCommand.java @@ -76,7 +76,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti SignedStateFileReader.readStateFile(platformContext, statePath, SignedStateFileUtils::readState); try (final ReservedSignedState reservedSignedState = deserializedSignedState.reservedSignedState()) { final PlatformStateAccessor platformState = - reservedSignedState.get().getState().getPlatformState(); + reservedSignedState.get().getState().getWritablePlatformState(); platformState.bulkUpdate(v -> { System.out.printf("Replacing platform data %n"); v.setRound(PlatformStateAccessor.GENESIS_ROUND); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java index 42d4ef68afda..295bd8934cd4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/cli/ValidateAddressBookStateCommand.java @@ -82,7 +82,7 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final AddressBook stateAddressBook; try (final ReservedSignedState reservedSignedState = deserializedSignedState.reservedSignedState()) { final PlatformStateAccessor platformState = - reservedSignedState.get().getState().getPlatformState(); + reservedSignedState.get().getState().getReadablePlatformState(); System.out.printf("Extracting the state address book for comparison %n"); stateAddressBook = platformState.getAddressBook(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java index e972df500e8d..42371c32f49f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionHandler.java @@ -221,7 +221,7 @@ public StateAndRound handleConsensusRound(@NonNull final ConsensusRound consensu */ private void updatePlatformState(@NonNull final ConsensusRound round) { final PlatformStateAccessor platformState = - swirldStateManager.getConsensusState().getPlatformState(); + swirldStateManager.getConsensusState().getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setRound(round.getRoundNum()); v.setConsensusTimestamp(round.getConsensusTimestamp()); @@ -239,7 +239,7 @@ private void updatePlatformState(@NonNull final ConsensusRound round) { */ private void updateRunningEventHash(@NonNull final ConsensusRound round) throws InterruptedException { final PlatformStateAccessor platformState = - swirldStateManager.getConsensusState().getPlatformState(); + swirldStateManager.getConsensusState().getWritablePlatformState(); if (writeLegacyRunningEventHash) { // Update the running hash object. If there are no events, the running hash does not change. diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java index 1db603c57fd1..25b8a8ebb364 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/DefaultSignedStateValidator.java @@ -67,10 +67,10 @@ public void validate( private void throwIfOld(final SignedState signedState, final SignedStateValidationData previousStateData) throws SignedStateInvalidException { - if (signedState.getState().getPlatformState().getRound() < previousStateData.round() + if (signedState.getState().getReadablePlatformState().getRound() < previousStateData.round() || signedState .getState() - .getPlatformState() + .getReadablePlatformState() .getConsensusTimestamp() .isBefore(previousStateData.consensusTimestamp())) { logger.error( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java index c4366cb9eb2c..055b9c77e750 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectLearner.java @@ -104,7 +104,7 @@ public ReconnectLearner( this.statistics = Objects.requireNonNull(statistics); // Save some of the current state data for validation - this.stateValidationData = new SignedStateValidationData(currentState.getPlatformState(), addressBook); + this.stateValidationData = new SignedStateValidationData(currentState.getReadablePlatformState(), addressBook); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java index fd7308f5f91b..fc3e2ca68636 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/EventRecoveryWorkflow.java @@ -153,7 +153,7 @@ public static void recoverState( platformContext, signedStateFile, SignedStateFileUtils::readState) .reservedSignedState()) { StaticSoftwareVersion.setSoftwareVersion( - initialState.get().getState().getPlatformState().getCreationSoftwareVersion()); + initialState.get().getState().getReadablePlatformState().getCreationSoftwareVersion()); logger.info( STARTUP.getMarker(), @@ -314,7 +314,7 @@ public static RecoveredState reapplyTransactions( .init( platform, InitTrigger.EVENT_STREAM_RECOVERY, - initialState.get().getState().getPlatformState().getCreationSoftwareVersion()); + initialState.get().getState().getReadablePlatformState().getCreationSoftwareVersion()); appMain.init(platform, platform.getSelfId()); @@ -379,32 +379,33 @@ private static ReservedSignedState handleNextRound( final PlatformEvent lastEvent = ((CesEvent) getLastEvent(round)).getPlatformEvent(); new DefaultEventHasher().hashEvent(lastEvent); - final PlatformStateAccessor platformState = newState.getPlatformState(); + final PlatformStateAccessor newReadablePlatformState = newState.getReadablePlatformState(); + final PlatformStateAccessor newWritablePlatformState = newState.getWritablePlatformState(); + final PlatformStateAccessor previousReadablePlatformState = + previousState.get().getState().getReadablePlatformState(); - platformState.bulkUpdate(v -> { + newWritablePlatformState.bulkUpdate(v -> { v.setRound(round.getRoundNum()); - v.setLegacyRunningEventHash(getHashEventsCons( - previousState.get().getState().getPlatformState().getLegacyRunningEventHash(), round)); + v.setLegacyRunningEventHash( + getHashEventsCons(previousReadablePlatformState.getLegacyRunningEventHash(), round)); v.setConsensusTimestamp(currentRoundTimestamp); v.setSnapshot(SyntheticSnapshot.generateSyntheticSnapshot( round.getRoundNum(), lastEvent.getConsensusOrder(), currentRoundTimestamp, config, lastEvent)); - v.setCreationSoftwareVersion( - previousState.get().getState().getPlatformState().getCreationSoftwareVersion()); + v.setCreationSoftwareVersion(previousReadablePlatformState.getCreationSoftwareVersion()); }); applyTransactions( previousState.get().getSwirldState().cast(), newState.getSwirldState().cast(), - newState.getPlatformState(), + newState.getWritablePlatformState(), round); final boolean isFreezeState = isFreezeState( previousState.get().getConsensusTimestamp(), currentRoundTimestamp, - newState.getPlatformState().getFreezeTime()); + newReadablePlatformState.getFreezeTime()); if (isFreezeState) { - newState.getPlatformState() - .setLastFrozenTime(newState.getPlatformState().getFreezeTime()); + newWritablePlatformState.setLastFrozenTime(newReadablePlatformState.getFreezeTime()); } final ReservedSignedState signedState = new SignedState( diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java index 7a5ed7b9acaf..7dde25310efc 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/BirthRoundStateMigration.java @@ -53,7 +53,7 @@ public static void modifyStateForBirthRoundMigration( @NonNull final SoftwareVersion appVersion) { if (ancientMode == AncientMode.GENERATION_THRESHOLD) { - if (initialState.getState().getPlatformState().getFirstVersionInBirthRoundMode() != null) { + if (initialState.getState().getReadablePlatformState().getFirstVersionInBirthRoundMode() != null) { throw new IllegalStateException( "Cannot revert to generation mode after birth round migration has been completed."); } @@ -64,18 +64,18 @@ public static void modifyStateForBirthRoundMigration( } final MerkleRoot state = initialState.getState(); - final PlatformStateAccessor platformState = state.getPlatformState(); + final PlatformStateAccessor writablePlatformState = state.getWritablePlatformState(); - final boolean alreadyMigrated = platformState.getFirstVersionInBirthRoundMode() != null; + final boolean alreadyMigrated = writablePlatformState.getFirstVersionInBirthRoundMode() != null; if (alreadyMigrated) { // Birth round migration was completed at a prior time, no action needed. logger.info(STARTUP.getMarker(), "Birth round state migration has already been completed."); return; } - final long lastRoundBeforeMigration = platformState.getRound(); + final long lastRoundBeforeMigration = writablePlatformState.getRound(); - final ConsensusSnapshot consensusSnapshot = Objects.requireNonNull(platformState.getSnapshot()); + final ConsensusSnapshot consensusSnapshot = Objects.requireNonNull(writablePlatformState.getSnapshot()); final List judgeInfoList = consensusSnapshot.getMinimumJudgeInfoList(); final long lowestJudgeGenerationBeforeMigration = judgeInfoList.getLast().minimumJudgeAncientThreshold(); @@ -88,9 +88,9 @@ public static void modifyStateForBirthRoundMigration( lastRoundBeforeMigration, lowestJudgeGenerationBeforeMigration); - platformState.setFirstVersionInBirthRoundMode(appVersion); - platformState.setLastRoundBeforeBirthRoundMode(lastRoundBeforeMigration); - platformState.setLowestJudgeGenerationBeforeBirthRoundMode(lowestJudgeGenerationBeforeMigration); + writablePlatformState.setFirstVersionInBirthRoundMode(appVersion); + writablePlatformState.setLastRoundBeforeBirthRoundMode(lastRoundBeforeMigration); + writablePlatformState.setLowestJudgeGenerationBeforeBirthRoundMode(lowestJudgeGenerationBeforeMigration); final List modifiedJudgeInfoList = new ArrayList<>(judgeInfoList.size()); for (final MinimumJudgeInfo judgeInfo : judgeInfoList) { @@ -102,7 +102,7 @@ public static void modifyStateForBirthRoundMigration( modifiedJudgeInfoList, consensusSnapshot.nextConsensusNumber(), consensusSnapshot.consensusTimestamp()); - platformState.setSnapshot(modifiedConsensusSnapshot); + writablePlatformState.setSnapshot(modifiedConsensusSnapshot); state.invalidateHash(); MerkleCryptoFactory.getInstance().digestTreeSync(state); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java index df09891cf934..a37b0aeb83ba 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java @@ -73,7 +73,7 @@ public static ReservedSignedState buildGenesisState( @NonNull final SoftwareVersion appVersion, @NonNull final MerkleRoot stateRoot) { - initGenesisPlatformState(platformContext, stateRoot.getPlatformState(), addressBook, appVersion); + initGenesisPlatformState(platformContext, stateRoot.getWritablePlatformState(), addressBook, appVersion); final SignedState signedState = new SignedState( platformContext, CryptoStatic::verifySignature, stateRoot, "genesis state", false, false, false); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java index 82812057e5c2..74244ab4323a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleRoot.java @@ -33,12 +33,23 @@ public interface MerkleRoot extends MerkleInternal { SwirldState getSwirldState(); /** - * Get the platform state. + * Get readable platform state. + * Works on both - mutable and immutable {@link MerkleRoot} and, therefore, this method should be preferred. * - * @return the platform state + * @return immutable platform state */ @NonNull - PlatformStateAccessor getPlatformState(); + PlatformStateAccessor getReadablePlatformState(); + + /** + * Get writable platform state. Works only on mutable {@link MerkleRoot}. + * Call this method only if you need to modify the platform state. + * + * @return mutable platform state + */ + @NonNull + PlatformStateAccessor getWritablePlatformState(); + /** * Set the platform state. * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java index 37d84a0d0f19..039aa89c5b82 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/MerkleStateRoot.java @@ -1005,8 +1005,20 @@ public SwirldState getSwirldState() { */ @NonNull @Override - public PlatformStateAccessor getPlatformState() { - return !isImmutable() ? writablePlatformStateStore() : readablePlatformStateStore(); + public PlatformStateAccessor getReadablePlatformState() { + return readablePlatformStateStore(); + } + + /** + * {@inheritDoc} + */ + @NonNull + @Override + public PlatformStateAccessor getWritablePlatformState() { + if (isImmutable()) { + throw new IllegalStateException("Cannot get writable platform state when state is immutable"); + } + return writablePlatformStateStore(); } /** @@ -1025,7 +1037,7 @@ public void updatePlatformState(@NonNull final PlatformStateAccessor accessor) { @NonNull @Override public String getInfoString(final int hashDepth) { - return createInfoString(hashDepth, getPlatformState(), getHash(), this); + return createInfoString(hashDepth, readablePlatformStateStore(), getHash(), this); } private ReadablePlatformStateStore readablePlatformStateStore() { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java index d49e3f0af1b1..d7b589ec798d 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/State.java @@ -84,8 +84,8 @@ private State(final State that) { if (that.getSwirldState() != null) { this.setSwirldState(that.getSwirldState().copy()); } - if (that.getPlatformState() != null) { - this.updatePlatformState(that.getPlatformState().copy()); + if (that.getWritablePlatformState() != null) { + this.updatePlatformState(that.getWritablePlatformState().copy()); } } @@ -101,7 +101,7 @@ public MerkleNode migrate(final int version) { if (version < ClassVersion.MIGRATE_PLATFORM_STATE && getSwirldState() instanceof MerkleStateRoot merkleStateRoot) { - PlatformState platformState = getPlatformState().copy(); + PlatformState platformState = getWritablePlatformState().copy(); setChild(ChildIndices.PLATFORM_STATE, null); merkleStateRoot.updatePlatformState(platformState); merkleStateRoot.setRoute(MerkleRouteFactory.getEmptyRoute()); @@ -139,14 +139,22 @@ public void setSwirldState(final SwirldState state) { setChild(ChildIndices.SWIRLD_STATE, state); } + /** + * Immutable platform state is not supported by this class. + */ + @NonNull + @Override + public PlatformStateAccessor getReadablePlatformState() { + return getChild(ChildIndices.PLATFORM_STATE); + } + /** * Get the platform state. - * * @return the platform state */ @NonNull @Override - public PlatformState getPlatformState() { + public PlatformState getWritablePlatformState() { return getChild(ChildIndices.PLATFORM_STATE); } @@ -214,7 +222,7 @@ public boolean equals(final Object other) { return false; } final MerkleRoot state = (MerkleRoot) other; - return Objects.equals(getPlatformState(), state.getPlatformState()) + return Objects.equals(getReadablePlatformState(), state.getReadablePlatformState()) && Objects.equals(getSwirldState(), state.getSwirldState()); } @@ -223,7 +231,7 @@ public boolean equals(final Object other) { */ @Override public int hashCode() { - return Objects.hash(getPlatformState(), getSwirldState()); + return Objects.hash(getReadablePlatformState(), getSwirldState()); } /** @@ -234,7 +242,7 @@ public int hashCode() { @NonNull @Override public String getInfoString(final int hashDepth) { - final PlatformStateAccessor platformState = getPlatformState(); + final PlatformStateAccessor platformState = getReadablePlatformState(); return createInfoString(hashDepth, platformState, getHash(), this); } @@ -244,7 +252,7 @@ public String getInfoString(final int hashDepth) { @Override public String toString() { return new ToStringBuilder(this) - .append("platformState", getPlatformState()) + .append("platformState", getReadablePlatformState()) .append("swirldState", getSwirldState()) .toString(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java index cd9df967bd00..e0fad3358917 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManager.java @@ -125,7 +125,7 @@ public void setInitialState(@NonNull final MerkleRoot state) { public void handleConsensusRound(final ConsensusRound round) { final MerkleRoot state = stateRef.get(); - uptimeTracker.handleRound(round, state.getPlatformState().getAddressBook()); + uptimeTracker.handleRound(round, state.getReadablePlatformState().getAddressBook()); transactionHandler.handleRound(round, state); } @@ -157,8 +157,8 @@ public MerkleRoot getConsensusState() { public void savedStateInFreezePeriod() { // set current DualState's lastFrozenTime to be current freezeTime stateRef.get() - .getPlatformState() - .setLastFrozenTime(stateRef.get().getPlatformState().getFreezeTime()); + .getWritablePlatformState() + .setLastFrozenTime(stateRef.get().getReadablePlatformState().getFreezeTime()); } /** @@ -213,7 +213,7 @@ private void setLatestImmutableState(final MerkleRoot immutableState) { */ @Override public boolean isInFreezePeriod(final Instant timestamp) { - final PlatformStateAccessor platformState = getConsensusState().getPlatformState(); + final PlatformStateAccessor platformState = getConsensusState().getReadablePlatformState(); return SwirldStateManagerUtils.isInFreezePeriod( timestamp, platformState.getFreezeTime(), platformState.getLastFrozenTime()); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java index f872f1885156..bb42bcd48af6 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/SwirldStateManagerUtils.java @@ -52,7 +52,7 @@ public static MerkleRoot fastCopy( // Create a fast copy final MerkleRoot copy = state.copy(); - final var platformState = copy.getPlatformState(); + final var platformState = copy.getWritablePlatformState(); platformState.setCreationSoftwareVersion(softwareVersion); // Increment the reference count because this reference becomes the new value diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java index 24063ee819f9..7075b59c8ede 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/TransactionHandler.java @@ -56,7 +56,7 @@ public void handleRound(final ConsensusRound round, final MerkleRoot state) { final Instant timeOfHandle = Instant.now(); final long startTime = System.nanoTime(); - state.getSwirldState().handleConsensusRound(round, state.getPlatformState()); + state.getSwirldState().handleConsensusRound(round, state.getWritablePlatformState()); final double secondsElapsed = (System.nanoTime() - startTime) * NANOSECONDS_TO_SECONDS; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/address/AddressBookInitializer.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/address/AddressBookInitializer.java index f5b4fcb82c38..41b4643e7c80 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/address/AddressBookInitializer.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/address/AddressBookInitializer.java @@ -130,7 +130,8 @@ public AddressBookInitializer( platformContext.getConfiguration().getConfigData(AddressBookConfig.class); this.initialState = Objects.requireNonNull(initialState, "The initialState must not be null."); - this.stateAddressBook = initialState.getState().getPlatformState().getAddressBook(); + this.stateAddressBook = + initialState.getState().getReadablePlatformState().getAddressBook(); if (stateAddressBook == null && !initialState.isGenesisState()) { throw new IllegalStateException("Only genesis states can have null address books."); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateService.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateService.java index bee02252192e..765f1f15828f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateService.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/PlatformStateService.java @@ -21,7 +21,6 @@ import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.platform.state.PlatformState; -import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.MerkleStateRoot; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.state.merkle.singleton.SingletonNode; @@ -33,8 +32,8 @@ import java.util.List; /** - * A service that provides the schema for the platform state, used by {@link MerkleStateRoot} to implement - * {@link MerkleRoot#getPlatformState()}. + * A service that provides the schema for the platform state, used by {@link MerkleStateRoot} + * to implement accessors to the platform state. */ public enum PlatformStateService implements Service { PLATFORM_STATE_SERVICE; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java index ea956efd3a11..cc859ccb3260 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedState.java @@ -213,7 +213,7 @@ public SignedState( */ @Override public long getRound() { - return state.getPlatformState().getRound(); + return state.getReadablePlatformState().getRound(); } /** @@ -222,7 +222,7 @@ public long getRound() { * @return true if this is the genesis state */ public boolean isGenesisState() { - return state.getPlatformState().getRound() == GENESIS_ROUND; + return state.getReadablePlatformState().getRound() == GENESIS_ROUND; } /** @@ -241,6 +241,8 @@ public boolean isGenesisState() { public void setSigSet(@NonNull final SigSet sigSet) { this.sigSet = Objects.requireNonNull(sigSet); signingWeight = 0; + // init + state.getWritablePlatformState(); if (!isGenesisState()) { // Only non-genesis states will have signing weight final AddressBook addressBook = getAddressBook(); @@ -258,7 +260,7 @@ public void setSigSet(@NonNull final SigSet sigSet) { @Override public @NonNull AddressBook getAddressBook() { return Objects.requireNonNull( - getState().getPlatformState().getAddressBook(), + getState().getReadablePlatformState().getAddressBook(), "address book stored in this signed state is null, this should never happen"); } @@ -455,7 +457,7 @@ public String toString() { * @return the consensus timestamp for this signed state. */ public @NonNull Instant getConsensusTimestamp() { - return state.getPlatformState().getConsensusTimestamp(); + return state.getReadablePlatformState().getConsensusTimestamp(); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java index 8d27c8cd4983..60ad8d4c47f4 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/StartupStateUtils.java @@ -273,7 +273,7 @@ private static ReservedSignedState loadStateFile( final Hash oldHash = deserializedSignedState.originalHash(); final Hash newHash = rehashTree(state); - final SoftwareVersion loadedVersion = state.getPlatformState().getCreationSoftwareVersion(); + final SoftwareVersion loadedVersion = state.getReadablePlatformState().getCreationSoftwareVersion(); if (oldHash.equals(newHash)) { logger.info(STARTUP.getMarker(), "Loaded state's hash is the same as when it was saved."); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java index ba6f69d7dde5..ddc2560fe64c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SavedStateMetadata.java @@ -167,7 +167,7 @@ public static SavedStateMetadata create( Objects.requireNonNull(signedState.getState().getHash(), "state must be hashed"); Objects.requireNonNull(now, "now must not be null"); - final PlatformStateAccessor platformState = signedState.getState().getPlatformState(); + final PlatformStateAccessor platformState = signedState.getState().getReadablePlatformState(); final List signingNodes = signedState.getSigSet().getSigningNodes(); Collections.sort(signingNodes); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java index 5f56891a5a8b..b79c5087a73a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileWriter.java @@ -169,7 +169,7 @@ public static void writeSignedStateFilesToDirectory( platformContext, selfId, directory, - signedState.getState().getPlatformState().getAncientThreshold(), + signedState.getState().getReadablePlatformState().getAncientThreshold(), signedState.getRound()); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java index 78ba2f901de1..73a32aedefcf 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/BootstrapUtils.java @@ -45,6 +45,7 @@ import com.swirlds.platform.health.entropy.OSEntropyChecker; import com.swirlds.platform.health.filesystem.OSFileSystemChecker; import com.swirlds.platform.network.Network; +import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.address.AddressBookNetworkUtils; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.swirldapp.AppLoaderException; @@ -192,9 +193,13 @@ public static boolean detectSoftwareUpgrade( @NonNull final SoftwareVersion appVersion, @Nullable final SignedState loadedSignedState) { Objects.requireNonNull(appVersion, "The app version must not be null."); - final SoftwareVersion loadedSoftwareVersion = loadedSignedState == null - ? null - : loadedSignedState.getState().getPlatformState().getCreationSoftwareVersion(); + final SoftwareVersion loadedSoftwareVersion; + if (loadedSignedState == null) { + loadedSoftwareVersion = null; + } else { + MerkleRoot state = loadedSignedState.getState(); + loadedSoftwareVersion = state.getReadablePlatformState().getCreationSoftwareVersion(); + } final int versionComparison = loadedSoftwareVersion == null ? 1 : appVersion.compareTo(loadedSoftwareVersion); final boolean softwareUpgrade; if (versionComparison < 0) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java index ae95551d83df..5db9809cfc6d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java @@ -379,7 +379,7 @@ private SignedState getMockSignedState( when(platformState.getAddressBook()).thenReturn(currentAddressBook); when(platformState.getPreviousAddressBook()).thenReturn(previousAddressBook); final MerkleRoot state = mock(MerkleRoot.class); - when(state.getPlatformState()).thenReturn(platformState); + when(state.getReadablePlatformState()).thenReturn(platformState); when(signedState.getState()).thenReturn(state); when(signedState.isGenesisState()).thenReturn(fromGenesis); when(signedState.getAddressBook()).thenReturn(currentAddressBook); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java index 44af71144e78..6e2cae0a7713 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SavedStateMetadataTests.java @@ -213,7 +213,7 @@ void signingNodesSortedTest() { final AddressBook addressBook = mock(AddressBook.class); when(signedState.getState()).thenReturn(state); - when(state.getPlatformState()).thenReturn(platformState); + when(state.getReadablePlatformState()).thenReturn(platformState); when(platformState.getAddressBook()).thenReturn(addressBook); when(signedState.getSigSet()).thenReturn(sigSet); when(sigSet.getSigningNodes()) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java index 439c8c1b5ad7..9786788ee916 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/consensus/RoundCalculationUtilsTest.java @@ -72,7 +72,7 @@ void getMinGenNonAncientFromSignedState() { final MerkleRoot state = mock(MerkleRoot.class); final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); when(signedState.getState()).thenReturn(state); - when(state.getPlatformState()).thenReturn(platformState); + when(state.getReadablePlatformState()).thenReturn(platformState); final AtomicLong lastRoundDecided = new AtomicLong(); when(signedState.getRound()).thenAnswer(a -> lastRoundDecided.get()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java index 0f4c0ebe31b6..d14a1e74ec91 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/eventhandling/TransactionHandlerTester.java @@ -63,7 +63,8 @@ public TransactionHandlerTester(final AddressBook addressBook) { final SwirldState swirldState = mock(SwirldState.class); when(consensusState.getSwirldState()).thenReturn(swirldState); when(consensusState.copy()).thenReturn(consensusState); - when(consensusState.getPlatformState()).thenReturn(platformState); + when(consensusState.getReadablePlatformState()).thenReturn(platformState); + when(consensusState.getWritablePlatformState()).thenReturn(platformState); doAnswer(i -> { handledRounds.add(i.getArgument(0)); return null; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java index 9fd083d6a271..e0fd1ded3c6b 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/DefaultSignedStateValidatorTests.java @@ -258,7 +258,7 @@ void testSignedStateValidationRandom(final String desc, final List nodes, final SignedState signedState = stateSignedByNodes(signingNodes); final SignedStateValidationData originalData = - new SignedStateValidationData(signedState.getState().getPlatformState(), addressBook); + new SignedStateValidationData(signedState.getState().getReadablePlatformState(), addressBook); final boolean shouldSucceed = stateHasEnoughWeight(nodes, signingNodes); if (shouldSucceed) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java index 7ee81b537b4c..d7e993850fb8 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/BirthRoundStateMigrationTests.java @@ -84,7 +84,7 @@ void generationModeTest() { final Hash originalHash = signedState.getState().getHash(); final SoftwareVersion previousSoftwareVersion = - signedState.getState().getPlatformState().getCreationSoftwareVersion(); + signedState.getState().getWritablePlatformState().getCreationSoftwareVersion(); final SoftwareVersion newSoftwareVersion = createNextVersion(previousSoftwareVersion); @@ -108,14 +108,17 @@ void alreadyMigratedTest() { final SignedState signedState = generateSignedState(random, platformContext); final SoftwareVersion previousSoftwareVersion = - signedState.getState().getPlatformState().getCreationSoftwareVersion(); + signedState.getState().getReadablePlatformState().getCreationSoftwareVersion(); ; final SoftwareVersion newSoftwareVersion = createNextVersion(previousSoftwareVersion); - signedState.getState().getPlatformState().setLastRoundBeforeBirthRoundMode(signedState.getRound() - 100); - signedState.getState().getPlatformState().setFirstVersionInBirthRoundMode(previousSoftwareVersion); - signedState.getState().getPlatformState().setLowestJudgeGenerationBeforeBirthRoundMode(100); + signedState + .getState() + .getWritablePlatformState() + .setLastRoundBeforeBirthRoundMode(signedState.getRound() - 100); + signedState.getState().getWritablePlatformState().setFirstVersionInBirthRoundMode(previousSoftwareVersion); + signedState.getState().getWritablePlatformState().setLowestJudgeGenerationBeforeBirthRoundMode(100); rehashTree(signedState.getState()); final Hash originalHash = signedState.getState().getHash(); @@ -145,13 +148,13 @@ void migrationTest() { final Hash originalHash = signedState.getState().getHash(); final SoftwareVersion previousSoftwareVersion = - signedState.getState().getPlatformState().getCreationSoftwareVersion(); + signedState.getState().getReadablePlatformState().getCreationSoftwareVersion(); final SoftwareVersion newSoftwareVersion = createNextVersion(previousSoftwareVersion); final long lastRoundMinimumJudgeGeneration = signedState .getState() - .getPlatformState() + .getReadablePlatformState() .getSnapshot() .getMinimumJudgeInfoList() .getLast() @@ -167,19 +170,19 @@ void migrationTest() { newSoftwareVersion.getPbjSemanticVersion(), signedState .getState() - .getPlatformState() + .getReadablePlatformState() .getFirstVersionInBirthRoundMode() .getPbjSemanticVersion()); assertEquals( lastRoundMinimumJudgeGeneration, - signedState.getState().getPlatformState().getLowestJudgeGenerationBeforeBirthRoundMode()); + signedState.getState().getReadablePlatformState().getLowestJudgeGenerationBeforeBirthRoundMode()); assertEquals( signedState.getRound(), - signedState.getState().getPlatformState().getLastRoundBeforeBirthRoundMode()); + signedState.getState().getReadablePlatformState().getLastRoundBeforeBirthRoundMode()); // All of the judge info objects should now be using a birth round equal to the round of the state for (final MinimumJudgeInfo minimumJudgeInfo : - signedState.getState().getPlatformState().getSnapshot().getMinimumJudgeInfoList()) { + signedState.getState().getReadablePlatformState().getSnapshot().getMinimumJudgeInfoList()) { assertEquals(signedState.getRound(), minimumJudgeInfo.minimumJudgeAncientThreshold()); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java index 71af42111a33..f8e2cd6c5a9a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/MerkleStateRootTest.java @@ -53,6 +53,7 @@ import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.merkle.map.MerkleMap; import com.swirlds.platform.state.service.PlatformStateService; +import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.WritablePlatformStateStore; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.InitTrigger; @@ -926,13 +927,13 @@ class PlatformStateTests { @Test @DisplayName("Platform state should be registered by default") void platformStateIsRegisteredByDefault() { - assertThat(stateRoot.getPlatformState()).isNotNull(); + assertThat(stateRoot.getWritablePlatformState()).isNotNull(); } @Test @DisplayName("Test access to the platform state") void testAccessToPlatformStateData() { - PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getPlatformState()); + PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); stateRoot.updatePlatformState(randomPlatformState); ReadableSingletonState readableSingletonState = stateRoot .getReadableStates(PlatformStateService.NAME) @@ -948,16 +949,16 @@ void testAccessToPlatformStateData() { @Test @DisplayName("Test update of the platform state") void testUpdatePlatformStateData() { - PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getPlatformState()); + PlatformStateAccessor randomPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); stateRoot.updatePlatformState(randomPlatformState); WritableStates writableStates = stateRoot.getWritableStates(PlatformStateService.NAME); WritableSingletonState writableSingletonState = writableStates.getSingleton(V0540PlatformStateSchema.PLATFORM_STATE_KEY); - PlatformStateAccessor newPlatformState = randomPlatformState(stateRoot.getPlatformState()); + PlatformStateAccessor newPlatformState = randomPlatformState(stateRoot.getWritablePlatformState()); writableSingletonState.put(toPbjPlatformState(newPlatformState)); ((CommittableWritableStates) writableStates).commit(); - PlatformStateAccessor stateAccessor = stateRoot.getPlatformState(); + PlatformStateAccessor stateAccessor = stateRoot.getReadablePlatformState(); assertThat(stateAccessor.getAddressBook()).isEqualTo(newPlatformState.getAddressBook()); assertThat(stateAccessor.getRound()) .isEqualTo(newPlatformState.getSnapshot().round()); @@ -1020,8 +1021,10 @@ void migrate_platform_state_zeroth_child() { assertFalse(stateRoot.isImmutable()); // MerkleStateRoot registers the platform state as a singleton upon the first request to it - assertInstanceOf(WritablePlatformStateStore.class, stateRoot.getPlatformState()); - assertEquals(toPbjPlatformState(platformState), toPbjPlatformState(stateRoot.getPlatformState())); + assertInstanceOf(WritablePlatformStateStore.class, stateRoot.getWritablePlatformState()); + assertInstanceOf(ReadablePlatformStateStore.class, stateRoot.getReadablePlatformState()); + assertEquals(toPbjPlatformState(platformState), toPbjPlatformState(stateRoot.getWritablePlatformState())); + assertEquals(toPbjPlatformState(platformState), toPbjPlatformState(stateRoot.getReadablePlatformState())); } @Test @@ -1059,8 +1062,8 @@ void migrate_platform_state_last_child() { assertFalse(stateRoot.isImmutable()); // MerkleStateRoot registers the platform state as a singleton upon the first request to it - assertInstanceOf(WritablePlatformStateStore.class, stateRoot.getPlatformState()); - assertEquals(toPbjPlatformState(platformState), toPbjPlatformState(stateRoot.getPlatformState())); + assertInstanceOf(WritablePlatformStateStore.class, stateRoot.getWritablePlatformState()); + assertEquals(toPbjPlatformState(platformState), toPbjPlatformState(stateRoot.getWritablePlatformState())); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java index 1811d7686101..4db93d4e6f51 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SignedStateTests.java @@ -66,7 +66,7 @@ private MerkleStateRoot buildMockState(final Runnable reserveCallback, final Run final PlatformStateAccessor platformState = new PlatformState(); platformState.setAddressBook(mock(AddressBook.class)); - when(state.getPlatformState()).thenReturn(platformState); + when(state.getWritablePlatformState()).thenReturn(platformState); if (reserveCallback != null) { doAnswer(invocation -> { reserveCallback.run(); @@ -209,7 +209,9 @@ void alternateConstructorReservationsTest() { final MerkleRoot state = spy(new MerkleStateRoot( FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.major()))); final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); - when(state.getPlatformState()).thenReturn(platformState); + // init state first + state.getWritablePlatformState(); + when(state.getReadablePlatformState()).thenReturn(platformState); when(platformState.getRound()).thenReturn(0L); final SignedState signedState = new SignedState( TestPlatformContextBuilder.create().build(), diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateRegistryTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateRegistryTests.java index ba038d34694f..666e81660311 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateRegistryTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateRegistryTests.java @@ -108,7 +108,7 @@ void activeStateCountTest() throws IOException { // Deserialize a state final MerkleStateRoot stateToSerialize = new MerkleStateRoot(FAKE_MERKLE_STATE_LIFECYCLES, softwareVersionSupplier); - final var platformState = stateToSerialize.getPlatformState(); + final var platformState = stateToSerialize.getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setCreationSoftwareVersion(new BasicSoftwareVersion(version.minor())); v.setLegacyRunningEventHash(new Hash()); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java index 4ff4500f69f8..0b16a114f10a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/StateSigningTests.java @@ -318,7 +318,7 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { final NodeId nodeRemovedFromAddressBook = nodes.get(0).getNodeId(); final long weightRemovedFromAddressBook = nodes.get(0).getWeight(); final AddressBook updatedAddressBook = signedState.getAddressBook().remove(nodeRemovedFromAddressBook); - signedState.getState().getPlatformState().setAddressBook(updatedAddressBook); + signedState.getState().getWritablePlatformState().setAddressBook(updatedAddressBook); // Tamper with a node's signature final long weightWithModifiedSignature = nodes.get(1).getWeight(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java index c4cfd203bb89..cc42a4cac511 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/hashlogger/HashLoggerTest.java @@ -157,7 +157,7 @@ private ReservedSignedState createSignedState(final long round) { when(platformState.getRound()).thenReturn(round); when(platformState.getAddressBook()).thenReturn(addressBook); - when(state.getPlatformState()).thenReturn(platformState); + when(state.getReadablePlatformState()).thenReturn(platformState); when(state.getRoute()).thenReturn(merkleNode.getRoute()); when(state.getHash()).thenReturn(merkleNode.getHash()); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java index 7a3ddc4056ec..20ccc2648240 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/BlockingSwirldState.java @@ -102,8 +102,8 @@ public boolean equals(final Object obj) { return false; } return Objects.equals( - this.getPlatformState().getAddressBook(), - that.getPlatformState().getAddressBook()); + this.getReadablePlatformState().getAddressBook(), + that.getReadablePlatformState().getAddressBook()); } /** diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java index 7d257c7e77e2..1d6b0487ef31 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/RandomSignedStateGenerator.java @@ -186,7 +186,7 @@ public SignedState build() { consensusSnapshotInstance = consensusSnapshot; } - final PlatformStateAccessor platformState = stateInstance.getPlatformState(); + final PlatformStateAccessor platformState = stateInstance.getWritablePlatformState(); platformState.bulkUpdate(v -> { v.setSnapshot(consensusSnapshotInstance); diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/merkle/StateUtils.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/merkle/StateUtils.java index 8b81f88ba05f..c27999baa925 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/merkle/StateUtils.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/merkle/StateUtils.java @@ -30,14 +30,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.Objects; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; /** Utility class for working with states. */ public final class StateUtils { - private static final Logger logger = LogManager.getLogger(); - /** Prevent instantiation */ private StateUtils() {} diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/SignedStateUtils.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/SignedStateUtils.java index 5ba41ddb5aac..cc6cd99e73b0 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/SignedStateUtils.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/main/java/com/swirlds/platform/test/SignedStateUtils.java @@ -36,7 +36,7 @@ public static SignedState randomSignedState(long seed) { public static SignedState randomSignedState(Random random) { MerkleStateRoot root = new MerkleStateRoot(FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.minor())); - randomPlatformState(random, root.getPlatformState()); + randomPlatformState(random, root.getWritablePlatformState()); boolean shouldSaveToDisk = random.nextBoolean(); SignedState signedState = new SignedState( TestPlatformContextBuilder.create().build(), From 2dc454af74499cf59a5ecae2b39610bb2f29c1da Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Tue, 10 Sep 2024 17:15:11 -0500 Subject: [PATCH 07/16] fix: permit 100:1 deflation for upgrade ZIP files (#15422) Signed-off-by: Michael Tinker --- .../app/service/networkadmin/impl/handlers/UnzipUtility.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java index 6e032e22d15b..053815a35299 100644 --- a/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java +++ b/hedera-node/hedera-network-admin-service-impl/src/main/java/com/hedera/node/app/service/networkadmin/impl/handlers/UnzipUtility.java @@ -46,7 +46,7 @@ public final class UnzipUtility { private static final int THRESHOLD_ZIP_SIZE = 1000000000; // 1 GB - max allowed total size of all uncompressed files private static final int THRESHOLD_ENTRY_SIZE = 100000000; // 100 MB - max allowed size of one uncompressed file // max allowed ratio between uncompressed and compressed file size - private static final double THRESHOLD_RATIO = 10; + private static final double THRESHOLD_RATIO = 100; private UnzipUtility() {} From ab8b709329b2df3bcf88feb55d5c6c35f7fc3430 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Wed, 11 Sep 2024 09:35:31 -0500 Subject: [PATCH 08/16] feat: BlockStreams-Inversion of control (#15325) Signed-off-by: Neeharika-Sompalli --- .../com/hedera/node/app/ServicesMain.java | 83 +++++- .../app/version/HederaSoftwareVersion.java | 2 + .../com/hedera/node/app/ServicesMainTest.java | 60 ++++- .../app/state/merkle/SerializationTest.java | 1 - .../java/com/swirlds/platform/Browser.java | 89 +++++- .../platform/builder/PlatformBuilder.java | 255 ++---------------- .../builder/PlatformBuildingBlocks.java | 28 ++ .../system/address/AddressBookUtils.java | 53 ++++ .../src/main/java/module-info.java | 5 +- .../platform/turtle/runner/TurtleNode.java | 34 +-- .../util/AddressBookNetworkUtilsTests.java | 11 +- 11 files changed, 341 insertions(+), 280 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java index aeea283a7a0d..11124585fd48 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ServicesMain.java @@ -18,12 +18,20 @@ import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; import static com.swirlds.common.io.utility.FileUtils.rethrowIO; +import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME; +import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.getMetricsProvider; +import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.setupGlobalMetrics; +import static com.swirlds.platform.config.internal.PlatformConfigUtils.checkConfiguration; +import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; +import static com.swirlds.platform.state.signed.StartupStateUtils.getInitialState; import static com.swirlds.platform.system.SystemExitCode.CONFIGURATION_ERROR; import static com.swirlds.platform.system.SystemExitCode.NODE_ADDRESS_MISMATCH; import static com.swirlds.platform.system.SystemExitUtils.exitSystem; +import static com.swirlds.platform.system.address.AddressBookUtils.createRoster; +import static com.swirlds.platform.system.address.AddressBookUtils.initializeAddressBook; import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; import static com.swirlds.platform.util.BootstrapUtils.getNodesToRun; import static java.util.Objects.requireNonNull; @@ -34,14 +42,21 @@ import com.swirlds.base.time.Time; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.RuntimeConstructable; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.CryptographyFactory; +import com.swirlds.common.crypto.CryptographyHolder; +import com.swirlds.common.io.filesystem.FileSystemManager; import com.swirlds.common.io.utility.FileUtils; +import com.swirlds.common.io.utility.RecycleBin; +import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; +import com.swirlds.common.merkle.crypto.MerkleCryptographyFactory; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.config.extensions.sources.SystemEnvironmentConfigSource; import com.swirlds.config.extensions.sources.SystemPropertiesConfigSource; import com.swirlds.platform.CommandLineArgs; +import com.swirlds.platform.ParameterProvider; import com.swirlds.platform.builder.PlatformBuilder; import com.swirlds.platform.config.legacy.ConfigurationException; import com.swirlds.platform.config.legacy.LegacyConfigProperties; @@ -155,7 +170,7 @@ public static void main(final String... args) throws Exception { // Determine which node to run locally // Load config.txt address book file and parse address book - final AddressBook addressBook = loadAddressBook(DEFAULT_CONFIG_FILE_NAME); + final AddressBook bootstrapAddressBook = loadAddressBook(DEFAULT_CONFIG_FILE_NAME); // parse command line arguments final CommandLineArgs commandLineArgs = CommandLineArgs.parse(args); @@ -170,7 +185,7 @@ public static void main(final String... args) throws Exception { // get the list of configured nodes from the address book // for each node in the address book, check if it has a local IP (local to this computer) // additionally if a command line arg is supplied then limit matching nodes to that node id - final List nodesToRun = getNodesToRun(addressBook, commandLineArgs.localNodesToStart()); + final List nodesToRun = getNodesToRun(bootstrapAddressBook, commandLineArgs.localNodesToStart()); // hard exit if no nodes are configured to run checkNodesToRun(nodesToRun); @@ -179,19 +194,58 @@ public static void main(final String... args) throws Exception { final SoftwareVersion version = hedera.getSoftwareVersion(); logger.info("Starting node {} with version {}", selfId, version); - final PlatformBuilder platformBuilder = PlatformBuilder.create( - Hedera.APP_NAME, - Hedera.SWIRLD_NAME, + final var configuration = buildConfiguration(); + final var keysAndCerts = + initNodeSecurity(bootstrapAddressBook, configuration).get(selfId); + + setupGlobalMetrics(configuration); + final var metrics = getMetricsProvider().createPlatformMetrics(selfId); + final var time = Time.getCurrent(); + final var fileSystemManager = FileSystemManager.create(configuration); + final var recycleBin = + RecycleBin.create(metrics, configuration, getStaticThreadManager(), time, fileSystemManager, selfId); + + final var cryptography = CryptographyFactory.create(); + CryptographyHolder.set(cryptography); + // the AddressBook is not changed after this point, so we calculate the hash now + cryptography.digestSync(bootstrapAddressBook); + + // Initialize the Merkle cryptography + final var merkleCryptography = MerkleCryptographyFactory.create(configuration, cryptography); + MerkleCryptoFactory.set(merkleCryptography); + + // Create the platform context + final var platformContext = PlatformContext.create( + configuration, + Time.getCurrent(), + metrics, + cryptography, + FileSystemManager.create(configuration), + recycleBin, + merkleCryptography); + // Create initial state for the platform + final var initialState = getInitialState( + platformContext, version, hedera::newMerkleStateRoot, SignedStateFileUtils::readState, - selfId); + Hedera.APP_NAME, + Hedera.SWIRLD_NAME, + selfId, + bootstrapAddressBook); + + // Initialize the address book and set on platform builder + final var addressBook = + initializeAddressBook(selfId, version, initialState, bootstrapAddressBook, platformContext); - // Add additional configuration to the platform - final Configuration configuration = buildConfiguration(); - platformBuilder.withConfiguration(configuration); - platformBuilder.withCryptography(CryptographyFactory.create()); - platformBuilder.withTime(Time.getCurrent()); + // Follow the Inversion of Control pattern by injecting all needed dependencies into the PlatformBuilder. + final var platformBuilder = PlatformBuilder.create( + Hedera.APP_NAME, Hedera.SWIRLD_NAME, version, initialState, selfId) + .withPlatformContext(platformContext) + .withConfiguration(configuration) + .withAddressBook(addressBook) + .withRoster(createRoster(addressBook)) + .withKeysAndCerts(keysAndCerts); // IMPORTANT: A surface-level reading of this method will undersell the centrality // of the Hedera instance. It is actually omnipresent throughout both the startup @@ -234,13 +288,15 @@ private static Configuration buildConfiguration() { .withSource(SystemPropertiesConfigSource.getInstance()); rethrowIO(() -> BootstrapUtils.setupConfigBuilder(configurationBuilder, getAbsolutePath(DEFAULT_SETTINGS_FILE_NAME))); - return configurationBuilder.build(); + final Configuration configuration = configurationBuilder.build(); + checkConfiguration(configuration); + return configuration; } /** * Selects the node to run locally from either the command line arguments or the address book. * - * @param nodesToRun the list of nodes configured to run based on the address book. + * @param nodesToRun the list of nodes configured to run based on the address book. * @param localNodesToStart the node ids specified on the command line. * @return the node which should be run locally. * @throws ConfigurationException if more than one node would be started or the requested node is not configured. @@ -289,6 +345,7 @@ private static AddressBook loadAddressBook(@NonNull final String addressBookPath try { final LegacyConfigProperties props = LegacyConfigPropertiesLoader.loadConfigFile(FileUtils.getAbsolutePath(addressBookPath)); + props.appConfig().ifPresent(c -> ParameterProvider.getInstance().setParameters(c.params())); return props.getAddressBook(); } catch (final Exception e) { logger.error(EXCEPTION.getMarker(), "Error loading address book", e); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/version/HederaSoftwareVersion.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/version/HederaSoftwareVersion.java index ae87b7764428..c8f44252f68c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/version/HederaSoftwareVersion.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/version/HederaSoftwareVersion.java @@ -39,7 +39,9 @@ * completely different from each other. * *

      The Services version is the version of the node software itself. + * This will be removed once we stop supporting 0.53.0 and earlier versions. */ +@Deprecated(forRemoval = true) public class HederaSoftwareVersion implements SoftwareVersion { public static final long CLASS_ID = 0x6f2b1bc2df8cbd0cL; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ServicesMainTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ServicesMainTest.java index cbb7c9add4e0..b366ba087f40 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ServicesMainTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/ServicesMainTest.java @@ -20,21 +20,36 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.mockStatic; import com.hedera.node.app.version.ServicesSoftwareVersion; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.Cryptography; +import com.swirlds.common.io.filesystem.FileSystemManager; +import com.swirlds.common.io.utility.RecycleBin; +import com.swirlds.common.merkle.crypto.MerkleCryptography; +import com.swirlds.common.metrics.platform.DefaultMetricsProvider; import com.swirlds.common.platform.NodeId; +import com.swirlds.config.api.Configuration; +import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.builder.PlatformBuilder; import com.swirlds.platform.config.legacy.ConfigurationException; import com.swirlds.platform.config.legacy.LegacyConfigProperties; import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; import com.swirlds.platform.state.MerkleStateRoot; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SystemExitUtils; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.util.BootstrapUtils; import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Assertions; +import java.util.function.BiFunction; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -47,9 +62,48 @@ final class ServicesMainTest { mockStatic(LegacyConfigPropertiesLoader.class); private static final MockedStatic bootstrapUtilsMockedStatic = mockStatic(BootstrapUtils.class); - @Mock + @Mock(strictness = LENIENT) private LegacyConfigProperties legacyConfigProperties; + @Mock(strictness = LENIENT) + private AddressBook addressBook; + + @Mock(strictness = LENIENT) + private DefaultMetricsProvider metricsProvider; + + @Mock(strictness = LENIENT) + private Metrics metrics; + + @Mock(strictness = LENIENT) + private FileSystemManager fileSystemManager; + + @Mock(strictness = LENIENT) + private RecycleBin recycleBin; + + @Mock(strictness = LENIENT) + private MerkleCryptography merkleCryptography; + + @Mock(strictness = LENIENT) + BiFunction merkleCryptographyFn; + + @Mock(strictness = LENIENT) + private PlatformContext platformContext; + + @Mock(strictness = LENIENT) + private PlatformBuilder platformBuilder; + + @Mock(strictness = LENIENT) + private ReservedSignedState reservedSignedState; + + @Mock(strictness = LENIENT) + private SignedState signedState; + + @Mock(strictness = LENIENT) + private Platform platform; + + @Mock(strictness = LENIENT) + private Hedera hedera; + private final ServicesMain subject = new ServicesMain(); // no local nodes specified but more than one match in address book @@ -102,7 +156,7 @@ void returnsSerializableVersion() { @Test void noopsAsExpected() { // expect: - Assertions.assertDoesNotThrow(subject::run); + assertDoesNotThrow(subject::run); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java index 00c09d0c1f1c..7d99d2473d59 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/state/merkle/SerializationTest.java @@ -208,7 +208,6 @@ void dualReadAndWrite() throws IOException, ConstructableRegistryException { final var originalTree = createMerkleHederaState(schemaV1); MerkleStateRoot copy = originalTree.copy(); // make a copy to make VM flushable - ; forceFlush(originalTree.getReadableStates(FIRST_SERVICE).get(ANIMAL_STATE_KEY)); copy.copy(); // make a fast copy because we can only write to disk an immutable copy diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Browser.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Browser.java index 4bf43ce66f31..03ff19a9fc37 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Browser.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Browser.java @@ -23,17 +23,31 @@ import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME; import static com.swirlds.platform.builder.PlatformBuildConstants.LOG4J_FILE_NAME; +import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.getMetricsProvider; +import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.setupGlobalMetrics; +import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; import static com.swirlds.platform.gui.internal.BrowserWindowManager.addPlatforms; import static com.swirlds.platform.gui.internal.BrowserWindowManager.getStateHierarchy; import static com.swirlds.platform.gui.internal.BrowserWindowManager.moveBrowserWindowToFront; import static com.swirlds.platform.gui.internal.BrowserWindowManager.setBrowserWindow; import static com.swirlds.platform.gui.internal.BrowserWindowManager.setStateHierarchy; import static com.swirlds.platform.gui.internal.BrowserWindowManager.showBrowserWindow; +import static com.swirlds.platform.state.signed.StartupStateUtils.getInitialState; +import static com.swirlds.platform.system.address.AddressBookUtils.createRoster; +import static com.swirlds.platform.system.address.AddressBookUtils.initializeAddressBook; import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; import static com.swirlds.platform.util.BootstrapUtils.getNodesToRun; import static com.swirlds.platform.util.BootstrapUtils.loadSwirldMains; import static com.swirlds.platform.util.BootstrapUtils.setupBrowserWindow; +import com.swirlds.base.time.Time; +import com.swirlds.common.context.PlatformContext; +import com.swirlds.common.crypto.CryptographyFactory; +import com.swirlds.common.crypto.CryptographyHolder; +import com.swirlds.common.io.filesystem.FileSystemManager; +import com.swirlds.common.io.utility.RecycleBin; +import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; +import com.swirlds.common.merkle.crypto.MerkleCryptographyFactory; import com.swirlds.common.platform.NodeId; import com.swirlds.common.startup.Log4jSetup; import com.swirlds.common.threading.framework.config.ThreadConfiguration; @@ -44,6 +58,7 @@ import com.swirlds.platform.builder.PlatformBuilder; import com.swirlds.platform.config.PathsConfig; import com.swirlds.platform.crypto.CryptoConstants; +import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.gui.GuiEventStorage; import com.swirlds.platform.gui.hashgraph.HashgraphGuiSource; import com.swirlds.platform.gui.hashgraph.internal.StandardGuiSource; @@ -52,10 +67,12 @@ import com.swirlds.platform.gui.model.InfoApp; import com.swirlds.platform.gui.model.InfoMember; import com.swirlds.platform.gui.model.InfoSwirld; +import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileUtils; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SystemExitCode; import com.swirlds.platform.system.SystemExitUtils; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.util.BootstrapUtils; import edu.umd.cs.findbugs.annotations.NonNull; import java.awt.GraphicsEnvironment; @@ -70,8 +87,12 @@ import org.apache.logging.log4j.Logger; /** - * The Browser that launches the Platforms that run the apps. + * The Browser that launches the Platforms that run the apps. This is used by the demo apps to launch the + * Platforms. + * This class will be removed once the demo apps moved to Inversion of Control pattern to build and start platform + * directly. */ +@Deprecated(forRemoval = true) public class Browser { // Each member is represented by an AddressBook entry in config.txt. On a given computer, a single java // process runs all members whose listed internal IP address matches some address on that computer. That @@ -213,21 +234,75 @@ private static void launchUnhandled(@NonNull final CommandLineArgs commandLineAr BootstrapUtils.setupConfigBuilder(configBuilder, getAbsolutePath(DEFAULT_SETTINGS_FILE_NAME))); final Configuration configuration = configBuilder.build(); + setupGlobalMetrics(configuration); + guiMetrics = getMetricsProvider().createPlatformMetrics(nodeId); + + final var recycleBin = RecycleBin.create( + guiMetrics, + configuration, + getStaticThreadManager(), + Time.getCurrent(), + FileSystemManager.create(configuration), + nodeId); + final var cryptography = CryptographyFactory.create(); + CryptographyHolder.set(cryptography); + final KeysAndCerts keysAndCerts = initNodeSecurity(appDefinition.getConfigAddressBook(), configuration) + .get(nodeId); + + // the AddressBook is not changed after this point, so we calculate the hash now + cryptography.digestSync(appDefinition.getConfigAddressBook()); + + // Set the MerkleCryptography instance for this node + final var merkleCryptography = MerkleCryptographyFactory.create(configuration, CryptographyHolder.get()); + MerkleCryptoFactory.set(merkleCryptography); + + // Create platform context + final var platformContext = PlatformContext.create( + configuration, + Time.getCurrent(), + guiMetrics, + cryptography, + FileSystemManager.create(configuration), + recycleBin, + MerkleCryptographyFactory.create(configuration, CryptographyHolder.get())); + // Create the initial state for the platform + final ReservedSignedState initialState = getInitialState( + platformContext, + appMain.getSoftwareVersion(), + appMain::newMerkleStateRoot, + SignedStateFileUtils::readState, + appMain.getClass().getName(), + appDefinition.getSwirldName(), + nodeId, + appDefinition.getConfigAddressBook()); + // Initialize the address book + final AddressBook addressBook = initializeAddressBook( + nodeId, + appMain.getSoftwareVersion(), + initialState, + appDefinition.getConfigAddressBook(), + platformContext); + + // Build the platform with the given values final PlatformBuilder builder = PlatformBuilder.create( appMain.getClass().getName(), appDefinition.getSwirldName(), appMain.getSoftwareVersion(), - appMain::newMerkleStateRoot, - SignedStateFileUtils::readState, + initialState, nodeId); - if (showUi && index == 0) { builder.withPreconsensusEventCallback(guiEventStorage::handlePreconsensusEvent); builder.withConsensusSnapshotOverrideCallback(guiEventStorage::handleSnapshotOverride); } - - final SwirldsPlatform platform = - (SwirldsPlatform) builder.withConfiguration(configuration).build(); + // Build platform using the Inversion of Control pattern by injecting all needed + // dependencies into the PlatformBuilder. + final SwirldsPlatform platform = (SwirldsPlatform) builder.withConfiguration(configuration) + .withPlatformContext(platformContext) + .withConfiguration(configuration) + .withAddressBook(addressBook) + .withRoster(createRoster(appDefinition.getConfigAddressBook())) + .withKeysAndCerts(keysAndCerts) + .build(); platforms.put(nodeId, platform); if (showUi) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java index 41e6b4403965..14929e4471e8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuilder.java @@ -17,50 +17,25 @@ package com.swirlds.platform.builder; import static com.swirlds.common.io.utility.FileUtils.getAbsolutePath; -import static com.swirlds.common.io.utility.FileUtils.rethrowIO; import static com.swirlds.common.threading.manager.AdHocThreadManager.getStaticThreadManager; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STARTUP; import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_CONFIG_FILE_NAME; -import static com.swirlds.platform.builder.PlatformBuildConstants.DEFAULT_SETTINGS_FILE_NAME; import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.doStaticSetup; -import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.getMetricsProvider; -import static com.swirlds.platform.builder.internal.StaticPlatformBuilder.setupGlobalMetrics; import static com.swirlds.platform.config.internal.PlatformConfigUtils.checkConfiguration; -import static com.swirlds.platform.crypto.CryptoStatic.initNodeSecurity; import static com.swirlds.platform.event.preconsensus.PcesUtilities.getDatabaseDirectory; -import static com.swirlds.platform.state.signed.StartupStateUtils.getInitialState; -import static com.swirlds.platform.system.address.AddressBookUtils.createRoster; import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; -import static com.swirlds.platform.util.BootstrapUtils.detectSoftwareUpgrade; import com.hedera.hapi.node.state.roster.Roster; -import com.swirlds.base.function.CheckedBiFunction; -import com.swirlds.base.time.Time; import com.swirlds.common.concurrent.ExecutorFactory; -import com.swirlds.common.context.DefaultPlatformContext; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.crypto.Cryptography; -import com.swirlds.common.crypto.CryptographyFactory; -import com.swirlds.common.crypto.CryptographyHolder; -import com.swirlds.common.io.filesystem.FileSystemManager; -import com.swirlds.common.io.streams.MerkleDataInputStream; -import com.swirlds.common.io.utility.RecycleBin; -import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; -import com.swirlds.common.merkle.crypto.MerkleCryptography; -import com.swirlds.common.merkle.crypto.MerkleCryptographyFactory; import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; import com.swirlds.common.wiring.WiringConfig; import com.swirlds.common.wiring.model.WiringModel; import com.swirlds.common.wiring.model.WiringModelBuilder; import com.swirlds.config.api.Configuration; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.metrics.api.Metrics; -import com.swirlds.platform.ParameterProvider; import com.swirlds.platform.SwirldsPlatform; -import com.swirlds.platform.config.legacy.LegacyConfigProperties; -import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.event.PlatformEvent; @@ -74,10 +49,7 @@ import com.swirlds.platform.gossip.sync.config.SyncConfig; import com.swirlds.platform.pool.TransactionPoolNexus; import com.swirlds.platform.scratchpad.Scratchpad; -import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.SwirldStateManager; -import com.swirlds.platform.state.address.AddressBookInitializer; import com.swirlds.platform.state.iss.IssScratchpad; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.Platform; @@ -85,7 +57,6 @@ import com.swirlds.platform.system.StaticSoftwareVersion; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.status.StatusActionSubmitter; -import com.swirlds.platform.util.BootstrapUtils; import com.swirlds.platform.util.RandomBuilder; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -97,7 +68,6 @@ import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; -import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -110,17 +80,11 @@ public final class PlatformBuilder { private final String appName; private final SoftwareVersion softwareVersion; - private final Supplier genesisStateBuilder; - private final CheckedBiFunction snapshotStateReader; + private final ReservedSignedState initialState; private final NodeId selfId; private final String swirldName; private Configuration configuration; - private Cryptography cryptography; - private Metrics metrics; - private Time time; - private FileSystemManager fileSystemManager; - private RecycleBin recycleBin; private ExecutorFactory executorFactory; private static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = @@ -129,7 +93,7 @@ public final class PlatformBuilder { /** * An address book that is used to bootstrap the system. Traditionally read from config.txt. */ - private AddressBook bootstrapAddressBook; + private AddressBook addressBook; private Roster roster; @@ -152,6 +116,10 @@ public final class PlatformBuilder { * The source of non-cryptographic randomness for this platform. */ private RandomBuilder randomBuilder; + /** + * The platform context for this platform. + */ + private PlatformContext platformContext; private Consumer preconsensusEventConsumer; private Consumer snapshotOverrideConsumer; @@ -178,19 +146,16 @@ public final class PlatformBuilder { * @param swirldName the name of the swirld, currently used for deciding where to store states on disk * @param selfId the ID of this node * @param softwareVersion the software version of the application - * @param genesisStateBuilder a supplier that will be called to create the genesis state, if necessary - * @param snapshotStateReader a function to read an existing state snapshot, if exists + * @param initialState the genesis state supplied by the application */ @NonNull public static PlatformBuilder create( @NonNull final String appName, @NonNull final String swirldName, @NonNull final SoftwareVersion softwareVersion, - @NonNull final Supplier genesisStateBuilder, - @NonNull final CheckedBiFunction snapshotStateReader, + @NonNull final ReservedSignedState initialState, @NonNull final NodeId selfId) { - return new PlatformBuilder( - appName, swirldName, softwareVersion, genesisStateBuilder, snapshotStateReader, selfId); + return new PlatformBuilder(appName, swirldName, softwareVersion, initialState, selfId); } /** @@ -200,23 +165,20 @@ public static PlatformBuilder create( * disk * @param swirldName the name of the swirld, currently used for deciding where to store states on disk * @param softwareVersion the software version of the application - * @param genesisStateBuilder a supplier that will be called to create the genesis state, if necessary - * @param snapshotStateReader a function to read an existing state snapshot, if exists + * @param initialState the genesis state supplied by application * @param selfId the ID of this node */ private PlatformBuilder( @NonNull final String appName, @NonNull final String swirldName, @NonNull final SoftwareVersion softwareVersion, - @NonNull final Supplier genesisStateBuilder, - @NonNull final CheckedBiFunction snapshotStateReader, + @NonNull final ReservedSignedState initialState, @NonNull final NodeId selfId) { this.appName = Objects.requireNonNull(appName); this.swirldName = Objects.requireNonNull(swirldName); this.softwareVersion = Objects.requireNonNull(softwareVersion); - this.genesisStateBuilder = Objects.requireNonNull(genesisStateBuilder); - this.snapshotStateReader = Objects.requireNonNull(snapshotStateReader); + this.initialState = Objects.requireNonNull(initialState); this.selfId = Objects.requireNonNull(selfId); StaticSoftwareVersion.setSoftwareVersion(softwareVersion); @@ -237,80 +199,6 @@ public PlatformBuilder withConfiguration(@NonNull final Configuration configurat return this; } - /** - * Provide the cryptography to use for this platform. If not provided then the default cryptography is used. - * - * @param cryptography the cryptography to use - * @return this - */ - @NonNull - public PlatformBuilder withCryptography(@NonNull final Cryptography cryptography) { - this.cryptography = Objects.requireNonNull(cryptography); - return this; - } - - /** - * Provide the metrics to use for this platform. If not provided then default metrics are created. - * - * @param metrics the metrics to use - * @return this - */ - @NonNull - public PlatformBuilder withMetrics(@NonNull final Metrics metrics) { - this.metrics = Objects.requireNonNull(metrics); - return this; - } - - /** - * Provide the time to use for this platform. If not provided then the default wall clock time is used. - * - * @param time the time to use - * @return this - */ - @NonNull - public PlatformBuilder withTime(@NonNull final Time time) { - this.time = Objects.requireNonNull(time); - return this; - } - - /** - * Provide the file system manager to use for this platform. If not provided then the default file system manager is - * used. - * - * @param fileSystemManager the file system manager to use - * @return this - */ - @NonNull - public PlatformBuilder withFileSystemManager(@NonNull final FileSystemManager fileSystemManager) { - this.fileSystemManager = Objects.requireNonNull(fileSystemManager); - return this; - } - - /** - * Provide the recycle bin to use for this platform. If not provided then the default recycle bin is used. - * - * @param recycleBin the recycle bin to use - * @return this - */ - @NonNull - public PlatformBuilder withRecycleBin(@NonNull final RecycleBin recycleBin) { - this.recycleBin = Objects.requireNonNull(recycleBin); - return this; - } - - /** - * Provide the executor factory to use for this platform. If not provided then the default executor factory is - * used. - * - * @param executorFactory the executor factory to use - * @return this - */ - @NonNull - public PlatformBuilder withExecutorFactory(@NonNull final ExecutorFactory executorFactory) { - this.executorFactory = Objects.requireNonNull(executorFactory); - return this; - } - /** * Registers a callback that is called for each valid non-ancient preconsensus event in topological order (i.e. * after each event exits the orphan buffer). Useful for scenarios where access to this internal stream of events is @@ -385,9 +273,9 @@ public PlatformBuilder withStaleEventCallback(@NonNull final Consumer ParameterProvider.getInstance().setParameters(c.params())); - return legacyConfig.getAddressBook(); + public PlatformBuilder withPlatformContext(@NonNull final PlatformContext platformContext) { + throwIfAlreadyUsed(); + this.platformContext = Objects.requireNonNull(platformContext); + return this; } /** @@ -476,116 +365,14 @@ public PlatformComponentBuilder buildComponentBuilder() { throwIfAlreadyUsed(); used = true; - if (configuration == null) { - final ConfigurationBuilder configurationBuilder = ConfigurationBuilder.create(); - rethrowIO(() -> BootstrapUtils.setupConfigBuilder( - configurationBuilder, getAbsolutePath(DEFAULT_SETTINGS_FILE_NAME))); - configuration = configurationBuilder.build(); - checkConfiguration(configuration); - } - - if (time == null) { - time = Time.getCurrent(); - } - - if (metrics == null) { - setupGlobalMetrics(configuration); - metrics = getMetricsProvider().createPlatformMetrics(selfId); - } - - if (cryptography == null) { - cryptography = CryptographyFactory.create(); - } - final MerkleCryptography merkleCryptography = MerkleCryptographyFactory.create(configuration, cryptography); - CryptographyHolder.set(cryptography); - MerkleCryptoFactory.set(merkleCryptography); - - if (fileSystemManager == null) { - fileSystemManager = FileSystemManager.create(configuration); - } - - if (recycleBin == null) { - recycleBin = RecycleBin.create( - metrics, configuration, getStaticThreadManager(), time, fileSystemManager, selfId); - } - if (executorFactory == null) { executorFactory = ExecutorFactory.create("platform", null, DEFAULT_UNCAUGHT_EXCEPTION_HANDLER); } - final PlatformContext platformContext = new DefaultPlatformContext( - configuration, - metrics, - cryptography, - time, - executorFactory, - fileSystemManager, - recycleBin, - merkleCryptography); - final boolean firstPlatform = doStaticSetup(configuration, configPath); - final AddressBook boostrapAddressBook = - this.bootstrapAddressBook == null ? loadConfigAddressBook() : this.bootstrapAddressBook; - checkNodesToRun(List.of(selfId)); - final KeysAndCerts keysAndCerts = this.keysAndCerts == null - ? initNodeSecurity(boostrapAddressBook, configuration).get(selfId) - : this.keysAndCerts; - - // the AddressBook is not changed after this point, so we calculate the hash now - platformContext.getCryptography().digestSync(boostrapAddressBook); - - final ReservedSignedState initialState = getInitialState( - platformContext, - softwareVersion, - genesisStateBuilder, - snapshotStateReader, - appName, - swirldName, - selfId, - boostrapAddressBook); - - final boolean softwareUpgrade = detectSoftwareUpgrade(softwareVersion, initialState.get()); - - // Initialize the address book from the configuration and platform saved state. - final AddressBookInitializer addressBookInitializer = new AddressBookInitializer( - selfId, - softwareVersion, - softwareUpgrade, - initialState.get(), - boostrapAddressBook.copy(), - platformContext); - - if (addressBookInitializer.hasAddressBookChanged()) { - final MerkleRoot state = initialState.get().getState(); - // Update the address book with the current address book read from config.txt. - // Eventually we will not do this, and only transactions will be capable of - // modifying the address book. - final PlatformStateAccessor platformState = state.getWritablePlatformState(); - platformState.bulkUpdate(v -> { - v.setAddressBook(addressBookInitializer.getCurrentAddressBook().copy()); - v.setPreviousAddressBook( - addressBookInitializer.getPreviousAddressBook() == null - ? null - : addressBookInitializer - .getPreviousAddressBook() - .copy()); - }); - } - - // At this point the initial state must have the current address book set. If not, something is wrong. - final AddressBook addressBook = - initialState.get().getState().getReadablePlatformState().getAddressBook(); - if (addressBook == null) { - throw new IllegalStateException("The current address book of the initial state is null."); - } - - if (roster == null) { - roster = createRoster(boostrapAddressBook); - } - final SyncConfig syncConfig = platformContext.getConfiguration().getConfigData(SyncConfig.class); final IntakeEventCounter intakeEventCounter; if (syncConfig.waitForEventsInIntake()) { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java index 667b7b26928c..bc74e1d6c31f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/builder/PlatformBuildingBlocks.java @@ -16,6 +16,8 @@ package com.swirlds.platform.builder; +import static java.util.Objects.requireNonNull; + import com.swirlds.common.context.PlatformContext; import com.swirlds.common.notification.NotificationEngine; import com.swirlds.common.platform.NodeId; @@ -122,6 +124,32 @@ public record PlatformBuildingBlocks( @NonNull AtomicReference clearAllPipelinesForReconnectReference, boolean firstPlatform) { + public PlatformBuildingBlocks { + requireNonNull(platformContext); + requireNonNull(model); + requireNonNull(keysAndCerts); + requireNonNull(selfId); + requireNonNull(mainClassName); + requireNonNull(swirldName); + requireNonNull(appVersion); + requireNonNull(initialState); + requireNonNull(applicationCallbacks); + requireNonNull(intakeEventCounter); + requireNonNull(randomBuilder); + requireNonNull(transactionPoolNexus); + requireNonNull(intakeQueueSizeSupplierSupplier); + requireNonNull(isInFreezePeriodReference); + requireNonNull(latestImmutableStateProviderReference); + requireNonNull(initialPcesFiles); + requireNonNull(issScratchpad); + requireNonNull(notificationEngine); + requireNonNull(statusActionSubmitterReference); + requireNonNull(swirldStateManager); + requireNonNull(getLatestCompleteStateReference); + requireNonNull(loadReconnectStateReference); + requireNonNull(clearAllPipelinesForReconnectReference); + } + /** * Get the address book from the initial state. * diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java index e0d9147f39fc..4cf817fecc50 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java @@ -16,12 +16,20 @@ package com.swirlds.platform.system.address; +import static com.swirlds.platform.util.BootstrapUtils.detectSoftwareUpgrade; + import com.hedera.hapi.node.base.ServiceEndpoint; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.context.PlatformContext; import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.state.MerkleRoot; +import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.address.AddressBookInitializer; +import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.system.SoftwareVersion; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.net.InetAddress; @@ -372,4 +380,49 @@ private static RosterEntry toRosterEntry(@NonNull final Address address, @NonNul .gossipEndpoint(serviceEndpoints) .build(); } + /** + * Initializes the address book from the configuration and platform saved state. + * @param selfId the node ID of the current node + * @param version the software version of the current node + * @param initialState the initial state of the platform + * @param bootstrapAddressBook the bootstrap address book + * @param platformContext the platform context + * @return the initialized address book + */ + public static @NonNull AddressBook initializeAddressBook( + @NonNull final NodeId selfId, + @NonNull final SoftwareVersion version, + @NonNull final ReservedSignedState initialState, + @NonNull final AddressBook bootstrapAddressBook, + @NonNull final PlatformContext platformContext) { + final boolean softwareUpgrade = detectSoftwareUpgrade(version, initialState.get()); + // Initialize the address book from the configuration and platform saved state. + final AddressBookInitializer addressBookInitializer = new AddressBookInitializer( + selfId, version, softwareUpgrade, initialState.get(), bootstrapAddressBook.copy(), platformContext); + + if (addressBookInitializer.hasAddressBookChanged()) { + final MerkleRoot state = initialState.get().getState(); + // Update the address book with the current address book read from config.txt. + // Eventually we will not do this, and only transactions will be capable of + // modifying the address book. + final PlatformStateAccessor platformState = state.getWritablePlatformState(); + platformState.bulkUpdate(v -> { + v.setAddressBook(addressBookInitializer.getCurrentAddressBook().copy()); + v.setPreviousAddressBook( + addressBookInitializer.getPreviousAddressBook() == null + ? null + : addressBookInitializer + .getPreviousAddressBook() + .copy()); + }); + } + + // At this point the initial state must have the current address book set. If not, something is wrong. + final AddressBook addressBook = + initialState.get().getState().getReadablePlatformState().getAddressBook(); + if (addressBook == null) { + throw new IllegalStateException("The current address book of the initial state is null."); + } + return addressBook; + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java index f2f7f8777331..d8738b47f8d9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/module-info.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/module-info.java @@ -75,7 +75,8 @@ com.swirlds.platform.test, com.hedera.node.test.clients, com.swirlds.platform.core.test.fixtures, - com.hedera.node.app.test.fixtures; + com.hedera.node.app.test.fixtures, + com.hedera.node.app; exports com.swirlds.platform.event.linking to com.swirlds.common, com.swirlds.platform.test, @@ -123,6 +124,8 @@ exports com.swirlds.platform.state.snapshot; exports com.swirlds.platform.state.service.schemas; exports com.swirlds.platform.state.service; + exports com.swirlds.platform.builder.internal; + exports com.swirlds.platform.config.internal; requires transitive com.hedera.node.hapi; requires transitive com.swirlds.base; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java index ae84e1ba974b..32681c30e042 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/turtle/runner/TurtleNode.java @@ -16,6 +16,7 @@ package com.swirlds.platform.turtle.runner; +import static com.swirlds.platform.state.signed.StartupStateUtils.getInitialState; import static com.swirlds.platform.system.address.AddressBookUtils.createRoster; import com.swirlds.base.time.Time; @@ -32,6 +33,7 @@ import com.swirlds.platform.config.BasicConfig_; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.eventhandling.EventConfig_; +import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.snapshot.SignedStateFileUtils; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.Platform; @@ -41,6 +43,7 @@ import com.swirlds.platform.util.RandomBuilder; import com.swirlds.platform.wiring.PlatformSchedulersConfig_; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.Supplier; /** * Encapsulates a single node running in a TURTLE network. @@ -94,25 +97,26 @@ public class TurtleNode { model = WiringModelBuilder.create(platformContext) .withDeterministicModeEnabled(true) .build(); - + final Supplier genesisStateSupplier = TurtleTestingToolState::getStateRootNode; + final var version = new BasicSoftwareVersion(1); + final var initialState = getInitialState( + platformContext, + version, + genesisStateSupplier, + SignedStateFileUtils::readState, + "foo", + "bar", + nodeId, + addressBook); final PlatformBuilder platformBuilder = PlatformBuilder.create( - "foo", - "bar", - new BasicSoftwareVersion(1), - TurtleTestingToolState::getStateRootNode, - SignedStateFileUtils::readState, - nodeId) + "foo", "bar", new BasicSoftwareVersion(1), initialState, nodeId) .withModel(model) - .withCryptography(platformContext.getCryptography()) - .withMetrics(platformContext.getMetrics()) - .withTime(platformContext.getTime()) - .withFileSystemManager(platformContext.getFileSystemManager()) - .withRecycleBin(platformContext.getRecycleBin()) - .withExecutorFactory(platformContext.getExecutorFactory()) .withRandomBuilder(new RandomBuilder(randotron.nextLong())) - .withBootstrapAddressBook(addressBook) + .withAddressBook(addressBook) .withRoster(createRoster(addressBook)) - .withKeysAndCerts(privateKeys); + .withKeysAndCerts(privateKeys) + .withPlatformContext(platformContext) + .withConfiguration(configuration); final PlatformComponentBuilder platformComponentBuilder = platformBuilder.buildComponentBuilder(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java index 29a5b4d8a346..89c4fa4f7ce9 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java @@ -32,6 +32,7 @@ import com.swirlds.platform.builder.PlatformBuilder; import com.swirlds.platform.network.Network; import com.swirlds.platform.state.address.AddressBookNetworkUtils; +import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -97,8 +98,7 @@ void testCreateRosterFromNonEmptyAddressBook() { "name", "swirldName", new BasicSoftwareVersion(1), - () -> null, - (inputStream, path) -> null, + ReservedSignedState.createNullReservation(), new NodeId(0)); final Address address1 = new Address(new NodeId(1), "", "", 10, null, 77, null, 88, null, null, ""); @@ -106,7 +106,7 @@ void testCreateRosterFromNonEmptyAddressBook() { final AddressBook addressBook = new AddressBook(); addressBook.add(address1); addressBook.add(address2); - platformBuilder.withBootstrapAddressBook(addressBook); + platformBuilder.withAddressBook(addressBook); final Roster roster = AddressBookUtils.createRoster(addressBook); assertNotNull(roster); @@ -129,11 +129,10 @@ void testCreateRosterFromEmptyAddressBook() { "name", "swirldName", new BasicSoftwareVersion(1), - () -> null, - (inputStream, path) -> null, + ReservedSignedState.createNullReservation(), new NodeId(0)); final AddressBook addressBook = new AddressBook(); - platformBuilder.withBootstrapAddressBook(addressBook); + platformBuilder.withAddressBook(addressBook); final Roster roster = AddressBookUtils.createRoster(addressBook); assertNotNull(roster); From 1c641aba51b8062520a9788ad3850ea88013a0e3 Mon Sep 17 00:00:00 2001 From: Matt Hess Date: Wed, 11 Sep 2024 09:44:29 -0500 Subject: [PATCH 09/16] fix: Check for usability in various ops (#15390) Signed-off-by: Matt Hess --- .../token/impl/handlers/BaseTokenHandler.java | 46 ++++++---- .../CryptoApproveAllowanceHandler.java | 83 ++++++++++++------- .../CryptoDeleteAllowanceHandler.java | 12 +-- .../impl/handlers/CryptoUpdateHandler.java | 5 +- .../handlers/TokenAccountWipeHandler.java | 8 +- .../TokenAssociateToAccountHandler.java | 23 +++-- .../token/impl/handlers/TokenBurnHandler.java | 12 ++- .../impl/handlers/TokenCreateHandler.java | 9 +- .../handlers/TokenFreezeAccountHandler.java | 9 +- .../token/impl/handlers/TokenMintHandler.java | 48 +++++++---- .../impl/handlers/TokenUpdateHandler.java | 5 +- .../src/main/java/module-info.java | 3 +- .../CryptoDeleteAllowanceHandlerTest.java | 1 + .../handlers/CryptoUpdateHandlerTest.java | 4 + .../TokenAssociateToAccountHandlerTest.java | 12 +++ .../test/handlers/TokenBurnHandlerTest.java | 10 +++ .../TokenFreezeAccountHandlerTest.java | 4 +- .../AssociateTokenRecipientsStepTest.java | 5 +- .../util/CryptoTokenHandlerTestBase.java | 14 +++- .../bdd/suites/crypto/CryptoUpdateSuite.java | 15 ++++ .../suites/token/TokenAssociationSpecs.java | 19 +++++ .../bdd/suites/token/TokenTransactSpecs.java | 21 +++++ .../bdd/suites/token/TokenUpdateSpecs.java | 30 +++++++ 23 files changed, 302 insertions(+), 96 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java index d346996d1ccc..8e477646ee35 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/BaseTokenHandler.java @@ -42,7 +42,9 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.util.TokenKey; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.config.data.EntitiesConfig; import com.hedera.node.config.data.TokensConfig; import com.swirlds.config.api.Configuration; @@ -77,12 +79,14 @@ public class BaseTokenHandler { /** * Mints fungible tokens. This method is called in both token create and mint. - * @param token the new or existing token to mint - * @param treasuryRel the treasury relation for the token - * @param amount the amount to mint - * @param accountStore the account store - * @param tokenStore the token store + * + * @param token the new or existing token to mint + * @param treasuryRel the treasury relation for the token + * @param amount the amount to mint + * @param accountStore the account store + * @param tokenStore the token store * @param tokenRelationStore the token relation store + * @param expiryValidator the expiry validator */ protected long mintFungible( @NonNull final Token token, @@ -90,12 +94,20 @@ protected long mintFungible( final long amount, @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelationStore) { + @NonNull final WritableTokenRelationStore tokenRelationStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(token); requireNonNull(treasuryRel); return changeSupply( - token, treasuryRel, +amount, INVALID_TOKEN_MINT_AMOUNT, accountStore, tokenStore, tokenRelationStore); + token, + treasuryRel, + +amount, + INVALID_TOKEN_MINT_AMOUNT, + accountStore, + tokenStore, + tokenRelationStore, + expiryValidator); } /** @@ -105,13 +117,14 @@ protected long mintFungible( *

      * Note: This method assumes the given token has a non-null supply key! * - * @param token the token that is minted or burned - * @param treasuryRel the treasury relation for the token - * @param amount the amount to mint or burn - * @param invalidSupplyCode the invalid supply code to use if the supply is invalid - * @param accountStore the account store - * @param tokenStore the token store + * @param token the token that is minted or burned + * @param treasuryRel the treasury relation for the token + * @param amount the amount to mint or burn + * @param invalidSupplyCode the invalid supply code to use if the supply is invalid + * @param accountStore the account store + * @param tokenStore the token store * @param tokenRelationStore the token relation store + * @param expiryValidator the expiry validator */ protected long changeSupply( @NonNull final Token token, @@ -120,7 +133,8 @@ protected long changeSupply( @NonNull final ResponseCodeEnum invalidSupplyCode, @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelationStore) { + @NonNull final WritableTokenRelationStore tokenRelationStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(token); requireNonNull(treasuryRel); requireNonNull(invalidSupplyCode); @@ -140,8 +154,8 @@ protected long changeSupply( validateTrue(token.maxSupply() >= newTotalSupply, TOKEN_MAX_SUPPLY_REACHED); } - final var treasuryAccount = accountStore.get(treasuryRel.accountId()); - validateTrue(treasuryAccount != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + final var treasuryAccount = TokenHandlerHelper.getIfUsable( + treasuryRel.accountId(), accountStore, expiryValidator, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); final long newTreasuryBalance = treasuryRel.balance() + amount; validateTrue(newTreasuryBalance >= 0, INSUFFICIENT_TOKEN_BALANCE); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java index c93d986638e3..51a843f38b09 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoApproveAllowanceHandler.java @@ -41,6 +41,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Account; @@ -56,9 +57,11 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.ApproveAllowanceValidator; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -186,8 +189,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); // Validate payer account exists - final var payerAccount = accountStore.getAccountById(payer); - validateTrue(payerAccount != null, INVALID_PAYER_ACCOUNT_ID); + final var payerAccount = TokenHandlerHelper.getIfUsable( + payer, accountStore, context.expiryValidator(), INVALID_PAYER_ACCOUNT_ID); // Validate the transaction body fields that include state or configuration. validateSemantics(context, payerAccount, accountStore); @@ -241,26 +244,36 @@ private void approveAllowance( /* --- Apply changes to state --- */ final var allowanceMaxAccountLimit = hederaConfig.allowancesMaxAccountLimit(); - applyCryptoAllowances(cryptoAllowances, payerId, accountStore, allowanceMaxAccountLimit); - applyFungibleTokenAllowances(tokenAllowances, payerId, accountStore, allowanceMaxAccountLimit); + final var expiryValidator = context.expiryValidator(); + applyCryptoAllowances(cryptoAllowances, payerId, accountStore, allowanceMaxAccountLimit, expiryValidator); + applyFungibleTokenAllowances(tokenAllowances, payerId, accountStore, allowanceMaxAccountLimit, expiryValidator); applyNftAllowances( - nftAllowances, payerId, accountStore, tokenStore, uniqueTokenStore, allowanceMaxAccountLimit); + nftAllowances, + payerId, + accountStore, + tokenStore, + uniqueTokenStore, + allowanceMaxAccountLimit, + expiryValidator); } /** * Applies all changes needed for Crypto allowances from the transaction. * If the spender already has an allowance, the allowance value will be replaced with values * from transaction. If the amount specified is 0, the allowance will be removed. - * @param cryptoAllowances the list of crypto allowances - * @param payerId the payer account id - * @param accountStore the account store + * + * @param cryptoAllowances the list of crypto allowances + * @param payerId the payer account id + * @param accountStore the account store * @param allowanceMaxAccountLimit the {@link HederaConfig} + * @param expiryValidator the expiry validator */ private void applyCryptoAllowances( @NonNull final List cryptoAllowances, @NonNull final AccountID payerId, @NonNull final WritableAccountStore accountStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(cryptoAllowances); requireNonNull(payerId); requireNonNull(accountStore); @@ -268,7 +281,7 @@ private void applyCryptoAllowances( for (final var allowance : cryptoAllowances) { final var owner = allowance.owner(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableAllowances = new ArrayList<>(effectiveOwner.cryptoAllowances()); final var amount = allowance.amount(); @@ -318,23 +331,25 @@ private void updateCryptoAllowance( * Applies all changes needed for fungible token allowances from the transaction. If the key * {token, spender} already has an allowance, the allowance value will be replaced with values * from transaction. - * @param tokenAllowances the list of token allowances - * @param payerId the payer account id - * @param accountStore the account store + * @param tokenAllowances the list of token allowances + * @param payerId the payer account id + * @param accountStore the account store * @param allowanceMaxAccountLimit the {@link HederaConfig} allowance max account limit + * @param expiryValidator the expiry validator */ private void applyFungibleTokenAllowances( @NonNull final List tokenAllowances, @NonNull final AccountID payerId, @NonNull final WritableAccountStore accountStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { for (final var allowance : tokenAllowances) { final var owner = allowance.owner(); final var amount = allowance.amount(); final var tokenId = allowance.tokenIdOrThrow(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableTokenAllowances = new ArrayList<>(effectiveOwner.tokenAllowances()); updateTokenAllowance(mutableTokenAllowances, amount, spender, tokenId); @@ -387,12 +402,13 @@ private void updateTokenAllowance( * spenderNum} doesn't exist in the map the allowance will be inserted. If the key exists, * existing allowance values will be replaced with new allowances given in operation * - * @param nftAllowances the list of nft allowances - * @param payerId the payer account id - * @param accountStore the account store - * @param tokenStore the token store - * @param uniqueTokenStore the unique token store + * @param nftAllowances the list of nft allowances + * @param payerId the payer account id + * @param accountStore the account store + * @param tokenStore the token store + * @param uniqueTokenStore the unique token store * @param allowanceMaxAccountLimit the {@link HederaConfig} config allowance max account limit + * @param expiryValidator the expiry validator */ protected void applyNftAllowances( final List nftAllowances, @@ -400,13 +416,14 @@ protected void applyNftAllowances( @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, @NonNull final WritableNftStore uniqueTokenStore, - final int allowanceMaxAccountLimit) { + final int allowanceMaxAccountLimit, + @NonNull final ExpiryValidator expiryValidator) { for (final var allowance : nftAllowances) { final var owner = allowance.owner(); final var tokenId = allowance.tokenIdOrThrow(); final var spender = allowance.spenderOrThrow(); - final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore); + final var effectiveOwner = getEffectiveOwnerAccount(owner, payerId, accountStore, expiryValidator); final var mutableNftAllowances = new ArrayList<>(effectiveOwner.approveForAllNftAllowances()); if (allowance.hasApprovedForAll()) { @@ -456,8 +473,11 @@ public void updateSpender( final var serialsSet = new HashSet<>(serialNums); for (final var serialNum : serialsSet) { - final var nft = uniqueTokenStore.get(tokenId, serialNum); - final var token = tokenStore.get(tokenId); + final var nftId = + NftID.newBuilder().serialNumber(serialNum).tokenId(tokenId).build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, uniqueTokenStore); + final var token = TokenHandlerHelper.getIfUsable( + tokenId, tokenStore, TokenHandlerHelper.TokenValidations.PERMIT_PAUSED); final AccountID accountOwner = owner.accountId(); validateTrue(isValidOwner(nft, accountOwner, token), SENDER_DOES_NOT_OWN_NFT_SERIAL_NO); @@ -508,15 +528,18 @@ private int lookupSpenderAndToken( * Returns the effective owner account. If the owner is not present or owner is same as payer. * Since we are modifying the payer account in the same transaction for each allowance if owner is not specified, * we need to get the payer account each time from the modifications map. - * @param owner owner of the allowance - * @param payerId payer of the transaction - * @param accountStore account store + * + * @param owner owner of the allowance + * @param payerId payer of the transaction + * @param accountStore account store + * @param expiryValidator the expiry validator * @return effective owner account */ private static Account getEffectiveOwnerAccount( @Nullable final AccountID owner, @NonNull final AccountID payerId, - @NonNull final ReadableAccountStore accountStore) { + @NonNull final ReadableAccountStore accountStore, + @NonNull final ExpiryValidator expiryValidator) { final var ownerNum = owner != null ? owner.accountNumOrElse(0L) : 0L; if (ownerNum == 0 || ownerNum == payerId.accountNumOrThrow()) { // The payer would have been modified in the same transaction for previous allowances @@ -524,8 +547,8 @@ private static Account getEffectiveOwnerAccount( return accountStore.getAccountById(payerId); } else { // If owner is in modifications get the modified account from state - final var ownerAccount = accountStore.getAccountById(owner); - validateTrue(ownerAccount != null, INVALID_ALLOWANCE_OWNER_ID); + final var ownerAccount = + TokenHandlerHelper.getIfUsable(owner, accountStore, expiryValidator, INVALID_ALLOWANCE_OWNER_ID); return ownerAccount; } } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java index 14684156873c..78812d9b856c 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoDeleteAllowanceHandler.java @@ -18,7 +18,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.EMPTY_ALLOWANCES; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ALLOWANCE_OWNER_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NFT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PAYER_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SENDER_DOES_NOT_OWN_NFT_SERIAL_NO; @@ -33,6 +32,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.HederaFunctionality; +import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.SubType; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.state.token.Account; @@ -42,6 +42,7 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableNftStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.DeleteAllowanceValidator; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; @@ -113,8 +114,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); // validate payer account exists - final var payerAccount = accountStore.getAccountById(payer); - validateTrue(payerAccount != null, INVALID_PAYER_ACCOUNT_ID); + final var payerAccount = TokenHandlerHelper.getIfUsable( + payer, accountStore, context.expiryValidator(), INVALID_PAYER_ACCOUNT_ID); // validate the transaction body fields that include state or configuration // We can use payerAccount for validations since it's not mutated in validateSemantics @@ -180,9 +181,10 @@ private void deleteNftSerials( final var owner = getEffectiveOwner(allowance.owner(), payerAccount, accountStore, expiryValidator); final var token = tokenStore.get(tokenId); for (final var serial : serialNums) { - final var nft = nftStore.get(tokenId, serial); + final var nftId = + NftID.newBuilder().serialNumber(serial).tokenId(tokenId).build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, nftStore); - validateTrue(nft != null, INVALID_NFT_ID); final AccountID accountOwner = owner.accountId(); validateTrue(isValidOwner(nft, accountOwner, token), SENDER_DOES_NOT_OWN_NFT_SERIAL_NO); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java index ea67624613a8..4cd8e94db1f7 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/CryptoUpdateHandler.java @@ -55,6 +55,7 @@ import com.hedera.node.app.service.token.CryptoSignatureWaivers; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.StakingValidator; import com.hedera.node.app.service.token.records.CryptoUpdateStreamBuilder; import com.hedera.node.app.spi.fees.FeeCalculator; @@ -147,8 +148,8 @@ public void handle(@NonNull final HandleContext context) { // validate update account exists final var accountStore = context.storeFactory().writableStore(WritableAccountStore.class); - final var targetAccount = accountStore.get(target); - validateTrue(targetAccount != null, INVALID_ACCOUNT_ID); + final var targetAccount = + TokenHandlerHelper.getIfUsable(target, accountStore, context.expiryValidator(), INVALID_ACCOUNT_ID); context.attributeValidator().validateMemo(op.memo()); // Customize the account based on fields set in transaction body diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java index eca905121322..63c9074cff55 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAccountWipeHandler.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DOES_NOT_OWN_WIPED_NFT; import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; -import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_NFT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_WIPING_AMOUNT; import static com.hedera.node.app.hapi.fees.usage.SingletonUsageProperties.USAGE_PROPERTIES; @@ -169,8 +168,11 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Load and validate the nfts for (final Long nftSerial : nftSerialNums) { - final var nft = nftStore.get(tokenId, nftSerial); - validateTrue(nft != null, INVALID_NFT_ID); + final var nftId = NftID.newBuilder() + .serialNumber(nftSerial) + .tokenId(tokenId) + .build(); + final var nft = TokenHandlerHelper.getIfUsable(nftId, nftStore); final var nftOwner = nft.ownerId(); validateTrue(Objects.equals(nftOwner, unaliasedId), ACCOUNT_DOES_NOT_OWN_WIPED_NFT); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java index 1a51c5521298..288a1577a1b2 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenAssociateToAccountHandler.java @@ -16,7 +16,6 @@ package com.hedera.node.app.service.token.impl.handlers; -import static com.hedera.hapi.node.base.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED; @@ -29,7 +28,6 @@ import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.hasAccountNumOrAlias; import static com.hedera.node.app.service.token.impl.util.TokenHandlerHelper.getIfUsable; import static com.hedera.node.app.spi.fees.Fees.CONSTANT_FEE_DATA; -import static com.hedera.node.app.spi.workflows.HandleException.validateFalse; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static com.hedera.node.app.spi.workflows.PreCheckException.validateFalsePreCheck; import static com.hedera.node.app.spi.workflows.PreCheckException.validateTruePreCheck; @@ -50,9 +48,11 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.validators.TokenListChecks; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -102,7 +102,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var accountStore = storeFactory.writableStore(WritableAccountStore.class); final var tokenRelStore = storeFactory.writableStore(WritableTokenRelationStore.class); final var validated = validateSemantics( - tokenIds, op.accountOrThrow(), tokensConfig, entitiesConfig, accountStore, tokenStore, tokenRelStore); + tokenIds, + op.accountOrThrow(), + tokensConfig, + entitiesConfig, + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); // Now that we've validated we can link all the new token IDs to the account, // create the corresponding token relations and update the account @@ -133,7 +140,8 @@ private Validated validateSemantics( @NonNull final EntitiesConfig entitiesConfig, @NonNull final WritableAccountStore accountStore, @NonNull final ReadableTokenStore tokenStore, - @NonNull final WritableTokenRelationStore tokenRelStore) { + @NonNull final WritableTokenRelationStore tokenRelStore, + @NonNull final ExpiryValidator expiryValidator) { requireNonNull(tokenConfig); requireNonNull(entitiesConfig); @@ -142,10 +150,9 @@ private Validated validateSemantics( isTotalNumTokenRelsWithinMax(tokenIds.size(), tokenRelStore, tokenConfig.maxAggregateRels()), MAX_ENTITIES_IN_PRICE_REGIME_HAVE_BEEN_CREATED); - // Check that the account exists - final var account = accountStore.get(accountId); - validateTrue(account != null, INVALID_ACCOUNT_ID); - validateFalse(account.deleted(), ACCOUNT_DELETED); + // Check that the account is usable + final var account = + TokenHandlerHelper.getIfUsable(accountId, accountStore, expiryValidator, INVALID_ACCOUNT_ID); // Check that the given tokens exist and are usable final var tokens = new ArrayList(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java index 4942d0fce953..2c8ad612ef74 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenBurnHandler.java @@ -136,7 +136,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException INVALID_TOKEN_BURN_AMOUNT, accountStore, tokenStore, - tokenRelStore); + tokenRelStore, + context.expiryValidator()); record.newTotalSupply(newTotalSupply); } else { validateTrue(!nftSerialNums.isEmpty(), INVALID_TOKEN_BURN_METADATA); @@ -152,7 +153,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Update counts for accounts and token rels final var newTotalSupply = changeSupply( - token, treasuryRel, -nftSerialNums.size(), FAIL_INVALID, accountStore, tokenStore, tokenRelStore); + token, + treasuryRel, + -nftSerialNums.size(), + FAIL_INVALID, + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); // Update treasury's NFT count final var treasuryAcct = accountStore.get(token.treasuryAccountIdOrThrow()); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java index 280cee2a2fc8..7f464a7c8cc1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenCreateHandler.java @@ -163,7 +163,14 @@ public void handle(@NonNull final HandleContext context) { final var treasuryRel = requireNonNull(tokenRelationStore.get(op.treasuryOrThrow(), newTokenId)); if (op.initialSupply() > 0) { // This keeps modified token with minted balance into modifications in token store - mintFungible(newToken, treasuryRel, op.initialSupply(), accountStore, tokenStore, tokenRelationStore); + mintFungible( + newToken, + treasuryRel, + op.initialSupply(), + accountStore, + tokenStore, + tokenRelationStore, + context.expiryValidator()); } // Increment treasury's title count final var treasuryAccount = requireNonNull(accountStore.get(treasuryRel.accountIdOrThrow())); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java index dc83931d7d05..cf0f278d802d 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenFreezeAccountHandler.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; -import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; import static com.hedera.node.app.hapi.fees.usage.token.TokenOpsUsageUtils.TOKEN_OPS_USAGE_UTILS; import static com.hedera.node.app.spi.workflows.HandleException.validateTrue; import static java.util.Objects.requireNonNull; @@ -143,12 +142,8 @@ private TokenRelation validateSemantics( // Check that token exists TokenHandlerHelper.getIfUsable(tokenId, tokenStore); - // Check that the token is associated to the account - final var tokenRel = tokenRelStore.getForModify(accountId, tokenId); - validateTrue(tokenRel != null, TOKEN_NOT_ASSOCIATED_TO_ACCOUNT); - - // Return the token relation - return tokenRel; + // Check that the token is associated to the account, and return it + return TokenHandlerHelper.getIfUsable(accountId, tokenId, tokenRelStore); } @NonNull diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java index 43f6f7d83b04..75c467ff1324 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenMintHandler.java @@ -57,6 +57,7 @@ import com.hedera.node.app.service.token.records.TokenMintStreamBuilder; import com.hedera.node.app.spi.fees.FeeContext; import com.hedera.node.app.spi.fees.Fees; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -138,8 +139,14 @@ public void handle(@NonNull final HandleContext context) throws HandleException if (token.tokenType() == TokenType.FUNGIBLE_COMMON) { validateTrue(op.amount() >= 0, INVALID_TOKEN_MINT_AMOUNT); // we need to know if treasury mint while creation to ignore supply key exist or not. - long newTotalSupply = - mintFungible(token, treasuryRel, op.amount(), accountStore, tokenStore, tokenRelStore); + long newTotalSupply = mintFungible( + token, + treasuryRel, + op.amount(), + accountStore, + tokenStore, + tokenRelStore, + context.expiryValidator()); recordBuilder.newTotalSupply(newTotalSupply); } else { // get the config needed for validation @@ -159,7 +166,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException accountStore, tokenStore, tokenRelStore, - nftStore); + nftStore, + context.expiryValidator()); recordBuilder.newTotalSupply(tokenStore.get(tokenId).totalSupply()); recordBuilder.serialNumbers(mintedSerials); } @@ -182,14 +190,15 @@ private void validateSemantics(final HandleContext context) { * serial number of the given base unique token, and increments total owned nfts of the * non-fungible token. * - * @param token - the token to mint nfts for - * @param treasuryRel - the treasury relation of the token - * @param metadata - the metadata of the nft to be minted - * @param consensusTime - the consensus time of the transaction - * @param accountStore - the account store - * @param tokenStore - the token store - * @param tokenRelStore - the token relation store - * @param nftStore - the nft store + * @param token - the token to mint nfts for + * @param treasuryRel - the treasury relation of the token + * @param metadata - the metadata of the nft to be minted + * @param consensusTime - the consensus time of the transaction + * @param accountStore - the account store + * @param tokenStore - the token store + * @param tokenRelStore - the token relation store + * @param nftStore - the nft store + * @param expiryValidator - the expiry validator */ private List mintNonFungible( final Token token, @@ -199,7 +208,8 @@ private List mintNonFungible( @NonNull final WritableAccountStore accountStore, @NonNull final WritableTokenStore tokenStore, @NonNull final WritableTokenRelationStore tokenRelStore, - @NonNull final WritableNftStore nftStore) { + @NonNull final WritableNftStore nftStore, + @NonNull final ExpiryValidator expiryValidator) { final var metadataCount = metadata.size(); validateFalse(metadata.isEmpty(), INVALID_TOKEN_MINT_METADATA); @@ -207,15 +217,23 @@ private List mintNonFungible( final var tokenId = treasuryRel.tokenId(); // get the treasury account - var treasuryAccount = accountStore.get(treasuryRel.accountIdOrThrow()); - validateTrue(treasuryAccount != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + var treasuryAccount = TokenHandlerHelper.getIfUsable( + treasuryRel.accountIdOrThrow(), accountStore, expiryValidator, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); // get the latest serial number minted for the token var currentSerialNumber = token.lastUsedSerialNumber(); validateTrue((currentSerialNumber + metadataCount) <= MAX_SERIAL_NO_ALLOWED, SERIAL_NUMBER_LIMIT_REACHED); // Change the supply on token - changeSupply(token, treasuryRel, metadataCount, FAIL_INVALID, accountStore, tokenStore, tokenRelStore); + changeSupply( + token, + treasuryRel, + metadataCount, + FAIL_INVALID, + accountStore, + tokenStore, + tokenRelStore, + expiryValidator); // Since changeSupply call above modifies the treasuryAccount, we need to get the modified treasuryAccount treasuryAccount = accountStore.get(treasuryRel.accountIdOrThrow()); // The token is modified in previous step, so we need to get the modified token diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java index 2752d16a73ed..8977c733730a 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenUpdateHandler.java @@ -58,6 +58,7 @@ import com.hedera.node.app.service.token.impl.WritableAccountStore; import com.hedera.node.app.service.token.impl.WritableTokenRelationStore; import com.hedera.node.app.service.token.impl.WritableTokenStore; +import com.hedera.node.app.service.token.impl.util.TokenHandlerHelper; import com.hedera.node.app.service.token.impl.util.TokenKey; import com.hedera.node.app.service.token.impl.validators.TokenUpdateValidator; import com.hedera.node.app.service.token.records.TokenUpdateStreamBuilder; @@ -170,8 +171,8 @@ public void handle(@NonNull final HandleContext context) throws HandleException // Treasury can be modified when it owns NFTs when the property "tokens.nfts.useTreasuryWildcards" // is enabled. if (!tokensConfig.nftsUseTreasuryWildcards() && token.tokenType().equals(NON_FUNGIBLE_UNIQUE)) { - final var existingTreasuryRel = tokenRelStore.get(existingTreasury, tokenId); - validateTrue(existingTreasuryRel != null, INVALID_TREASURY_ACCOUNT_FOR_TOKEN); + final var existingTreasuryRel = + TokenHandlerHelper.getIfUsable(existingTreasury, tokenId, tokenRelStore); final var tokenRelBalance = existingTreasuryRel.balance(); validateTrue(tokenRelBalance == 0, CURRENT_TREASURY_STILL_OWNS_NFTS); } diff --git a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java index 8b2c1e55c8b1..e5ba7bd85c1e 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/module-info.java @@ -28,7 +28,8 @@ exports com.hedera.node.app.service.token.impl.handlers to com.hedera.node.app, - com.hedera.node.app.service.token.impl.test; + com.hedera.node.app.service.token.impl.test, + com.hedera.node.test.clients; exports com.hedera.node.app.service.token.impl; exports com.hedera.node.app.service.token.impl.api to com.hedera.node.app, diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java index 5536c049a51d..fe206a575965 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoDeleteAllowanceHandlerTest.java @@ -71,6 +71,7 @@ public void setUp() { given(handleContext.configuration()).willReturn(configuration); given(handleContext.expiryValidator()).willReturn(expiryValidator); + given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); } @Test diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java index fff176cb6b2a..f29e8e781472 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/CryptoUpdateHandlerTest.java @@ -27,6 +27,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_EXPIRATION_TIME; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_STAKING_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.MEMO_TOO_LONG; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.PROXY_ACCOUNT_ID_FIELD_IS_DEPRECATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; import static com.hedera.hapi.node.base.ResponseCodeEnum.STAKING_NOT_ENABLED; @@ -146,6 +147,9 @@ public void setUp() { updateReadableAccountStore(Map.of(updateAccountId.accountNum(), updateAccount, accountNum, account)); lenient().when(handleContext.savepointStack()).thenReturn(stack); lenient().when(stack.getBaseBuilder(CryptoUpdateStreamBuilder.class)).thenReturn(streamBuilder); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(OK); subject = new CryptoUpdateHandler(waivers); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java index a13e38f7a1fa..22bcb3d20355 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenAssociateToAccountHandlerTest.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_ID_REPEATED_IN_TOKEN_LIST; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_PAUSED; @@ -33,7 +34,11 @@ import static com.hedera.node.app.service.token.impl.test.keys.KeysAndIds.MISC_ACCOUNT; import static com.hedera.node.app.spi.fixtures.workflows.ExceptionConditions.responseCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mock.Strictness.LENIENT; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -53,6 +58,7 @@ import com.hedera.node.app.service.token.impl.test.handlers.util.ParityTestBase; import com.hedera.node.app.spi.fixtures.workflows.FakePreHandleContext; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -86,8 +92,14 @@ class TokenAssociateToAccountHandlerTest { @Mock private StoreFactory storeFactory; + @Mock(strictness = LENIENT) + private ExpiryValidator expiryValidator; + @BeforeEach void setUp() { + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(OK); + subject = new TokenAssociateToAccountHandler(); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java index 8ea4083b7937..42e7614403c9 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenBurnHandlerTest.java @@ -26,6 +26,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TRANSACTION_BODY; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TREASURY_ACCOUNT_FOR_TOKEN; import static com.hedera.hapi.node.base.ResponseCodeEnum.NOT_SUPPORTED; +import static com.hedera.hapi.node.base.ResponseCodeEnum.OK; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_HAS_NO_SUPPLY_KEY; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_IS_PAUSED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; @@ -42,6 +43,9 @@ import static com.hedera.node.app.spi.workflows.record.StreamBuilder.ReversingBehavior.REVERSIBLE; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; @@ -72,6 +76,7 @@ import com.hedera.node.app.spi.fixtures.state.MapWritableStates; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.PreCheckException; @@ -868,6 +873,11 @@ private HandleContext mockContext(TransactionBody txn) { given(storeFactory.writableStore(WritableNftStore.class)).willReturn(writableNftStore); given(context.configuration()).willReturn(configuration); lenient().when(context.savepointStack()).thenReturn(stack); + final var expiryValidator = mock(ExpiryValidator.class); + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(OK); return context; } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java index ec9a9964c522..17eaed27a331 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenFreezeAccountHandlerTest.java @@ -253,7 +253,7 @@ void tokenRelNotFound() throws HandleException { given(readableTokenStore.getTokenMeta(token)).willReturn(tokenMetaWithFreezeKey()); given(readableAccountStore.getAccountById(ACCOUNT_13257)) .willReturn(Account.newBuilder().accountId(ACCOUNT_13257).build()); - given(tokenRelStore.getForModify(ACCOUNT_13257, token)).willReturn(null); + given(tokenRelStore.get(ACCOUNT_13257, token)).willReturn(null); given(expiryValidator.expirationStatus(EntityType.ACCOUNT, false, 0)) .willReturn(OK); final var txn = newFreezeTxn(token); @@ -273,7 +273,7 @@ void tokenRelFreezeSuccessful() { given(readableTokenStore.getTokenMeta(token)).willReturn(tokenMetaWithFreezeKey()); given(readableAccountStore.getAccountById(ACCOUNT_13257)) .willReturn(Account.newBuilder().accountId(ACCOUNT_13257).build()); - given(tokenRelStore.getForModify(ACCOUNT_13257, token)) + given(tokenRelStore.get(ACCOUNT_13257, token)) .willReturn(TokenRelation.newBuilder() .tokenId(token) .accountId(ACCOUNT_13257) diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java index 213b502b6bec..a27935c60d8a 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/transfer/AssociateTokenRecipientsStepTest.java @@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.lenient; import com.hedera.hapi.node.base.AccountAmount; import com.hedera.hapi.node.base.AccountID; @@ -115,7 +116,9 @@ void givenValidTxn() { .build(); given(handleContext.configuration()).willReturn(configuration); given(handleContext.expiryValidator()).willReturn(expiryValidator); - given(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())).willReturn(ResponseCodeEnum.OK); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(ResponseCodeEnum.OK); given(handleContext.savepointStack()).willReturn(stack); } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java index f98ce345e12d..f573577db529 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/util/CryptoTokenHandlerTestBase.java @@ -26,8 +26,12 @@ import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; @@ -37,6 +41,7 @@ import com.hedera.hapi.node.base.NftID; import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.PendingAirdropValue; +import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TokenID; import com.hedera.hapi.node.base.TokenSupplyType; @@ -86,6 +91,7 @@ import com.hedera.node.app.spi.fees.Fees; import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.store.StoreFactory; +import com.hedera.node.app.spi.validation.ExpiryValidator; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.PreHandleContext; import com.hedera.node.config.VersionedConfigImpl; @@ -1173,7 +1179,13 @@ protected void givenStoresAndConfig(final HandleContext context) { .willReturn(readableRewardsStore); given(storeFactory.writableStore(WritableNetworkStakingRewardsStore.class)) .willReturn(writableRewardsStore); - given(context.dispatchComputeFees(any(), any(), any())).willReturn(new Fees(1l, 2l, 3l)); + given(context.dispatchComputeFees(any(), any(), any())).willReturn(new Fees(1L, 2L, 3L)); + + final var expiryValidator = mock(ExpiryValidator.class); + lenient().when(context.expiryValidator()).thenReturn(expiryValidator); + lenient() + .when(expiryValidator.expirationStatus(any(), anyBoolean(), anyLong())) + .thenReturn(ResponseCodeEnum.OK); } protected void givenStoresAndConfig(final FinalizeContext context) { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java index 04c5f8d0271e..4b038e755376 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoUpdateSuite.java @@ -35,6 +35,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; @@ -50,6 +51,7 @@ import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.EXPECT_STREAMLINED_INGEST_RECORDS; import static com.hedera.services.bdd.spec.utilops.records.SnapshotMatchMode.NONDETERMINISTIC_TRANSACTION_FEES; +import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; @@ -57,6 +59,7 @@ import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.contract.hapi.ContractUpdateSuite.ADMIN_KEY; import static com.hederahashgraph.api.proto.java.HederaFunctionality.CryptoUpdate; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EXISTING_AUTOMATIC_ASSOCIATIONS_EXCEED_GIVEN_LIMIT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_EXPIRATION_TIME; @@ -549,4 +552,16 @@ final Stream updateMaxAutoAssociationsWorks() { contractUpdate(CONTRACT).newMaxAutomaticAssociations(-1).hasKnownStatus(SUCCESS), getContractInfo(CONTRACT).has(contractWith().maxAutoAssociations(-1))); } + + @HapiTest + final Stream deletedAccountCannotBeUpdated() { + final var accountToDelete = "accountToDelete"; + return hapiTest( + cryptoCreate(accountToDelete).declinedReward(false), + cryptoDelete(accountToDelete), + cryptoUpdate(accountToDelete) + .payingWith(DEFAULT_PAYER) + .newDeclinedReward(true) + .hasKnownStatus(ACCOUNT_DELETED)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java index 5a7201250925..135dcb515a0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenAssociationSpecs.java @@ -71,6 +71,7 @@ import static com.hederahashgraph.api.proto.java.TokenFreezeStatus.Unfrozen; import static com.hederahashgraph.api.proto.java.TokenKycStatus.Granted; import static com.hederahashgraph.api.proto.java.TokenKycStatus.KycNotApplicable; +import static com.hederahashgraph.api.proto.java.TokenType.FUNGIBLE_COMMON; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import com.google.protobuf.ByteString; @@ -583,4 +584,22 @@ public static HapiSpecOperation[] basicKeysAndTokens() { tokenCreate(VANILLA_TOKEN).treasury(TOKEN_TREASURY) }; } + + @HapiTest + final Stream deletedAccountCannotBeAssociatedToToken() { + final var accountToDelete = "accountToDelete"; + final var token = "anyToken"; + final var supplyKey = "supplyKey"; + return hapiTest( + newKeyNamed(supplyKey), + cryptoCreate(accountToDelete), + cryptoDelete(accountToDelete), + tokenCreate(token) + .treasury(DEFAULT_PAYER) + .tokenType(FUNGIBLE_COMMON) + .initialSupply(1000L) + .supplyKey(supplyKey) + .hasKnownStatus(SUCCESS), + tokenAssociate(accountToDelete, token).hasKnownStatus(ACCOUNT_DELETED)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java index 08e6edd9fd18..d304c11f5383 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenTransactSpecs.java @@ -20,6 +20,7 @@ import static com.hedera.services.bdd.junit.TestTags.ADHOC; import static com.hedera.services.bdd.junit.TestTags.TOKEN; import static com.hedera.services.bdd.spec.HapiSpec.defaultHapiSpec; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.accountWith; import static com.hedera.services.bdd.spec.assertions.AccountInfoAsserts.changeFromSnapshot; import static com.hedera.services.bdd.spec.assertions.AutoAssocAsserts.accountTokenPairs; @@ -44,6 +45,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDissociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFeeInheritingRoyaltyCollector; @@ -2038,6 +2040,25 @@ final Stream collectorIsChargedRoyaltyFallbackFeeUnlessExempt() { .logged()); } + @HapiTest + final Stream tokenFrozenOnTreasuryCannotBeFrozenAgain() { + final var alice = "alice"; + final var token = "token"; + final var freezeKey = "freezeKey"; + return hapiTest( + newKeyNamed(freezeKey), + cryptoCreate(alice), + tokenCreate(token) + .treasury(DEFAULT_PAYER) + .tokenType(FUNGIBLE_COMMON) + .initialSupply(1000L) + .freezeKey(freezeKey) + .hasKnownStatus(SUCCESS), + tokenAssociate(alice, token), + tokenFreeze(token, alice).hasKnownStatus(SUCCESS), + tokenFreeze(token, alice).hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); + } + private static final String TXN_TRIGGERING_COLLECTOR_EXEMPT_FEE = "collectorExempt"; private static final String TXN_TRIGGERING_COLLECTOR_NON_EXEMPT_FEE = "collectorNonExempt"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java index 45335e53072d..0d00678b0d0c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateSpecs.java @@ -34,6 +34,7 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFeeScheduleUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.wipeTokenAccount; @@ -47,6 +48,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doSeveralWithStartupConfigNow; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doWithStartupConfigNow; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.specOps; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.HapiSuite.ADDRESS_BOOK_CONTROL; @@ -59,6 +61,7 @@ import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hedera.services.bdd.suites.HapiSuite.salted; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_DELETED; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_FROZEN_FOR_TOKEN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_AUTORENEW_ACCOUNT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_CUSTOM_FEE_SCHEDULE_KEY; @@ -68,6 +71,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ZERO_BYTE_IN_STRING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.NO_REMAINING_AUTOMATIC_ASSOCIATIONS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FEE_SCHEDULE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_FREEZE_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_KYC_KEY; @@ -85,6 +89,7 @@ import com.google.protobuf.ByteString; import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel; import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hederahashgraph.api.proto.java.TokenFreezeStatus; @@ -1014,4 +1019,29 @@ final Stream updateUniqueTreasuryWithNfts() { getAccountInfo("newTreasury").logged(), getTokenInfo("tbu").hasTreasury("newTreasury")); } + + @LeakyHapiTest(overrides = {"tokens.nfts.useTreasuryWildcards"}) + final Stream tokenFrozenOnTreasuryCannotBeUpdated() { + final var accountToFreeze = "account"; + final var adminKey = "adminKey"; + final var tokenToFreeze = "token"; + return hapiTest( + overriding("tokens.nfts.useTreasuryWildcards", "false"), + newKeyNamed(adminKey), + cryptoCreate(accountToFreeze), + tokenCreate(tokenToFreeze) + .treasury(accountToFreeze) + .tokenType(NON_FUNGIBLE_UNIQUE) + .initialSupply(0) + .supplyKey(adminKey) + .freezeKey(adminKey) + .adminKey(adminKey) + .hasKnownStatus(SUCCESS), + tokenFreeze(tokenToFreeze, accountToFreeze), + tokenAssociate(DEFAULT_PAYER, tokenToFreeze), + tokenUpdate(tokenToFreeze) + .treasury(DEFAULT_PAYER) + .signedBy(DEFAULT_PAYER, adminKey) + .hasKnownStatus(ACCOUNT_FROZEN_FOR_TOKEN)); + } } From c518976296431232245510c83e00b20df4856989 Mon Sep 17 00:00:00 2001 From: anthony-swirldslabs <152534762+anthony-swirldslabs@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:15:57 -0700 Subject: [PATCH 10/16] chore: Remove PeerInfo.nodeName (#15441) Signed-off-by: Anthony Petrov --- .../java/com/swirlds/platform/Utilities.java | 1 - .../swirlds/platform/network/PeerInfo.java | 17 +++++--- .../swirlds/platform/roster/RosterUtils.java | 39 +++++++++++++++++++ .../network/NetworkPeerIdentifierTest.java | 13 +++++-- 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java index f3e7529722d2..3926f4e6ddc3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/Utilities.java @@ -379,7 +379,6 @@ public static boolean hasAnyCauseSuppliedType( .filter(address -> !address.getNodeId().equals(selfId)) .map(address -> new PeerInfo( address.getNodeId(), - address.getSelfName(), Objects.requireNonNull(address.getHostnameExternal()), Objects.requireNonNull(address.getSigCert()))) .toList(); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/PeerInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/PeerInfo.java index 39873c2f5052..c3d69d66737f 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/PeerInfo.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/network/PeerInfo.java @@ -17,6 +17,7 @@ package com.swirlds.platform.network; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.roster.RosterUtils; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.cert.Certificate; @@ -24,12 +25,16 @@ * A record representing a peer's network information. * * @param nodeId the ID of the peer - * @param nodeName the name of the peer * @param hostname the hostname (or IP address) of the peer * @param signingCertificate the certificate used to validate the peer's TLS certificate */ -public record PeerInfo( - @NonNull NodeId nodeId, - @NonNull String nodeName, - @NonNull String hostname, - @NonNull Certificate signingCertificate) {} +public record PeerInfo(@NonNull NodeId nodeId, @NonNull String hostname, @NonNull Certificate signingCertificate) { + /** + * Return a "node name" for the peer, e.g. "node1" for a peer with NodeId == 0. + * @return a "node name" + */ + @NonNull + public String nodeName() { + return RosterUtils.formatNodeName(nodeId.id()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java new file mode 100644 index 000000000000..837b9526151b --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.swirlds.platform.roster; + +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A utility class to help use Rooster and RosterEntry instances. + */ +public final class RosterUtils { + private RosterUtils() {} + + /** + * Formats a "node name" for a given node id, e.g. "node1" for nodeId == 0. + * This name can be used for logging purposes, or to support code that + * uses strings to identify nodes. + * + * @param nodeId a node id + * @return a "node name" + */ + @NonNull + public static String formatNodeName(final long nodeId) { + return "node" + (nodeId + 1); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/NetworkPeerIdentifierTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/NetworkPeerIdentifierTest.java index f3011e2f39fe..575ed2a0888c 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/NetworkPeerIdentifierTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/network/NetworkPeerIdentifierTest.java @@ -47,13 +47,16 @@ import java.util.HashSet; import java.util.List; import java.util.Objects; -import java.util.Random; import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; class NetworkPeerIdentifierTest { + private static final Pattern NODE_NAME_PATTERN = Pattern.compile("^node(\\d+)$"); + final PlatformContext platformContext = mock(PlatformContext.class); List peerInfoList = null; PublicStores publicStores = null; @@ -74,15 +77,17 @@ void setUp() throws URISyntaxException, KeyLoadingException, KeyStoreException { publicStores = PublicStores.fromAllPublic(publicKeys, names.stream().toList()); peerInfoList = new ArrayList<>(); - final Random random = new Random(); names.forEach(name -> { - final int id = random.nextInt(names.size()); + final Matcher nameMatcher = NODE_NAME_PATTERN.matcher(name); + if (!nameMatcher.matches()) { + throw new RuntimeException("Invalid node name " + name); + } + final int id = Integer.parseInt(nameMatcher.group(1)) - 1; final NodeId node = new NodeId(id); final PeerInfo peer; try { peer = new PeerInfo( node, - name, "127.0.0.1", Objects.requireNonNull(publicStores.getCertificate(KeyCertPurpose.SIGNING, name))); } catch (final KeyLoadingException e) { From 7a4616bf840053c73a0713600bd50a09d6238b1b Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:18:29 -0700 Subject: [PATCH 11/16] refactor: remove AbortException (#15004) Signed-off-by: lukelee-sl --- .../node/config/data/ContractsConfig.java | 4 +- .../exec/ContextTransactionProcessor.java | 74 +++++++------- .../impl/exec/TransactionProcessor.java | 28 +----- .../impl/exec/failure/AbortException.java | 99 ------------------- .../exec/v046/Version046FeatureFlags.java | 2 +- .../impl/hevm/HederaEvmTransaction.java | 19 ++++ .../exec/ContextTransactionProcessorTest.java | 70 ++++++++++++- .../test/exec/TransactionProcessorTest.java | 22 +++-- 8 files changed, 146 insertions(+), 172 deletions(-) delete mode 100644 hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 62c9dedae00a..032f68ffac72 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -87,8 +87,8 @@ public record ContractsConfig( boolean evmVersionDynamic, @ConfigProperty(value = "evm.allowCallsToNonContractAccounts", defaultValue = "true") @NetworkProperty boolean evmAllowCallsToNonContractAccounts, - @ConfigProperty(value = "evm.chargeGasOnPreEvmException", defaultValue = "true") @NetworkProperty - boolean chargeGasOnPreEvmException, + @ConfigProperty(value = "evm.chargeGasOnEvmHandleException", defaultValue = "true") @NetworkProperty + boolean chargeGasOnEvmHandleException, @ConfigProperty(value = "evm.nonExtantContractsFail", defaultValue = "0") @NetworkProperty Set evmNonExtantContractsFail, @ConfigProperty(value = "evm.version", defaultValue = "v0.50") @NetworkProperty String evmVersion) {} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java index 49a56f2cbed6..b43d83f4de49 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/ContextTransactionProcessor.java @@ -19,11 +19,11 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ContractID; import com.hedera.hapi.streams.ContractBytecode; import com.hedera.node.app.hapi.utils.ethereum.EthTxData; import com.hedera.node.app.service.contract.impl.annotations.TransactionScope; -import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.tracers.AddOnEvmActionTracer; import com.hedera.node.app.service.contract.impl.exec.tracers.EvmActionTracer; @@ -33,6 +33,7 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.hevm.HydratedEthTxData; import com.hedera.node.app.service.contract.impl.infra.HevmTransactionFactory; +import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.state.RootProxyWorldUpdater; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.HandleException; @@ -109,8 +110,12 @@ public CallOutcome call() { // if an exception occurs during a ContractCall, charge fees to the sender and return a CallOutcome reflecting // the error. final var hevmTransaction = safeCreateHevmTransaction(); - if (hevmTransaction.isException() && contractsConfig.chargeGasOnPreEvmException()) { - return chargeFeesAndReturnOutcome(hevmTransaction); + if (hevmTransaction.isException()) { + return maybeChargeFeesAndReturnOutcome( + hevmTransaction, + context.body().transactionIDOrThrow().accountIDOrThrow(), + null, + contractsConfig.chargeGasOnEvmHandleException()); } // Process the transaction and return its outcome @@ -135,30 +140,15 @@ public CallOutcome call() { } return CallOutcome.fromResultsWithMaybeSidecars( result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result); - } catch (AbortException e) { - if (e.isChargeable() && contractsConfig.chargeGasOnPreEvmException()) { - gasCharging.chargeGasForAbortedTransaction( - requireNonNull(e.senderId()), hederaEvmContext, rootProxyWorldUpdater, hevmTransaction); - } - // Commit any HAPI fees that were charged before aborting - rootProxyWorldUpdater.commit(); - - ContractID recipientId = null; - if (!INVALID_CONTRACT_ID.equals(e.getStatus())) { - recipientId = hevmTransaction.contractId(); - } - - var result = HederaEvmTransactionResult.fromAborted(e.senderId(), recipientId, e.getStatus()); - - if (context.body().hasEthereumTransaction()) { - final var sender = rootProxyWorldUpdater.getHederaAccount(e.senderId()); - if (sender != null) { - result = result.withSignerNonce(sender.getNonce()); - } - } - - return CallOutcome.fromResultsWithoutSidecars( - result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result); + } catch (HandleException e) { + final var sender = rootProxyWorldUpdater.getHederaAccount(hevmTransaction.senderId()); + final var senderId = sender != null ? sender.hederaId() : hevmTransaction.senderId(); + + return maybeChargeFeesAndReturnOutcome( + hevmTransaction.withException(e), + senderId, + sender, + hevmTransaction.isContractCall() && contractsConfig.chargeGasOnEvmHandleException()); } } @@ -171,16 +161,28 @@ private HederaEvmTransaction safeCreateHevmTransaction() { } } - private CallOutcome chargeFeesAndReturnOutcome(@NonNull final HederaEvmTransaction hevmTransaction) { - // If there was an exception while creating the HederaEvmTransaction and the transaction is a ContractCall - // charge fees to the sender and return a CallOutcome reflecting the error. - final var senderId = context.body().transactionIDOrThrow().accountIDOrThrow(); - gasCharging.chargeGasForAbortedTransaction(senderId, hederaEvmContext, rootProxyWorldUpdater, hevmTransaction); + private CallOutcome maybeChargeFeesAndReturnOutcome( + @NonNull final HederaEvmTransaction hevmTransaction, + @NonNull final AccountID senderId, + @Nullable final HederaEvmAccount sender, + final boolean chargeGas) { + final var status = requireNonNull(hevmTransaction.exception()).getStatus(); + if (chargeGas) { + gasCharging.chargeGasForAbortedTransaction( + senderId, hederaEvmContext, rootProxyWorldUpdater, hevmTransaction); + } rootProxyWorldUpdater.commit(); - final var result = HederaEvmTransactionResult.fromAborted( - senderId, - hevmTransaction.contractId(), - hevmTransaction.exception().getStatus()); + ContractID recipientId = null; + if (!INVALID_CONTRACT_ID.equals(status)) { + recipientId = hevmTransaction.contractId(); + } + + var result = HederaEvmTransactionResult.fromAborted(senderId, recipientId, status); + + if (context.body().hasEthereumTransaction() && sender != null) { + result = result.withSignerNonce(sender.getNonce()); + } + return CallOutcome.fromResultsWithoutSidecars( result.asProtoResultOf(ethTxDataIfApplicable(), rootProxyWorldUpdater), result); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java index 51e9a508d60a..aa2b3cac65e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionProcessor.java @@ -19,7 +19,6 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_CONTRACT_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.WRONG_NONCE; -import static com.hedera.node.app.service.contract.impl.exec.failure.AbortException.validateTrueOrAbort; import static com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult.resourceExhaustionFrom; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.contractIDToBesuAddress; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.isEvmAddress; @@ -31,7 +30,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; -import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.processors.CustomMessageCallProcessor; import com.hedera.node.app.service.contract.impl.exec.utils.FrameBuilder; @@ -40,7 +38,6 @@ import com.hedera.node.app.service.contract.impl.hevm.HederaEvmTransactionResult; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; -import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; @@ -113,7 +110,6 @@ public FeatureFlags featureFlags() { * @param tracer the tracer to use * @param config the node configuration * @return the result of running the transaction to completion - * @throws AbortException if processing failed before initiating the EVM transaction */ public HederaEvmTransactionResult processTransaction( @NonNull final HederaEvmTransaction transaction, @@ -123,14 +119,7 @@ public HederaEvmTransactionResult processTransaction( @NonNull final ActionSidecarContentTracer tracer, @NonNull final Configuration config) { final var parties = computeInvolvedPartiesOrAbort(transaction, updater, config); - try { - return processTransactionWithParties( - transaction, updater, feesOnlyUpdater, context, tracer, config, parties); - } catch (HandleException e) { - final var sender = updater.getHederaAccount(transaction.senderId()); - final var senderId = sender != null ? sender.hederaId() : transaction.senderId(); - throw new AbortException(e.getStatus(), senderId); - } + return processTransactionWithParties(transaction, updater, feesOnlyUpdater, context, tracer, config, parties); } private HederaEvmTransactionResult processTransactionWithParties( @@ -175,13 +164,7 @@ private InvolvedParties computeInvolvedPartiesOrAbort( @NonNull final HederaEvmTransaction transaction, @NonNull final HederaWorldUpdater updater, @NonNull final Configuration config) { - try { - return computeInvolvedParties(transaction, updater, config); - } catch (AbortException e) { - throw e; - } catch (HandleException e) { - throw new AbortException(e.getStatus(), transaction.senderId(), null, true); - } + return computeInvolvedParties(transaction, updater, config); } private HederaEvmTransactionResult safeCommit( @@ -243,12 +226,11 @@ private InvolvedParties computeInvolvedParties( @NonNull final HederaWorldUpdater updater, @NonNull final Configuration config) { final var sender = updater.getHederaAccount(transaction.senderId()); - validateTrueOrAbort(sender != null, INVALID_ACCOUNT_ID, transaction.senderId()); - final var senderId = sender.hederaId(); + validateTrue(sender != null, INVALID_ACCOUNT_ID); HederaEvmAccount relayer = null; if (transaction.isEthereumTransaction()) { relayer = updater.getHederaAccount(requireNonNull(transaction.relayerId())); - validateTrueOrAbort(relayer != null, INVALID_ACCOUNT_ID, senderId); + validateTrue(relayer != null, INVALID_ACCOUNT_ID); } final InvolvedParties parties; if (transaction.isCreate()) { @@ -270,7 +252,7 @@ private InvolvedParties computeInvolvedParties( } } if (transaction.isEthereumTransaction()) { - validateTrueOrAbort(transaction.nonce() == parties.sender().getNonce(), WRONG_NONCE, senderId); + validateTrue(transaction.nonce() == parties.sender().getNonce(), WRONG_NONCE); } return parties; } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java deleted file mode 100644 index 7186be142f3d..000000000000 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/failure/AbortException.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC - * - * 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 com.hedera.node.app.service.contract.impl.exec.failure; - -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.base.AccountID; -import com.hedera.hapi.node.base.ResponseCodeEnum; -import com.hedera.node.app.spi.workflows.HandleException; -import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; - -/** - * An exception thrown when a transaction is aborted before entering the EVM. - * - *

      Includes the effective Hedera id of the sender. - */ -public class AbortException extends HandleException { - private final AccountID senderId; - - @Nullable - private final AccountID relayerId; - // Whether gas can still be charged for the transaction - private final boolean isChargeable; - - public AbortException(@NonNull final ResponseCodeEnum status, @NonNull final AccountID senderId) { - super(status); - this.senderId = requireNonNull(senderId); - this.relayerId = null; - this.isChargeable = false; - } - - public AbortException( - @NonNull final ResponseCodeEnum status, - @NonNull final AccountID senderId, - @Nullable final AccountID relayerId, - final boolean isChargeable) { - super(status); - this.senderId = requireNonNull(senderId); - this.relayerId = relayerId; - this.isChargeable = isChargeable; - } - - /** - * Returns the effective Hedera id of the sender. - * - * @return the effective Hedera id of the sender - */ - public AccountID senderId() { - return senderId; - } - - /** - * Returns whether the transaction can still be charged for gas. - * - * @return whether the transaction can still be charged for gas. - */ - public boolean isChargeable() { - return isChargeable; - } - - /** - * Returns the relayer id. - * - * @return the relayer id. - */ - @Nullable - public AccountID relayerId() { - return relayerId; - } - - /** - * Throws an {@code AbortException} if the given flag is {@code false}. - * - * @param flag the flag to check - * @param status the status to use if the flag is {@code false} - * @param senderId the effective Hedera id of the sender - */ - public static void validateTrueOrAbort( - final boolean flag, @NonNull final ResponseCodeEnum status, @NonNull final AccountID senderId) { - if (!flag) { - throw new AbortException(status, senderId); - } - } -} diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java index c961bf7e5cfc..20af04cf60ce 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/v046/Version046FeatureFlags.java @@ -41,6 +41,6 @@ public boolean isAllowCallsToNonContractAccountsEnabled( @Override public boolean isChargeGasOnPreEvmException(@NonNull Configuration config) { - return config.getConfigData(ContractsConfig.class).chargeGasOnPreEvmException(); + return config.getConfigData(ContractsConfig.class).chargeGasOnEvmHandleException(); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java index 596f57240845..56964caa7407 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/hevm/HederaEvmTransaction.java @@ -127,4 +127,23 @@ public long offeredGasCost() { public boolean requiresFullRelayerAllowance() { return offeredGasPrice == 0L; } + + /** + * @return a copy of this transaction with the given {@code exception} + */ + public HederaEvmTransaction withException(@NonNull final HandleException exception) { + return new HederaEvmTransaction( + this.senderId, + this.relayerId, + this.contractId, + this.nonce, + this.payload, + this.chainId, + this.value, + this.gasLimit, + this.offeredGasPrice, + this.maxGasAllowance, + this.hapiCreation, + exception); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java index cb4f3f553772..0f5ec2a45555 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/ContextTransactionProcessorTest.java @@ -31,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import com.hedera.hapi.node.base.TransactionID; @@ -38,7 +39,6 @@ import com.hedera.node.app.service.contract.impl.exec.CallOutcome; import com.hedera.node.app.service.contract.impl.exec.ContextTransactionProcessor; import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; -import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.tracers.EvmActionTracer; import com.hedera.node.app.service.contract.impl.hevm.HederaEvmContext; @@ -61,6 +61,9 @@ @ExtendWith(MockitoExtension.class) class ContextTransactionProcessorTest { private static final Configuration CONFIGURATION = HederaTestConfigBuilder.createConfig(); + private static final Configuration CONFIG_NO_CHARGE_ON_EXCEPTION = HederaTestConfigBuilder.create() + .withValue("contracts.evm.chargeGasOnEvmHandleException", false) + .getOrCreateConfig(); @Mock private HandleContext context; @@ -224,10 +227,11 @@ void stillChargesHapiFeesOnAbort() { .willReturn(HEVM_CREATION); given(processor.processTransaction( HEVM_CREATION, rootProxyWorldUpdater, feesOnlyUpdater, hederaEvmContext, tracer, CONFIGURATION)) - .willThrow(new AbortException(INVALID_CONTRACT_ID, SENDER_ID)); + .willThrow(new HandleException(INVALID_CONTRACT_ID)); subject.call(); + verify(customGasCharging).chargeGasForAbortedTransaction(any(), any(), any(), any()); verify(rootProxyWorldUpdater).commit(); } @@ -255,6 +259,36 @@ void stillChargesHapiFeesOnHevmException() { final var outcome = subject.call(); + verify(customGasCharging).chargeGasForAbortedTransaction(any(), any(), any(), any()); + verify(rootProxyWorldUpdater).commit(); + assertEquals(INVALID_CONTRACT_ID, outcome.status()); + } + + @Test + void doesNotChargeHapiFeesOnHevmExceptionIfSoConfiggured() { + final var contractsConfig = CONFIG_NO_CHARGE_ON_EXCEPTION.getConfigData(ContractsConfig.class); + final var subject = new ContextTransactionProcessor( + null, + context, + contractsConfig, + CONFIG_NO_CHARGE_ON_EXCEPTION, + hederaEvmContext, + null, + tracer, + rootProxyWorldUpdater, + hevmTransactionFactory, + feesOnlyUpdater, + processor, + customGasCharging); + + given(context.body()).willReturn(transactionBody); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody)).willReturn(HEVM_Exception); + given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); + given(transactionID.accountIDOrThrow()).willReturn(SENDER_ID); + + final var outcome = subject.call(); + + verify(customGasCharging, never()).chargeGasForAbortedTransaction(any(), any(), any(), any()); verify(rootProxyWorldUpdater).commit(); assertEquals(INVALID_CONTRACT_ID, outcome.status()); } @@ -285,6 +319,38 @@ void stillChargesHapiFeesOnExceptionThrown() { final var outcome = subject.call(); + verify(customGasCharging).chargeGasForAbortedTransaction(any(), any(), any(), any()); + verify(rootProxyWorldUpdater).commit(); + assertEquals(INVALID_CONTRACT_ID, outcome.status()); + } + + @Test + void doesNotChargeHapiFeesOnExceptionThrownIfSoConfigured() { + final var contractsConfig = CONFIG_NO_CHARGE_ON_EXCEPTION.getConfigData(ContractsConfig.class); + final var subject = new ContextTransactionProcessor( + null, + context, + contractsConfig, + CONFIG_NO_CHARGE_ON_EXCEPTION, + hederaEvmContext, + null, + tracer, + rootProxyWorldUpdater, + hevmTransactionFactory, + feesOnlyUpdater, + processor, + customGasCharging); + + given(context.body()).willReturn(transactionBody); + given(hevmTransactionFactory.fromHapiTransaction(transactionBody)) + .willThrow(new HandleException(INVALID_CONTRACT_ID)); + given(hevmTransactionFactory.fromContractTxException(any(), any())).willReturn(HEVM_Exception); + given(transactionBody.transactionIDOrThrow()).willReturn(transactionID); + given(transactionID.accountIDOrThrow()).willReturn(SENDER_ID); + + final var outcome = subject.call(); + + verify(customGasCharging, never()).chargeGasForAbortedTransaction(any(), any(), any(), any()); verify(rootProxyWorldUpdater).commit(); assertEquals(INVALID_CONTRACT_ID, outcome.status()); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java index 657d8c2e079b..ae7110c37a1a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionProcessorTest.java @@ -65,7 +65,6 @@ import com.hedera.node.app.service.contract.impl.exec.FeatureFlags; import com.hedera.node.app.service.contract.impl.exec.FrameRunner; import com.hedera.node.app.service.contract.impl.exec.TransactionProcessor; -import com.hedera.node.app.service.contract.impl.exec.failure.AbortException; import com.hedera.node.app.service.contract.impl.exec.gas.CustomGasCharging; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; @@ -78,6 +77,7 @@ import com.hedera.node.app.service.contract.impl.records.ContractOperationStreamBuilder; import com.hedera.node.app.service.contract.impl.state.HederaEvmAccount; import com.hedera.node.app.service.contract.impl.utils.ConversionUtils; +import com.hedera.node.app.spi.workflows.HandleException; import com.hedera.node.app.spi.workflows.ResourceExhaustedException; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; @@ -163,7 +163,7 @@ void abortsOnMissingSender() { @Test void lazyCreationAttemptWithNoValueFailsFast() { - givenSenderAccount(); + givenSenderAccountWithNoHederaAccount(); givenRelayerAccount(); given(messageCallProcessor.isImplicitCreationEnabled(config)).willReturn(true); assertAbortsWith(wellKnownRelayedHapiCall(0), INVALID_CONTRACT_ID); @@ -171,7 +171,7 @@ void lazyCreationAttemptWithNoValueFailsFast() { @Test void lazyCreationAttemptWithInvalidAddress() { - givenSenderAccount(); + givenSenderAccountWithNoHederaAccount(); givenRelayerAccount(); final var invalidCreation = new HederaEvmTransaction( SENDER_ID, @@ -363,22 +363,22 @@ void callWhenComputePartiesThrowsException() { final var context = wellKnownContextWith(blocks, tinybarValues, systemContractGasCalculator); given(worldUpdater.getHederaAccount(SENDER_ID)).willReturn(null); - final var abortException = catchThrowableOfType( + final var handleException = catchThrowableOfType( () -> subject.processTransaction( transaction, worldUpdater, () -> feesOnlyUpdater, context, tracer, config), - AbortException.class); - assertThat(abortException.isChargeable()).isFalse(); + HandleException.class); + assertThat(handleException.getStatus()).isEqualTo(INVALID_ACCOUNT_ID); } @Test void requiresEthTxToHaveNonNullRelayer() { - givenSenderAccount(); + givenSenderAccountWithNoHederaAccount(); assertAbortsWith(wellKnownRelayedHapiCall(0), INVALID_ACCOUNT_ID); } @Test void nonLazyCreateCandidateMustHaveReceiver() { - givenSenderAccount(); + givenSenderAccountWithNoHederaAccount(); givenRelayerAccount(); assertAbortsWith(wellKnownRelayedHapiCall(0), INVALID_CONTRACT_ID); } @@ -756,8 +756,12 @@ private void givenFeeOnlyParties() { given(feesOnlyUpdater.getHederaAccount(CALLED_CONTRACT_ID)).willReturn(receiverAccount); } - private void givenSenderAccount() { + private void givenSenderAccountWithNoHederaAccount() { given(worldUpdater.getHederaAccount(SENDER_ID)).willReturn(senderAccount); + } + + private void givenSenderAccount() { + givenSenderAccountWithNoHederaAccount(); given(senderAccount.hederaId()).willReturn(SENDER_ID); } From 5c06347436cec7c9285166927e011e7a8127932b Mon Sep 17 00:00:00 2001 From: Mustafa Uzun Date: Fri, 13 Sep 2024 15:03:01 +0300 Subject: [PATCH 12/16] feat: metadata view functions via smart contracts (#15019) Signed-off-by: Mustafa Uzun Signed-off-by: Stanimir Stoyanov Signed-off-by: Valentin Valkanov Co-authored-by: Stanimir Stoyanov Co-authored-by: Valentin Valkanov --- .../configuration/dev/application.properties | 1 + .../utils/contracts/ParsingConstants.java | 5 + .../node/config/data/ContractsConfig.java | 2 + .../common/AbstractCallAttempt.java | 10 + .../exec/systemcontracts/hts/ReturnTypes.java | 172 +- .../systemcontracts/hts/TokenTupleUtils.java | 66 +- .../FungibleTokenInfoCall.java | 24 +- .../FungibleTokenInfoTranslator.java | 15 +- .../hts/nfttokeninfo/NftTokenInfoCall.java | 29 +- .../nfttokeninfo/NftTokenInfoTranslator.java | 16 +- .../hts/tokeninfo/TokenInfoCall.java | 22 +- .../hts/tokeninfo/TokenInfoTranslator.java | 14 +- .../contract/impl/test/TestHelpers.java | 42 + .../FungibleTokenInfoCallTest.java | 78 +- .../FungibleTokenInfoTranslatorTest.java | 35 + .../nfttokeninfo/NftTokenInfoCallTest.java | 92 +- .../NftTokenInfoTranslatorTest.java | 35 + .../hts/tokeninfo/TokenInfoCallTest.java | 73 +- .../tokeninfo/TokenInfoTranslatorTest.java | 35 + .../precompile/TokenInfoHTSSuite.java | 236 ++- .../precompile/HTSPrecompileResult.java | 129 +- .../contracts/TokenInfo/TokenInfo.bin | 1 + .../contracts/TokenInfo/TokenInfo.json | 1791 +++++++++++++++++ .../contracts/TokenInfo/TokenInfo.sol | 71 + 24 files changed, 2796 insertions(+), 198 deletions(-) create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.bin create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.json create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.sol diff --git a/hedera-node/configuration/dev/application.properties b/hedera-node/configuration/dev/application.properties index f3cf6bb08774..56c01f9a71b1 100644 --- a/hedera-node/configuration/dev/application.properties +++ b/hedera-node/configuration/dev/application.properties @@ -2,6 +2,7 @@ balances.exportDir.path=data/accountBalances/ upgrade.artifacts.path=data/upgrade contracts.chainId=298 contracts.maxGasPerSec=15000000000 +contracts.systemContract.tokenInfo.v2.enabled=true # Needed for end-end tests running on mod-service code staking.periodMins=1 staking.fees.nodeRewardPercentage=10 diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/contracts/ParsingConstants.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/contracts/ParsingConstants.java index d79e6a599440..fb0d100a1418 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/contracts/ParsingConstants.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/contracts/ParsingConstants.java @@ -60,6 +60,8 @@ private ParsingConstants() { "(" + "string,string,address,string,bool,int64,bool," + TOKEN_KEY + ARRAY_BRACKETS + "," + EXPIRY + ")"; public static final String HEDERA_TOKEN_V3 = "(" + "string,string,address,string,bool,int64,bool," + TOKEN_KEY + ARRAY_BRACKETS + "," + EXPIRY_V2 + ")"; + public static final String HEDERA_TOKEN_V4 = "(" + "string,string,address,string,bool,uint32,bool," + TOKEN_KEY + + ARRAY_BRACKETS + "," + EXPIRY + ",bytes" + ")"; public static final String TOKEN_INFO = "(" + HEDERA_TOKEN_V2 + ",int64,bool,bool,bool," @@ -135,8 +137,11 @@ public enum FunctionType { HAPI_TRANSFER_FROM_NFT, HAPI_GET_APPROVED, HAPI_GET_FUNGIBLE_TOKEN_INFO, + HAPI_GET_FUNGIBLE_TOKEN_INFO_V2, HAPI_GET_TOKEN_INFO, + HAPI_GET_TOKEN_INFO_V2, HAPI_GET_NON_FUNGIBLE_TOKEN_INFO, + HAPI_GET_NON_FUNGIBLE_TOKEN_INFO_V2, HAPI_IS_APPROVED_FOR_ALL, HAPI_IS_KYC, GET_TOKEN_DEFAULT_FREEZE_STATUS, diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 032f68ffac72..63f38e8c1ded 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -83,6 +83,8 @@ public record ContractsConfig( boolean systemContractAccountServiceIsAuthorizedRawEnabled, @ConfigProperty(value = "systemContract.updateCustomFees.enabled", defaultValue = "true") @NetworkProperty boolean systemContractUpdateCustomFeesEnabled, + @ConfigProperty(value = "systemContract.tokenInfo.v2.enabled", defaultValue = "false") @NetworkProperty + boolean systemContractTokenInfoV2Enabled, @ConfigProperty(value = "evm.version.dynamic", defaultValue = "false") @NetworkProperty boolean evmVersionDynamic, @ConfigProperty(value = "evm.allowCallsToNonContractAccounts", defaultValue = "true") @NetworkProperty diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java index 4a49451d47b9..d22842249ce0 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/common/AbstractCallAttempt.java @@ -259,6 +259,16 @@ public boolean isSelector(@NonNull final Function... functions) { return false; } + /** + * Returns whether this call attempt is a selector for any of the given functions. + * @param function selectors to match against + * @param configEnabled whether the config is enabled + * @return boolean result + */ + public boolean isSelectorIfConfigEnabled(@NonNull final Function function, final boolean configEnabled) { + return configEnabled && isSelector(function); + } + private boolean isRedirectSelector(@NonNull final byte[] functionSelector, @NonNull final byte[] input) { return Arrays.equals(input, 0, functionSelector.length, functionSelector, 0, functionSelector.length); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java index 1942ce429eb4..890f5195260c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/ReturnTypes.java @@ -52,6 +52,35 @@ private ReturnTypes() { public static final Fraction ZERO_FRACTION = new Fraction(0, 1); public static final FixedFee ZERO_FIXED_FEE = new FixedFee(0, null); + // Token info fields + protected static final String TOKEN_FIELDS = + // name, symbol, treasury, memo, tokenSupplyType, maxSupply, freezeDefault + "string,string,address,string,bool,int64,bool,"; + protected static final String TOKEN_KEYS = "(" + // keyType + + "uint256," + // KeyValue + // inheritedAccountKey, contractId, ed25519, ECDSA_secp256k1, delegatableContractId + + "(bool,address,bytes,bytes,address)" + + ")[],"; + protected static final String EXPIRY_FIELDS = + // second, autoRenewAccount, autoRenewPeriod + "(uint32,address,uint32)"; + protected static final String CUSTOM_FEES = + // FixedFee array + // amount, tokenId, useHbarsForPayment, useCurrentTokenForPayment, feeCollector + "(uint32,address,bool,bool,address)[]," + // FractionalFee array + // numerator, denominator, minimumAmount, maximumAmount, netOfTransfers, feeCollector + + "(uint32,uint32,uint32,uint32,bool,address)[]," + // RoyaltyFee array + // numerator, denominator, amount, tokenId, useHbarsForPayment, feeCollector + + "(uint32,uint32,uint32,address,bool,address)[]"; + protected static final String STATUS_FIELDS = + // totalSupply, deleted, defaultKycStatus, pauseStatus + ",int64,bool,bool,bool,"; + + // Response code types public static final String INT = "(int)"; public static final String INT_64 = "(int64)"; public static final String INT64_INT64 = "(int64,int64)"; @@ -76,34 +105,40 @@ private ReturnTypes() { + "(bool,address,bytes,bytes,address)" // inheritedAccountKey, contractId, ed25519, ECDSA_secp256k1, // delegatableContractId + ")"; + // spotless:off public static final String RESPONSE_CODE_TOKEN_INFO = "(int32," // TokenInfo + "(" // HederaToken + "(" - + "string,string,address,string,bool,int64,bool," // name, symbol, treasury, memo, tokenSupplyType, maxSupply, freezeDefault + + TOKEN_FIELDS // TokenKey array - + "(" - + "uint256," // keyType - // KeyValue - + "(bool,address,bytes,bytes,address)" // inheritedAccountKey, contractId, ed25519, ECDSA_secp256k1, delegatableContractId - + ")[]," + + TOKEN_KEYS // Expiry - + "(uint32,address,uint32)" // second, autoRenewAccount, autoRenewPeriod + + EXPIRY_FIELDS + ")" + + STATUS_FIELDS + + CUSTOM_FEES + + ",string" // ledgerId + ")" + + ")"; - + ",int64,bool,bool,bool," // totalSupply, deleted, defaultKycStatus, pauseStatus + public static final String RESPONSE_CODE_TOKEN_INFO_V2 = "(int32," + // TokenInfoV2 + + "(" + // HederaTokenV2 + + "(" + + TOKEN_FIELDS + + TOKEN_KEYS + + EXPIRY_FIELDS + + ",bytes" // metadata + + ")" + + STATUS_FIELDS // totalSupply, deleted, defaultKycStatus, pauseStatus + + CUSTOM_FEES + + ",string" // ledgerId + + ")" + + ")"; - // FixedFee array - + "(uint32,address,bool,bool,address)[]," // amount, tokenId, useHbarsForPayment, useCurrentTokenForPayment, feeCollector - // FractionalFee array - + "(uint32,uint32,uint32,uint32,bool,address)[]," // numerator, denominator, minimumAmount, maximumAmount, netOfTransfers, feeCollector - // RoyaltyFee array - + "(uint32,uint32,uint32,address,bool,address)[]" // numerator, denominator, amount, tokenId, useHbarsForPayment, feeCollector - + ",string" // ledgerId - + ")" - + ")"; public static final String RESPONSE_CODE_FUNGIBLE_TOKEN_INFO = "(int32," // FungibleTokenInfo + "(" @@ -111,30 +146,37 @@ private ReturnTypes() { + "(" // HederaToken + "(" - + "string,string,address,string,bool,int64,bool," // name, symbol, treasury, memo, tokenSupplyType, maxSupply, freezeDefault - // TokenKey array - + "(" - + "uint256," // keyType - // KeyValue - + "(bool,address,bytes,bytes,address)" // inheritedAccountKey, contractId, ed25519, ECDSA_secp256k1, delegatableContractId - + ")[]," - // Expiry - + "(uint32,address,uint32)" // second, autoRenewAccount, autoRenewPeriod - + ")" + + TOKEN_FIELDS + + TOKEN_KEYS + + EXPIRY_FIELDS + ")" + + STATUS_FIELDS + + CUSTOM_FEES + + ",string" // ledgerId + + ")," + + "int32" // decimals + + ")" + + ")"; - + ",int64,bool,bool,bool," // totalSupply, deleted, defaultKycStatus, pauseStatus + public static final String RESPONSE_CODE_FUNGIBLE_TOKEN_INFO_V2 = "(int32," + // FungibleTokenInfoV2 + + "(" + // TokenInfoV2 + + "(" + // HederaTokenV2 + + "(" + + TOKEN_FIELDS + + TOKEN_KEYS + + EXPIRY_FIELDS + + ",bytes" // metadata + + ")" + + STATUS_FIELDS + + CUSTOM_FEES + + ",string" // ledgerId + + ")," + + "int32" // decimals + + ")" + + ")"; - // FixedFee array - + "(uint32,address,bool,bool,address)[]," // amount, tokenId, useHbarsForPayment, useCurrentTokenForPayment, feeCollector - // FractionalFee array - + "(uint32,uint32,uint32,uint32,bool,address)[]," // numerator, denominator, minimumAmount, maximumAmount, netOfTransfers, feeCollector - // RoyaltyFee array - + "(uint32,uint32,uint32,address,bool,address)[]" // numerator, denominator, amount, tokenId, useHbarsForPayment, feeCollector - + ",string" // ledgerId - + ")," - + "int32" // decimals - + ")" - + ")"; public static final String RESPONSE_CODE_NON_FUNGIBLE_TOKEN_INFO = "(int32," // NonFungibleTokenInfo + "(" @@ -142,30 +184,36 @@ private ReturnTypes() { + "(" // HederaToken + "(" - + "string,string,address,string,bool,int64,bool," // name, symbol, treasury, memo, tokenSupplyType, maxSupply, freezeDefault - // TokenKey array - + "(" - + "uint256," // keyType - // KeyValue - + "(bool,address,bytes,bytes,address)" // inheritedAccountKey, contractId, ed25519, ECDSA_secp256k1, delegatableContractId - + ")[]," - // Expiry - + "(uint32,address,uint32)" // second, autoRenewAccount, autoRenewPeriod - + ")" - - + ",int64,bool,bool,bool," // totalSupply, deleted, defaultKycStatus, pauseStatus + + TOKEN_FIELDS + + TOKEN_KEYS + + EXPIRY_FIELDS + ")" + + STATUS_FIELDS + + CUSTOM_FEES + + ",string" // ledgerId + + ")," + + "int64,address,int64,bytes,address" // serialNumber, ownerId, creationTime, metadata, spenderId + + ")" + + ")"; - // FixedFee array - + "(uint32,address,bool,bool,address)[]," // amount, tokenId, useHbarsForPayment, useCurrentTokenForPayment, feeCollector - // FractionalFee array - + "(uint32,uint32,uint32,uint32,bool,address)[]," // numerator, denominator, minimumAmount, maximumAmount, netOfTransfers, feeCollector - // RoyaltyFee array - + "(uint32,uint32,uint32,address,bool,address)[]" // numerator, denominator, amount, tokenId, useHbarsForPayment, feeCollector - + ",string" // ledgerId - + ")," - + "int64,address,int64,bytes,address" // serialNumber, ownerId, creationTime, metadata, spenderId - + ")" - + ")"; + public static final String RESPONSE_CODE_NON_FUNGIBLE_TOKEN_INFO_V2 = "(int32," + // NonFungibleTokenInfoV2 + + "(" + // TokenInfoV2 + + "(" + // HederaTokenV2 + + "(" + + TOKEN_FIELDS + + TOKEN_KEYS + + EXPIRY_FIELDS + + ",bytes" // metadata + + ")" + + STATUS_FIELDS // totalSupply, deleted, defaultKycStatus, pauseStatus + + CUSTOM_FEES + + ",string" // ledgerId + + ")," + + "int64,address,int64,bytes,address" // serialNumber, ownerId, creationTime, metadata, spenderId + + ")" + + ")"; public static final String RESPONSE_CODE_CUSTOM_FEES = "(int32," // FixedFee array diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java index 88434b9d9e0a..321da1173184 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/TokenTupleUtils.java @@ -181,13 +181,15 @@ private static List royaltyFeesTupleListFor(@NonNull final Token token) { * @return Tuple encoding of the TokenInfo */ @NonNull - public static Tuple tokenInfoTupleFor(@NonNull final Token token, @NonNull final String ledgerId) { + public static Tuple tokenInfoTupleFor(@NonNull final Token token, @NonNull final String ledgerId, int version) { final var fixedFees = fixedFeesTupleListFor(token); final var fractionalFees = fractionalFeesTupleListFor(token); final var royaltyFees = royaltyFeesTupleListFor(token); + final var hederaToken = version == 1 ? hederaTokenTupleFor(token) : hederaTokenTupleForV2(token); + return Tuple.of( - hederaTokenTupleFor(token), + hederaToken, token.totalSupply(), token.deleted(), token.accountsKycGrantedByDefault(), @@ -205,8 +207,9 @@ public static Tuple tokenInfoTupleFor(@NonNull final Token token, @NonNull final * @return Tuple encoding of the FungibleTokenInfo */ @NonNull - public static Tuple fungibleTokenInfoTupleFor(@NonNull final Token token, @NonNull final String ledgerId) { - return Tuple.of(tokenInfoTupleFor(token, ledgerId), token.decimals()); + public static Tuple fungibleTokenInfoTupleFor( + @NonNull final Token token, @NonNull final String ledgerId, int version) { + return Tuple.of(tokenInfoTupleFor(token, ledgerId, version), token.decimals()); } /** @@ -220,7 +223,8 @@ public static Tuple nftTokenInfoTupleFor( @NonNull final Nft nft, final long serialNumber, @NonNull final String ledgerId, - @NonNull final HederaNativeOperations nativeOperations) { + @NonNull final HederaNativeOperations nativeOperations, + int version) { requireNonNull(nft); requireNonNull(token); requireNonNull(ledgerId); @@ -228,7 +232,7 @@ public static Tuple nftTokenInfoTupleFor( final var nftMetaData = nft.metadata() != null ? nft.metadata().toByteArray() : Bytes.EMPTY.toByteArray(); return Tuple.of( - tokenInfoTupleFor(token, ledgerId), + tokenInfoTupleFor(token, ledgerId, version), serialNumber, // The odd construct allowing a token to not have a treasury account set is to accommodate // Token.DEFAULT being passed into this method, which a few Call implementations do @@ -273,17 +277,6 @@ public static Tuple typedKeyTupleFor(@NonNull final BigInteger keyType, @NonNull */ @NonNull private static Tuple hederaTokenTupleFor(@NonNull final Token token) { - // - final Tuple[] keyList = { - typedKeyTupleFor(TokenKeyType.ADMIN_KEY.bigIntegerValue(), token.adminKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.KYC_KEY.bigIntegerValue(), token.kycKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.FREEZE_KEY.bigIntegerValue(), token.freezeKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.WIPE_KEY.bigIntegerValue(), token.wipeKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.SUPPLY_KEY.bigIntegerValue(), token.supplyKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.FEE_SCHEDULE_KEY.bigIntegerValue(), token.feeScheduleKeyOrElse(Key.DEFAULT)), - typedKeyTupleFor(TokenKeyType.PAUSE_KEY.bigIntegerValue(), token.pauseKeyOrElse(Key.DEFAULT)) - }; - return Tuple.of( token.name(), token.symbol(), @@ -292,10 +285,44 @@ private static Tuple hederaTokenTupleFor(@NonNull final Token token) { token.supplyType().protoOrdinal() == TokenSupplyType.FINITE_VALUE, token.maxSupply(), token.accountsFrozenByDefault(), - keyList, + prepareKeyList(token, false), expiryTupleFor(token)); } + private static Tuple[] prepareKeyList(@NonNull final Token token, final boolean isV2) { + final var keyList = new java.util.ArrayList<>(List.of( + typedKeyTupleFor(TokenKeyType.ADMIN_KEY.bigIntegerValue(), token.adminKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor(TokenKeyType.KYC_KEY.bigIntegerValue(), token.kycKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor(TokenKeyType.FREEZE_KEY.bigIntegerValue(), token.freezeKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor(TokenKeyType.WIPE_KEY.bigIntegerValue(), token.wipeKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor(TokenKeyType.SUPPLY_KEY.bigIntegerValue(), token.supplyKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor( + TokenKeyType.FEE_SCHEDULE_KEY.bigIntegerValue(), token.feeScheduleKeyOrElse(Key.DEFAULT)), + typedKeyTupleFor(TokenKeyType.PAUSE_KEY.bigIntegerValue(), token.pauseKeyOrElse(Key.DEFAULT)))); + if (isV2) { + keyList.add(typedKeyTupleFor( + TokenKeyType.METADATA_KEY.bigIntegerValue(), token.metadataKeyOrElse(Key.DEFAULT))); + } + return keyList.toArray(Tuple[]::new); + } + + @NonNull + private static Tuple hederaTokenTupleForV2(@NonNull final Token token) { + final var tokenMetaData = + token.metadata().length() > 0 ? token.metadata().toByteArray() : Bytes.EMPTY.toByteArray(); + return Tuple.of( + token.name(), + token.symbol(), + headlongAddressOf(token.treasuryAccountIdOrElse(ZERO_ACCOUNT_ID)), + token.memo(), + token.supplyType().protoOrdinal() == TokenSupplyType.FINITE_VALUE, + token.maxSupply(), + token.accountsFrozenByDefault(), + prepareKeyList(token, true), + expiryTupleFor(token), + tokenMetaData); + } + public enum TokenKeyType { ADMIN_KEY(1), KYC_KEY(2), @@ -303,7 +330,8 @@ public enum TokenKeyType { WIPE_KEY(8), SUPPLY_KEY(16), FEE_SCHEDULE_KEY(32), - PAUSE_KEY(64); + PAUSE_KEY(64), + METADATA_KEY(128); private final int value; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java index cefa0f7f3d17..69fa08c2b07c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCall.java @@ -22,8 +22,10 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call.PricedResult.gasOnly; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.TokenTupleUtils.fungibleTokenInfoTupleFor; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO_V2; import static java.util.Objects.requireNonNull; +import com.esaulpaugh.headlong.abi.Function; import com.hedera.hapi.node.base.ResponseCodeEnum; import com.hedera.hapi.node.state.token.Token; import com.hedera.node.app.service.contract.impl.exec.gas.SystemContractGasCalculator; @@ -39,16 +41,19 @@ public class FungibleTokenInfoCall extends AbstractNonRevertibleTokenViewCall { private final Configuration configuration; private final boolean isStaticCall; + private final Function function; public FungibleTokenInfoCall( @NonNull final SystemContractGasCalculator gasCalculator, @NonNull final HederaWorldUpdater.Enhancement enhancement, final boolean isStaticCall, @Nullable final Token token, - @NonNull final Configuration configuration) { + @NonNull final Configuration configuration, + Function function) { super(gasCalculator, enhancement, token); this.configuration = requireNonNull(configuration); this.isStaticCall = isStaticCall; + this.function = function; } /** @@ -78,10 +83,17 @@ public FungibleTokenInfoCall( if (isStaticCall && status != SUCCESS) { return revertResult(status, gasRequirement); } - return successResult( - FUNGIBLE_TOKEN_INFO - .getOutputs() - .encodeElements(status.protoOrdinal(), fungibleTokenInfoTupleFor(token, ledgerId)), - gasRequirement); + + return function.getName().equals(FUNGIBLE_TOKEN_INFO.getName()) + ? successResult( + FUNGIBLE_TOKEN_INFO + .getOutputs() + .encodeElements(status.protoOrdinal(), fungibleTokenInfoTupleFor(token, ledgerId, 1)), + gasRequirement) + : successResult( + FUNGIBLE_TOKEN_INFO_V2 + .getOutputs() + .encodeElements(status.protoOrdinal(), fungibleTokenInfoTupleFor(token, ledgerId, 2)), + gasRequirement); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java index 65b0e1d958b1..e38fb02ed5c1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslator.java @@ -24,6 +24,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.common.Call; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.HtsCallAttempt; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes; +import com.hedera.node.config.data.ContractsConfig; import edu.umd.cs.findbugs.annotations.NonNull; import javax.inject.Inject; @@ -32,6 +33,9 @@ public class FungibleTokenInfoTranslator extends AbstractCallTranslator public static final Function TOKEN_INFO = new Function("getTokenInfo(address)", ReturnTypes.RESPONSE_CODE_TOKEN_INFO); + public static final Function TOKEN_INFO_V2 = + new Function("getTokenInfoV2(address)", ReturnTypes.RESPONSE_CODE_TOKEN_INFO_V2); + @Inject public TokenInfoTranslator() { // Dagger2 @@ -43,7 +47,9 @@ public TokenInfoTranslator() { @Override public boolean matches(@NonNull final HtsCallAttempt attempt) { requireNonNull(attempt); - return attempt.isSelector(TOKEN_INFO); + final var v2Enabled = + attempt.configuration().getConfigData(ContractsConfig.class).systemContractTokenInfoV2Enabled(); + return attempt.isSelector(TOKEN_INFO) || attempt.isSelectorIfConfigEnabled(TOKEN_INFO_V2, v2Enabled); } /** @@ -52,13 +58,15 @@ public boolean matches(@NonNull final HtsCallAttempt attempt) { @Override public Call callFrom(@NonNull final HtsCallAttempt attempt) { requireNonNull(attempt); - final var args = TOKEN_INFO.decodeCall(attempt.input().toArrayUnsafe()); + final var function = attempt.isSelector(TOKEN_INFO) ? TOKEN_INFO : TOKEN_INFO_V2; + final var args = function.decodeCall(attempt.input().toArrayUnsafe()); final var token = attempt.linkedToken(fromHeadlongAddress(args.get(0))); return new TokenInfoCall( attempt.systemContractGasCalculator(), attempt.enhancement(), attempt.isStaticCall(), token, - attempt.configuration()); + attempt.configuration(), + function); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java index 0b4d31ed9052..0c3953d31f4b 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/TestHelpers.java @@ -313,6 +313,9 @@ public class TestHelpers { public static final Key PAUSE_KEY = Key.newBuilder() .ed25519(Bytes.fromHex("0505050505050505050505050505050505050505050505050505050505050505")) .build(); + public static final Key METADATA_KEY = Key.newBuilder() + .ed25519(Bytes.fromHex("0606060606060606060606060606060606060606060606060606060606060606")) + .build(); public static final Token FUNGIBLE_EVERYTHING_TOKEN = Token.newBuilder() .tokenId(FUNGIBLE_TOKEN_ID) .name("Fungible Everything Token") @@ -339,6 +342,35 @@ public class TestHelpers { .feeScheduleKey(FEE_SCHEDULE_KEY) .pauseKey(PAUSE_KEY) .build(); + + public static final Token FUNGIBLE_EVERYTHING_TOKEN_V2 = Token.newBuilder() + .tokenId(FUNGIBLE_TOKEN_ID) + .name("Fungible Everything Token") + .symbol("FET") + .memo("The memo") + .treasuryAccountId(SENDER_ID) + .decimals(6) + .totalSupply(7777777L) + .maxSupply(88888888L) + .supplyType(TokenSupplyType.FINITE) + .tokenType(TokenType.FUNGIBLE_COMMON) + .accountsFrozenByDefault(true) + .accountsKycGrantedByDefault(true) + .paused(true) + .expirationSecond(100) + .autoRenewAccountId(SENDER_ID) + .autoRenewSeconds(200) + .metadata(Bytes.wrap("SOLD")) + .customFees(CUSTOM_FEES) + .adminKey(ADMIN_KEY) + .kycKey(KYC_KEY) + .freezeKey(FREEZE_KEY) + .wipeKey(WIPE_KEY) + .supplyKey(SUPPLY_KEY) + .feeScheduleKey(FEE_SCHEDULE_KEY) + .pauseKey(PAUSE_KEY) + .metadataKey(METADATA_KEY) + .build(); public static final List EXPECTED_FIXED_CUSTOM_FEES = List.of( Tuple.of(2L, headlongAddressOf(ZERO_TOKEN_ID), true, false, headlongAddressOf(SENDER_ID)), Tuple.of(3L, headlongAddressOf(FUNGIBLE_TOKEN_ID), false, false, headlongAddressOf(SENDER_ID))); @@ -357,6 +389,16 @@ public class TestHelpers { typedKeyTupleFor(TokenKeyType.FEE_SCHEDULE_KEY.bigIntegerValue(), FEE_SCHEDULE_KEY), typedKeyTupleFor(TokenKeyType.PAUSE_KEY.bigIntegerValue(), PAUSE_KEY)); + public static final List EXPECTED_KEYLIST_V2 = List.of( + typedKeyTupleFor(TokenKeyType.ADMIN_KEY.bigIntegerValue(), ADMIN_KEY), + typedKeyTupleFor(TokenKeyType.KYC_KEY.bigIntegerValue(), KYC_KEY), + typedKeyTupleFor(TokenKeyType.FREEZE_KEY.bigIntegerValue(), FREEZE_KEY), + typedKeyTupleFor(TokenKeyType.WIPE_KEY.bigIntegerValue(), WIPE_KEY), + typedKeyTupleFor(TokenKeyType.SUPPLY_KEY.bigIntegerValue(), SUPPLY_KEY), + typedKeyTupleFor(TokenKeyType.FEE_SCHEDULE_KEY.bigIntegerValue(), FEE_SCHEDULE_KEY), + typedKeyTupleFor(TokenKeyType.PAUSE_KEY.bigIntegerValue(), PAUSE_KEY), + typedKeyTupleFor(TokenKeyType.METADATA_KEY.bigIntegerValue(), METADATA_KEY)); + public static final List EXPECTE_DEFAULT_KEYLIST = List.of( typedKeyTupleFor(TokenKeyType.ADMIN_KEY.bigIntegerValue(), Key.DEFAULT), typedKeyTupleFor(TokenKeyType.KYC_KEY.bigIntegerValue(), Key.DEFAULT), diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java index 16c2e7bd0ce7..e05e758382e9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoCallTest.java @@ -19,12 +19,16 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FIXED_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FRACTIONAL_CUSTOM_FEES; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_KEYLIST_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_ROYALTY_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_DEFAULT_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.revertOutputFor; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.headlongAddressOf; @@ -33,7 +37,6 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; @@ -59,14 +62,14 @@ void returnsFungibleTokenInfoStatusForPresentToken() { final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01"); when(ledgerConfig.id()).thenReturn(expectedLedgerId); - final var subject = - new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN, config); + final var subject = new FungibleTokenInfoCall( + gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN, config, FUNGIBLE_TOKEN_INFO); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - Bytes.wrap(FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO + Bytes.wrap(FUNGIBLE_TOKEN_INFO .getOutputs() .encodeElements( SUCCESS.protoOrdinal(), @@ -96,19 +99,65 @@ void returnsFungibleTokenInfoStatusForPresentToken() { result.getOutput()); } + @Test + void returnsFungibleTokenInfoStatusForPresentTokenV2() { + when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); + final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01"); + when(ledgerConfig.id()).thenReturn(expectedLedgerId); + + final var subject = new FungibleTokenInfoCall( + gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN_V2, config, FUNGIBLE_TOKEN_INFO_V2); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(FUNGIBLE_TOKEN_INFO_V2 + .getOutputs() + .encodeElements( + SUCCESS.protoOrdinal(), + Tuple.of( + Tuple.of( + Tuple.of( + "Fungible Everything Token", + "FET", + headlongAddressOf(SENDER_ID), + "The memo", + true, + 88888888L, + true, + EXPECTED_KEYLIST_V2.toArray(new Tuple[0]), + Tuple.of(100L, headlongAddressOf(SENDER_ID), 200L), + com.hedera.pbj.runtime.io.buffer.Bytes.wrap("SOLD") + .toByteArray()), + 7777777L, + false, + true, + true, + EXPECTED_FIXED_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_FRACTIONAL_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_ROYALTY_CUSTOM_FEES.toArray(new Tuple[0]), + Bytes.wrap(expectedLedgerId.toByteArray()) + .toString()), + 6)) + .array()), + result.getOutput()); + } + @Test void returnsFungibleTokenInfoStatusForMissingToken() { when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01"); when(ledgerConfig.id()).thenReturn(expectedLedgerId); - final var subject = new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), false, null, config); + final var subject = + new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), false, null, config, FUNGIBLE_TOKEN_INFO); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - Bytes.wrap(FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO + Bytes.wrap(FUNGIBLE_TOKEN_INFO .getOutputs() .encodeElements( INVALID_TOKEN_ID.protoOrdinal(), @@ -143,7 +192,22 @@ void returnsFungibleTokenInfoStatusForMissingTokenStaticCall() { when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); when(ledgerConfig.id()).thenReturn(com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01")); - final var subject = new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), true, null, config); + final var subject = + new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), true, null, config, FUNGIBLE_TOKEN_INFO); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(revertOutputFor(INVALID_TOKEN_ID), result.getOutput()); + } + + @Test + void returnsFungibleTokenInfoStatusForMissingTokenStaticCallV2() { + when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); + when(ledgerConfig.id()).thenReturn(com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01")); + + final var subject = + new FungibleTokenInfoCall(gasCalculator, mockEnhancement(), true, null, config, FUNGIBLE_TOKEN_INFO_V2); final var result = subject.execute().fullResult().result(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslatorTest.java index 62725f345f9f..7e2ea0055dcd 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/fungibletokeninfo/FungibleTokenInfoTranslatorTest.java @@ -18,8 +18,10 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator.BURN_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator.FUNGIBLE_TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN_HEADLONG_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelector; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelectorAndCustomConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.fungibletokeninfo.FungibleTokenInfoTranslator; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; +import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; @@ -61,6 +64,9 @@ class FungibleTokenInfoTranslatorTest { @Mock private VerificationStrategies verificationStrategies; + @Mock + private ContractsConfig contractsConfig; + private FungibleTokenInfoTranslator subject; @BeforeEach @@ -75,6 +81,21 @@ void matchesFungibleTokenInfoTranslatorTest() { assertTrue(subject.matches(attempt)); } + @Test + void matchesFungibleTokenInfoTranslatorTestV2() { + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractTokenInfoV2Enabled()).willReturn(true); + attempt = prepareHtsAttemptWithSelectorAndCustomConfig( + FUNGIBLE_TOKEN_INFO_V2, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration); + assertTrue(subject.matches(attempt)); + } + @Test void matchesFailsIfIncorrectSelectorTest() { attempt = prepareHtsAttemptWithSelector( @@ -90,6 +111,20 @@ void callFromTest() { given(attempt.enhancement()).willReturn(enhancement); given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); given(attempt.configuration()).willReturn(configuration); + given(attempt.isSelector(FUNGIBLE_TOKEN_INFO)).willReturn(true); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(FungibleTokenInfoCall.class); + } + + @Test + void callFromTestV2() { + final Tuple tuple = new Tuple(FUNGIBLE_TOKEN_HEADLONG_ADDRESS); + final Bytes inputBytes = Bytes.wrapByteBuffer(FUNGIBLE_TOKEN_INFO_V2.encodeCall(tuple)); + given(attempt.input()).willReturn(inputBytes); + given(attempt.enhancement()).willReturn(enhancement); + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.configuration()).willReturn(configuration); final var call = subject.callFrom(attempt); assertThat(call).isInstanceOf(FungibleTokenInfoCall.class); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java index c811e2e90319..23a9afcfee0a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoCallTest.java @@ -19,13 +19,17 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator.NON_FUNGIBLE_TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator.NON_FUNGIBLE_TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.CIVILIAN_OWNED_NFT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FIXED_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FRACTIONAL_CUSTOM_FEES; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_KEYLIST_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_ROYALTY_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_DEFAULT_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.LEDGER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OPERATOR; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; @@ -69,8 +73,14 @@ void returnsNftTokenInfoStatusForPresentToken() { when(nativeOperations.getAccount(CIVILIAN_OWNED_NFT.ownerIdOrThrow())).thenReturn(SOMEBODY); when(nativeOperations.getAccount(CIVILIAN_OWNED_NFT.spenderIdOrThrow())).thenReturn(OPERATOR); - final var subject = - new NftTokenInfoCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN, 2L, config); + final var subject = new NftTokenInfoCall( + gasCalculator, + mockEnhancement(), + false, + FUNGIBLE_EVERYTHING_TOKEN, + 2L, + config, + NON_FUNGIBLE_TOKEN_INFO); final var result = subject.execute().fullResult().result(); @@ -111,13 +121,75 @@ void returnsNftTokenInfoStatusForPresentToken() { result.getOutput()); } + @Test + void returnsNftTokenInfoStatusForPresentTokenV2() { + when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); + final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex(LEDGER_ID); + when(ledgerConfig.id()).thenReturn(expectedLedgerId); + when(nativeOperations.getNft(FUNGIBLE_EVERYTHING_TOKEN_V2.tokenId().tokenNum(), 2L)) + .thenReturn(CIVILIAN_OWNED_NFT); + + when(nativeOperations.getAccount(CIVILIAN_OWNED_NFT.ownerIdOrThrow())).thenReturn(SOMEBODY); + when(nativeOperations.getAccount(CIVILIAN_OWNED_NFT.spenderIdOrThrow())).thenReturn(OPERATOR); + + final var subject = new NftTokenInfoCall( + gasCalculator, + mockEnhancement(), + false, + FUNGIBLE_EVERYTHING_TOKEN_V2, + 2L, + config, + NON_FUNGIBLE_TOKEN_INFO_V2); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(NON_FUNGIBLE_TOKEN_INFO_V2 + .getOutputs() + .encodeElements( + SUCCESS.protoOrdinal(), + Tuple.of( + Tuple.of( + Tuple.of( + "Fungible Everything Token", + "FET", + headlongAddressOf(SENDER_ID), + "The memo", + true, + 88888888L, + true, + EXPECTED_KEYLIST_V2.toArray(new Tuple[0]), + Tuple.of(100L, headlongAddressOf(SENDER_ID), 200L), + com.hedera.pbj.runtime.io.buffer.Bytes.wrap("SOLD") + .toByteArray()), + 7777777L, + false, + true, + true, + EXPECTED_FIXED_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_FRACTIONAL_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_ROYALTY_CUSTOM_FEES.toArray(new Tuple[0]), + Bytes.wrap(expectedLedgerId.toByteArray()) + .toString()), + 2L, + headlongAddressOf(CIVILIAN_OWNED_NFT.ownerId()), + 1000000L, + com.hedera.pbj.runtime.io.buffer.Bytes.wrap("SOLD") + .toByteArray(), + headlongAddressOf(CIVILIAN_OWNED_NFT.spenderId()))) + .array()), + result.getOutput()); + } + @Test void returnsNftTokenInfoStatusForMissingToken() { when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01"); when(ledgerConfig.id()).thenReturn(expectedLedgerId); - final var subject = new NftTokenInfoCall(gasCalculator, mockEnhancement(), false, null, 0L, config); + final var subject = new NftTokenInfoCall( + gasCalculator, mockEnhancement(), false, null, 0L, config, NON_FUNGIBLE_TOKEN_INFO); final var result = subject.execute().fullResult().result(); @@ -159,7 +231,19 @@ void returnsNftTokenInfoStatusForMissingToken() { @Test void returnsNftTokenInfoStatusForMissingTokenStaticCall() { - final var subject = new NftTokenInfoCall(gasCalculator, mockEnhancement(), true, null, 0L, config); + final var subject = + new NftTokenInfoCall(gasCalculator, mockEnhancement(), true, null, 0L, config, NON_FUNGIBLE_TOKEN_INFO); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(revertOutputFor(INVALID_TOKEN_ID), result.getOutput()); + } + + @Test + void returnsNftTokenInfoStatusForMissingTokenStaticCallV2() { + final var subject = new NftTokenInfoCall( + gasCalculator, mockEnhancement(), true, null, 0L, config, NON_FUNGIBLE_TOKEN_INFO_V2); final var result = subject.execute().fullResult().result(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslatorTest.java index a00b8c0a8dc6..5516a98a808e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/nfttokeninfo/NftTokenInfoTranslatorTest.java @@ -18,8 +18,10 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator.BURN_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator.NON_FUNGIBLE_TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator.NON_FUNGIBLE_TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN_HEADLONG_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelector; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelectorAndCustomConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.nfttokeninfo.NftTokenInfoTranslator; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; +import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; @@ -55,6 +58,9 @@ class NftTokenInfoTranslatorTest { @Mock Configuration configuration; + @Mock + private ContractsConfig contractsConfig; + @Mock private AddressIdConverter addressIdConverter; @@ -80,6 +86,21 @@ void matchesTokenInfoTranslatorTest() { assertTrue(subject.matches(attempt)); } + @Test + void matchesTokenInfoTranslatorTestV2() { + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractTokenInfoV2Enabled()).willReturn(true); + attempt = prepareHtsAttemptWithSelectorAndCustomConfig( + NON_FUNGIBLE_TOKEN_INFO_V2, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration); + assertTrue(subject.matches(attempt)); + } + @Test void matchesFailsIfIncorrectSelectorTest() { attempt = prepareHtsAttemptWithSelector( @@ -95,6 +116,20 @@ void callFromTest() { given(attempt.enhancement()).willReturn(enhancement); given(attempt.configuration()).willReturn(configuration); given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.isSelector(NON_FUNGIBLE_TOKEN_INFO)).willReturn(true); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(NftTokenInfoCall.class); + } + + @Test + void callFromTestV2() { + final Tuple tuple = new Tuple(FUNGIBLE_TOKEN_HEADLONG_ADDRESS, 0L); + final Bytes inputBytes = Bytes.wrapByteBuffer(NON_FUNGIBLE_TOKEN_INFO_V2.encodeCall(tuple)); + given(attempt.input()).willReturn(inputBytes); + given(attempt.enhancement()).willReturn(enhancement); + given(attempt.configuration()).willReturn(configuration); + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); final var call = subject.callFrom(attempt); assertThat(call).isInstanceOf(NftTokenInfoCall.class); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java index c700043454f9..0799ec5d4d1c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoCallTest.java @@ -19,12 +19,16 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_TOKEN_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.ReturnTypes.ZERO_ACCOUNT_ID; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FIXED_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_FRACTIONAL_CUSTOM_FEES; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_KEYLIST_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTED_ROYALTY_CUSTOM_FEES; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_DEFAULT_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.EXPECTE_KEYLIST; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_EVERYTHING_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.LEDGER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SENDER_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.revertOutputFor; @@ -34,7 +38,6 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoCall; -import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator; import com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.common.CallTestBase; import com.hedera.node.config.data.LedgerConfig; import com.swirlds.config.api.Configuration; @@ -60,14 +63,14 @@ void returnsTokenInfoStatusForPresentToken() { final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex(LEDGER_ID); when(ledgerConfig.id()).thenReturn(expectedLedgerId); - final var subject = - new TokenInfoCall(gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN, config); + final var subject = new TokenInfoCall( + gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN, config, TOKEN_INFO); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - Bytes.wrap(TokenInfoTranslator.TOKEN_INFO + Bytes.wrap(TOKEN_INFO .getOutputs() .encodeElements( SUCCESS.protoOrdinal(), @@ -95,19 +98,62 @@ void returnsTokenInfoStatusForPresentToken() { result.getOutput()); } + @Test + void returnsTokenInfoStatusForPresentTokenV2() { + when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); + final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex(LEDGER_ID); + when(ledgerConfig.id()).thenReturn(expectedLedgerId); + + final var subject = new TokenInfoCall( + gasCalculator, mockEnhancement(), false, FUNGIBLE_EVERYTHING_TOKEN_V2, config, TOKEN_INFO_V2); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); + assertEquals( + Bytes.wrap(TOKEN_INFO_V2 + .getOutputs() + .encodeElements( + SUCCESS.protoOrdinal(), + Tuple.of( + Tuple.of( + "Fungible Everything Token", + "FET", + headlongAddressOf(SENDER_ID), + "The memo", + true, + 88888888L, + true, + EXPECTED_KEYLIST_V2.toArray(new Tuple[0]), + Tuple.of(100L, headlongAddressOf(SENDER_ID), 200L), + com.hedera.pbj.runtime.io.buffer.Bytes.wrap("SOLD") + .toByteArray()), + 7777777L, + false, + true, + true, + EXPECTED_FIXED_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_FRACTIONAL_CUSTOM_FEES.toArray(new Tuple[0]), + EXPECTED_ROYALTY_CUSTOM_FEES.toArray(new Tuple[0]), + Bytes.wrap(expectedLedgerId.toByteArray()) + .toString())) + .array()), + result.getOutput()); + } + @Test void returnsTokenInfoStatusForMissingToken() { when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); final var expectedLedgerId = com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01"); when(ledgerConfig.id()).thenReturn(expectedLedgerId); - final var subject = new TokenInfoCall(gasCalculator, mockEnhancement(), false, null, config); + final var subject = new TokenInfoCall(gasCalculator, mockEnhancement(), false, null, config, TOKEN_INFO); final var result = subject.execute().fullResult().result(); assertEquals(MessageFrame.State.COMPLETED_SUCCESS, result.getState()); assertEquals( - Bytes.wrap(TokenInfoTranslator.TOKEN_INFO + Bytes.wrap(TOKEN_INFO .getOutputs() .encodeElements( INVALID_TOKEN_ID.protoOrdinal(), @@ -140,7 +186,20 @@ void returnsTokenInfoStatusForMissingTokenStaticCall() { when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); when(ledgerConfig.id()).thenReturn(com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01")); - final var subject = new TokenInfoCall(gasCalculator, mockEnhancement(), true, null, config); + final var subject = new TokenInfoCall(gasCalculator, mockEnhancement(), true, null, config, TOKEN_INFO); + + final var result = subject.execute().fullResult().result(); + + assertEquals(MessageFrame.State.REVERT, result.getState()); + assertEquals(revertOutputFor(INVALID_TOKEN_ID), result.getOutput()); + } + + @Test + void returnsTokenInfoStatusForMissingTokenStaticCallV2() { + when(config.getConfigData(LedgerConfig.class)).thenReturn(ledgerConfig); + when(ledgerConfig.id()).thenReturn(com.hedera.pbj.runtime.io.buffer.Bytes.fromHex("01")); + + final var subject = new TokenInfoCall(gasCalculator, mockEnhancement(), true, null, config, TOKEN_INFO_V2); final var result = subject.execute().fullResult().result(); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslatorTest.java index 6b195e333f12..8f493844c995 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/tokeninfo/TokenInfoTranslatorTest.java @@ -18,8 +18,10 @@ import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.burn.BurnTranslator.BURN_TOKEN_V2; import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO; +import static com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator.TOKEN_INFO_V2; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.FUNGIBLE_TOKEN_HEADLONG_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelector; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHtsAttemptWithSelectorAndCustomConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -33,6 +35,7 @@ import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoCall; import com.hedera.node.app.service.contract.impl.exec.systemcontracts.hts.tokeninfo.TokenInfoTranslator; import com.hedera.node.app.service.contract.impl.hevm.HederaWorldUpdater.Enhancement; +import com.hedera.node.config.data.ContractsConfig; import com.swirlds.config.api.Configuration; import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.BeforeEach; @@ -55,6 +58,9 @@ class TokenInfoTranslatorTest { @Mock Configuration configuration; + @Mock + private ContractsConfig contractsConfig; + @Mock private AddressIdConverter addressIdConverter; @@ -75,6 +81,21 @@ void matchesTokenInfoTranslatorTest() { assertTrue(subject.matches(attempt)); } + @Test + void matchesTokenInfoTranslatorTestV2() { + given(configuration.getConfigData(ContractsConfig.class)).willReturn(contractsConfig); + given(contractsConfig.systemContractTokenInfoV2Enabled()).willReturn(true); + attempt = prepareHtsAttemptWithSelectorAndCustomConfig( + TOKEN_INFO_V2, + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration); + assertTrue(subject.matches(attempt)); + } + @Test void matchesFailsIfIncorrectSelectorTest() { attempt = prepareHtsAttemptWithSelector( @@ -90,6 +111,20 @@ void callFromTest() { given(attempt.enhancement()).willReturn(enhancement); given(attempt.configuration()).willReturn(configuration); given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); + given(attempt.isSelector(TOKEN_INFO)).willReturn(true); + + final var call = subject.callFrom(attempt); + assertThat(call).isInstanceOf(TokenInfoCall.class); + } + + @Test + void callFromTestV2() { + final Tuple tuple = new Tuple(FUNGIBLE_TOKEN_HEADLONG_ADDRESS); + final Bytes inputBytes = Bytes.wrapByteBuffer(TOKEN_INFO_V2.encodeCall(tuple)); + given(attempt.input()).willReturn(inputBytes); + given(attempt.enhancement()).willReturn(enhancement); + given(attempt.configuration()).willReturn(configuration); + given(attempt.systemContractGasCalculator()).willReturn(gasCalculator); final var call = subject.callFrom(attempt); assertThat(call).isInstanceOf(TokenInfoCall.class); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java index 8f3b7dcdf728..9f17dca38cf0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java @@ -100,7 +100,6 @@ import java.util.OptionalLong; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; -import org.apache.tuweni.bytes.Bytes; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Tag; @@ -180,8 +179,11 @@ final Stream happyPathGetTokenInfo() { newKeyNamed(WIPE_KEY), newKeyNamed(FEE_SCHEDULE_KEY), newKeyNamed(PAUSE_KEY), + newKeyNamed(TokenKeyType.METADATA_KEY.name()), uploadInitCode(TOKEN_INFO_CONTRACT), contractCreate(TOKEN_INFO_CONTRACT).gas(1_000_000L), + uploadInitCode("TokenInfo"), + contractCreate("TokenInfo").gas(1_000_000L), tokenCreate(PRIMARY_TOKEN_NAME) .supplyType(TokenSupplyType.FINITE) .entityMemo(MEMO) @@ -199,6 +201,8 @@ final Stream happyPathGetTokenInfo() { .wipeKey(WIPE_KEY) .feeScheduleKey(FEE_SCHEDULE_KEY) .pauseKey(PAUSE_KEY) + .metadataKey(TokenKeyType.METADATA_KEY.name()) + .metaData("metadata") .withCustom(fixedHbarFee(500L, HTS_COLLECTOR)) // Include a fractional fee with no minimum to collect .withCustom(fractionalFee( @@ -209,8 +213,7 @@ final Stream happyPathGetTokenInfo() { MINIMUM_TO_COLLECT, OptionalLong.of(MAXIMUM_TO_COLLECT), TOKEN_TREASURY)) - .via(CREATE_TXN), - getTokenInfo(PRIMARY_TOKEN_NAME).via(GET_TOKEN_INFO_TXN)) + .via(CREATE_TXN)) .when(withOpContext((spec, opLog) -> allRunFor( spec, contractCall( @@ -224,7 +227,14 @@ final Stream happyPathGetTokenInfo() { TOKEN_INFO_CONTRACT, GET_INFORMATION_FOR_TOKEN, HapiParserUtil.asHeadlongAddress( - asAddress(spec.registry().getTokenID(PRIMARY_TOKEN_NAME))))))) + asAddress(spec.registry().getTokenID(PRIMARY_TOKEN_NAME)))), + contractCall( + "TokenInfo", + "getInformationForTokenV2", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(PRIMARY_TOKEN_NAME)))) + .via("TOKEN_INFO_TXN_V2") + .gas(1_000_000L)))) .then(exposeTargetLedgerIdTo(targetLedgerId::set), withOpContext((spec, opLog) -> { final var getTokenInfoQuery = getTokenInfo(PRIMARY_TOKEN_NAME); allRunFor(spec, getTokenInfoQuery); @@ -248,6 +258,25 @@ final Stream happyPathGetTokenInfo() { .forFunction(FunctionType.HAPI_GET_TOKEN_INFO) .withStatus(SUCCESS) .withTokenInfo(getTokenInfoStructForFungibleToken( + spec, + PRIMARY_TOKEN_NAME, + SYMBOL, + MEMO, + spec.registry() + .getAccountID(TOKEN_TREASURY), + getTokenKeyFromSpec(spec, TokenKeyType.ADMIN_KEY), + expirySecond, + targetLedgerId.get()))))), + childRecordsCheck( + "TOKEN_INFO_TXN_V2", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(FunctionType.HAPI_GET_TOKEN_INFO_V2) + .withStatus(SUCCESS) + .withTokenInfo(getTokenInfoStructForFungibleTokenV2( spec, PRIMARY_TOKEN_NAME, SYMBOL, @@ -280,8 +309,11 @@ final Stream happyPathGetFungibleTokenInfo() { newKeyNamed(WIPE_KEY), newKeyNamed(FEE_SCHEDULE_KEY), newKeyNamed(PAUSE_KEY), + newKeyNamed(TokenKeyType.METADATA_KEY.name()), uploadInitCode(TOKEN_INFO_CONTRACT), contractCreate(TOKEN_INFO_CONTRACT).gas(1_000_000L), + uploadInitCode("TokenInfo"), + contractCreate("TokenInfo").gas(1_000_000L), tokenCreate(FUNGIBLE_TOKEN_NAME) .supplyType(TokenSupplyType.FINITE) .entityMemo(MEMO) @@ -300,6 +332,8 @@ final Stream happyPathGetFungibleTokenInfo() { .wipeKey(WIPE_KEY) .feeScheduleKey(FEE_SCHEDULE_KEY) .pauseKey(PAUSE_KEY) + .metadataKey(TokenKeyType.METADATA_KEY.name()) + .metaData("metadata") .withCustom(fixedHbarFee(500L, HTS_COLLECTOR)) // Also include a fractional fee with no minimum to collect .withCustom(fractionalFee( @@ -324,7 +358,14 @@ final Stream happyPathGetFungibleTokenInfo() { TOKEN_INFO_CONTRACT, GET_INFORMATION_FOR_FUNGIBLE_TOKEN, HapiParserUtil.asHeadlongAddress( - asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN_NAME))))))) + asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN_NAME)))), + contractCall( + "TokenInfo", + "getInformationForFungibleTokenV2", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(FUNGIBLE_TOKEN_NAME)))) + .via("FUNGIBLE_TOKEN_INFO_TXN_V2") + .gas(1_000_000L)))) .then(exposeTargetLedgerIdTo(targetLedgerId::set), withOpContext((spec, opLog) -> { final var getTokenInfoQuery = getTokenInfo(FUNGIBLE_TOKEN_NAME); allRunFor(spec, getTokenInfoQuery); @@ -348,6 +389,26 @@ final Stream happyPathGetFungibleTokenInfo() { .withStatus(SUCCESS) .withDecimals(decimals) .withTokenInfo(getTokenInfoStructForFungibleToken( + spec, + FUNGIBLE_TOKEN_NAME, + FUNGIBLE_SYMBOL, + MEMO, + spec.registry() + .getAccountID(TOKEN_TREASURY), + getTokenKeyFromSpec(spec, TokenKeyType.ADMIN_KEY), + expirySecond, + targetLedgerId.get()))))), + childRecordsCheck( + "FUNGIBLE_TOKEN_INFO_TXN_V2", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction(FunctionType.HAPI_GET_FUNGIBLE_TOKEN_INFO_V2) + .withStatus(SUCCESS) + .withDecimals(decimals) + .withTokenInfo(getTokenInfoStructForFungibleTokenV2( spec, FUNGIBLE_TOKEN_NAME, FUNGIBLE_SYMBOL, @@ -383,8 +444,11 @@ final Stream happyPathGetNonFungibleTokenInfo() { newKeyNamed(WIPE_KEY), newKeyNamed(FEE_SCHEDULE_KEY), newKeyNamed(PAUSE_KEY), + newKeyNamed(TokenKeyType.METADATA_KEY.name()), uploadInitCode(TOKEN_INFO_CONTRACT), contractCreate(TOKEN_INFO_CONTRACT).gas(1_000_000L), + uploadInitCode("TokenInfo"), + contractCreate("TokenInfo").gas(1_000_000L), tokenCreate(FEE_DENOM).treasury(HTS_COLLECTOR), tokenCreate(NON_FUNGIBLE_TOKEN_NAME) .tokenType(TokenType.NON_FUNGIBLE_UNIQUE) @@ -404,6 +468,8 @@ final Stream happyPathGetNonFungibleTokenInfo() { .wipeKey(WIPE_KEY) .feeScheduleKey(FEE_SCHEDULE_KEY) .pauseKey(PAUSE_KEY) + .metadataKey(TokenKeyType.METADATA_KEY.name()) + .metaData("metadata") .withCustom(royaltyFeeWithFallback( 1, 2, fixedHtsFeeInheritingRoyaltyCollector(100, FEE_DENOM), HTS_COLLECTOR)) .via(CREATE_TXN), @@ -435,7 +501,15 @@ final Stream happyPathGetNonFungibleTokenInfo() { GET_INFORMATION_FOR_NON_FUNGIBLE_TOKEN, HapiParserUtil.asHeadlongAddress( asAddress(spec.registry().getTokenID(NON_FUNGIBLE_TOKEN_NAME))), - 1L)))) + 1L), + contractCall( + "TokenInfo", + "getInformationForNonFungibleTokenV2", + HapiParserUtil.asHeadlongAddress( + asAddress(spec.registry().getTokenID(NON_FUNGIBLE_TOKEN_NAME))), + 1L) + .via("NON_FUNGIBLE_TOKEN_INFO_TXN_V2") + .gas(1_000_000L)))) .then(exposeTargetLedgerIdTo(targetLedgerId::set), withOpContext((spec, opLog) -> { final var getTokenInfoQuery = getTokenInfo(NON_FUNGIBLE_TOKEN_NAME); allRunFor(spec, getTokenInfoQuery); @@ -473,6 +547,24 @@ final Stream happyPathGetNonFungibleTokenInfo() { getTokenKeyFromSpec(spec, TokenKeyType.ADMIN_KEY), expirySecond, targetLedgerId.get())) + .withNftTokenInfo(nftTokenInfo)))), + childRecordsCheck( + "NON_FUNGIBLE_TOKEN_INFO_TXN_V2", + SUCCESS, + recordWith() + .status(SUCCESS) + .contractCallResult(resultWith() + .contractCallResult(htsPrecompileResult() + .forFunction( + FunctionType.HAPI_GET_NON_FUNGIBLE_TOKEN_INFO_V2) + .withStatus(SUCCESS) + .withTokenInfo(getTokenInfoStructForNonFungibleTokenV2( + spec, + spec.registry() + .getAccountID(TOKEN_TREASURY), + getTokenKeyFromSpec(spec, TokenKeyType.ADMIN_KEY), + expirySecond, + targetLedgerId.get())) .withNftTokenInfo(nftTokenInfo))))); })); } @@ -1590,18 +1682,21 @@ private TokenNftInfo getTokenNftInfoForCheck( .build(); } - private TokenNftInfo getEmptyNft() { - return TokenNftInfo.newBuilder() - .setLedgerId(ByteString.empty()) - .setNftID(NftID.getDefaultInstance()) - .setAccountID(AccountID.getDefaultInstance()) - .setCreationTime(Timestamp.newBuilder().build()) - .setMetadata(ByteString.empty()) - .setSpenderId(AccountID.getDefaultInstance()) + private TokenInfo getTokenInfoStructForFungibleToken( + final HapiSpec spec, + final String tokenName, + final String symbol, + final String memo, + final AccountID treasury, + final Key adminKey, + final long expirySecond, + ByteString ledgerId) { + + return buildBaseTokenInfo(spec, tokenName, symbol, memo, treasury, adminKey, expirySecond, ledgerId) .build(); } - private TokenInfo getTokenInfoStructForFungibleToken( + private TokenInfo getTokenInfoStructForFungibleTokenV2( final HapiSpec spec, final String tokenName, final String symbol, @@ -1610,9 +1705,27 @@ private TokenInfo getTokenInfoStructForFungibleToken( final Key adminKey, final long expirySecond, ByteString ledgerId) { - final var autoRenewAccount = spec.registry().getAccountID(AUTO_RENEW_ACCOUNT); - final ArrayList customFees = getExpectedCustomFees(spec); + final ByteString meta = ByteString.copyFrom("metadata".getBytes(StandardCharsets.UTF_8)); + + return buildBaseTokenInfo(spec, tokenName, symbol, memo, treasury, adminKey, expirySecond, ledgerId) + .setMetadata(meta) + .setMetadataKey(getTokenKeyFromSpec(spec, TokenKeyType.METADATA_KEY)) + .build(); + } + + private TokenInfo.Builder buildBaseTokenInfo( + final HapiSpec spec, + final String tokenName, + final String symbol, + final String memo, + final AccountID treasury, + final Key adminKey, + final long expirySecond, + ByteString ledgerId) { + + final var autoRenewAccount = spec.registry().getAccountID(AUTO_RENEW_ACCOUNT); + final var customFees = getExpectedCustomFees(spec); return TokenInfo.newBuilder() .setLedgerId(ledgerId) @@ -1635,41 +1748,7 @@ private TokenInfo getTokenInfoStructForFungibleToken( .setWipeKey(getTokenKeyFromSpec(spec, TokenKeyType.WIPE_KEY)) .setSupplyKey(getTokenKeyFromSpec(spec, TokenKeyType.SUPPLY_KEY)) .setFeeScheduleKey(getTokenKeyFromSpec(spec, TokenKeyType.FEE_SCHEDULE_KEY)) - .setPauseKey(getTokenKeyFromSpec(spec, TokenKeyType.PAUSE_KEY)) - .build(); - } - - private TokenInfo getTokenInfoStructForEmptyFungibleToken( - final String tokenName, - final String symbol, - final String memo, - final AccountID treasury, - final long expirySecond, - ByteString ledgerId) { - - final ArrayList customFees = new ArrayList<>(); - - return TokenInfo.newBuilder() - .setLedgerId(ledgerId) - .setSupplyTypeValue(0) - .setExpiry(Timestamp.newBuilder().setSeconds(expirySecond)) - .setAutoRenewAccount(AccountID.getDefaultInstance()) - .setAutoRenewPeriod(Duration.newBuilder().setSeconds(0).build()) - .setSymbol(symbol) - .setName(tokenName) - .setMemo(memo) - .setTreasury(treasury) - .setTotalSupply(0) - .setMaxSupply(0) - .addAllCustomFees(customFees) - .setAdminKey(Key.newBuilder().build()) - .setKycKey(Key.newBuilder().build()) - .setFreezeKey(Key.newBuilder().build()) - .setWipeKey(Key.newBuilder().build()) - .setSupplyKey(Key.newBuilder().build()) - .setFeeScheduleKey(Key.newBuilder().build()) - .setPauseKey(Key.newBuilder().build()) - .build(); + .setPauseKey(getTokenKeyFromSpec(spec, TokenKeyType.PAUSE_KEY)); } @NonNull @@ -1721,9 +1800,43 @@ private TokenInfo getTokenInfoStructForNonFungibleToken( final Key adminKey, final long expirySecond, final ByteString ledgerId) { + return buildTokenInfo(spec, tokenName, symbol, memo, treasury, adminKey, expirySecond, ledgerId, null, false); + } + + private TokenInfo getTokenInfoStructForNonFungibleTokenV2( + final HapiSpec spec, + final AccountID treasury, + final Key adminKey, + final long expirySecond, + final ByteString ledgerId) { + final ByteString meta = ByteString.copyFrom("metadata".getBytes(StandardCharsets.UTF_8)); + return buildTokenInfo( + spec, + TokenInfoHTSSuite.NON_FUNGIBLE_TOKEN_NAME, + TokenInfoHTSSuite.NON_FUNGIBLE_SYMBOL, + TokenInfoHTSSuite.MEMO, + treasury, + adminKey, + expirySecond, + ledgerId, + meta, + true); + } + + private TokenInfo buildTokenInfo( + final HapiSpec spec, + final String tokenName, + final String symbol, + final String memo, + final AccountID treasury, + final Key adminKey, + final long expirySecond, + final ByteString ledgerId, + final ByteString metadata, + final boolean includeMetadataKey) { final var autoRenewAccount = spec.registry().getAccountID(AUTO_RENEW_ACCOUNT); - return TokenInfo.newBuilder() + TokenInfo.Builder builder = TokenInfo.newBuilder() .setLedgerId(ledgerId) .setSupplyTypeValue(TokenSupplyType.FINITE_VALUE) .setExpiry(Timestamp.newBuilder().setSeconds(expirySecond)) @@ -1744,8 +1857,17 @@ private TokenInfo getTokenInfoStructForNonFungibleToken( .setWipeKey(getTokenKeyFromSpec(spec, TokenKeyType.WIPE_KEY)) .setSupplyKey(getTokenKeyFromSpec(spec, TokenKeyType.SUPPLY_KEY)) .setFeeScheduleKey(getTokenKeyFromSpec(spec, TokenKeyType.FEE_SCHEDULE_KEY)) - .setPauseKey(getTokenKeyFromSpec(spec, TokenKeyType.PAUSE_KEY)) - .build(); + .setPauseKey(getTokenKeyFromSpec(spec, TokenKeyType.PAUSE_KEY)); + + if (metadata != null) { + builder.setMetadata(metadata); + } + + if (includeMetadataKey) { + builder.setMetadataKey(getTokenKeyFromSpec(spec, TokenKeyType.METADATA_KEY)); + } + + return builder.build(); } @NonNull @@ -1794,8 +1916,4 @@ private Key getTokenKeyFromSpec(final HapiSpec spec, final TokenKeyType type) { return keyBuilder.build(); } - - private ByteString fromString(final String value) { - return ByteString.copyFrom(Bytes.fromHexString(value).toArray()); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/contracts/precompile/HTSPrecompileResult.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/contracts/precompile/HTSPrecompileResult.java index add3da7eb0d3..5a593836d1b1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/contracts/precompile/HTSPrecompileResult.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/utils/contracts/precompile/HTSPrecompileResult.java @@ -23,6 +23,7 @@ import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FIXED_FEE; import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.FRACTIONAL_FEE; import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.HEDERA_TOKEN_V1; +import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.HEDERA_TOKEN_V4; import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.KEY_VALUE; import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.RESPONSE_STATUS_AT_BEGINNING; import static com.hedera.node.app.hapi.utils.contracts.ParsingConstants.ROYALTY_FEE; @@ -81,19 +82,40 @@ private HTSPrecompileResult() {} + ARRAY_BRACKETS + ",string" + ")"; + public static final String TOKEN_INFO_V2 = "(" + + HEDERA_TOKEN_V4.replace(removeBrackets(ADDRESS), removeBrackets(BYTES32)) + + ",int64,bool,bool,bool," + + FIXED_FEE_REPLACED_ADDRESS + + ARRAY_BRACKETS + + "," + + FRACTIONAL_FEE_REPLACED_ADDRESS + + ARRAY_BRACKETS + + "," + + ROYALTY_FEE_REPLACED_ADDRESS + + ARRAY_BRACKETS + + ",string" + + ")"; public static final String FUNGIBLE_TOKEN_INFO_REPLACED_ADDRESS = "(" + TOKEN_INFO_REPLACED_ADDRESS + ",int32" + ")"; + public static final String FUNGIBLE_TOKEN_INFO_V2 = "(" + TOKEN_INFO_V2 + ",int32" + ")"; public static final String NON_FUNGIBLE_TOKEN_INFO_REPLACED_ADDRESS = "(" + TOKEN_INFO_REPLACED_ADDRESS + ",int64,bytes32,int64,bytes,bytes32" + ")"; + public static final String NON_FUNGIBLE_TOKEN_INFO_V2 = + "(" + TOKEN_INFO_V2 + ",int64,bytes32,int64,bytes,bytes32" + ")"; public static final String KEY_VALUE_REPLACED_ADDRESS = KEY_VALUE.replace(ADDRESS_TYPE, BYTES_32_TYPE); public static final TupleType getTokenInfoTypeReplacedAddress = TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + TOKEN_INFO_REPLACED_ADDRESS + ")"); + public static final TupleType getTokenInfoV2 = TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + TOKEN_INFO_V2 + ")"); public static final TupleType getFungibleTokenInfoTypeReplacedAddress = TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + FUNGIBLE_TOKEN_INFO_REPLACED_ADDRESS + ")"); + public static final TupleType getFungibleTokenInfoV2 = + TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + FUNGIBLE_TOKEN_INFO_V2 + ")"); public static final TupleType getNonFungibleTokenInfoTypeReplacedAddress = TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + NON_FUNGIBLE_TOKEN_INFO_REPLACED_ADDRESS + ")"); + public static final TupleType getNonFungibleTokenInfoV2 = + TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + NON_FUNGIBLE_TOKEN_INFO_V2 + ")"); public static final TupleType tokenGetCustomFeesReplacedAddress = TupleType.parse(RESPONSE_STATUS_AT_BEGINNING + FIXED_FEE_REPLACED_ADDRESS + ARRAY_BRACKETS @@ -160,8 +182,11 @@ public HTSPrecompileResult forFunction(final FunctionType functionType) { GET_TOKEN_DEFAULT_FREEZE_STATUS, HAPI_IS_KYC -> intBoolTuple; case HAPI_GET_TOKEN_INFO -> getTokenInfoTypeReplacedAddress; + case HAPI_GET_TOKEN_INFO_V2 -> getTokenInfoV2; case HAPI_GET_FUNGIBLE_TOKEN_INFO -> getFungibleTokenInfoTypeReplacedAddress; + case HAPI_GET_FUNGIBLE_TOKEN_INFO_V2 -> getFungibleTokenInfoV2; case HAPI_GET_NON_FUNGIBLE_TOKEN_INFO -> getNonFungibleTokenInfoTypeReplacedAddress; + case HAPI_GET_NON_FUNGIBLE_TOKEN_INFO_V2 -> getNonFungibleTokenInfoV2; case HAPI_GET_TOKEN_CUSTOM_FEES -> tokenGetCustomFeesReplacedAddress; case HAPI_GET_TOKEN_KEY -> getTokenKeyReplacedAddress; case HAPI_GET_TOKEN_TYPE -> intPairTuple; @@ -333,8 +358,11 @@ public Bytes getBytes() { case HAPI_ALLOWANCE -> Tuple.of(status.getNumber(), BigInteger.valueOf(allowance)); case HAPI_GET_APPROVED -> Tuple.of(status.getNumber(), expandByteArrayTo32Length(approved)); case HAPI_GET_TOKEN_INFO -> getTupleForGetTokenInfo(); + case HAPI_GET_TOKEN_INFO_V2 -> getTupleForGetTokenInfoV2(); case HAPI_GET_FUNGIBLE_TOKEN_INFO -> getTupleForGetFungibleTokenInfo(); + case HAPI_GET_FUNGIBLE_TOKEN_INFO_V2 -> getTupleForGetFungibleTokenInfoV2(); case HAPI_GET_NON_FUNGIBLE_TOKEN_INFO -> getTupleForGetNonFungibleTokenInfo(); + case HAPI_GET_NON_FUNGIBLE_TOKEN_INFO_V2 -> getTupleForGetNonFungibleTokenInfoV2(); case HAPI_IS_KYC -> Tuple.of(status.getNumber(), isKyc); case GET_TOKEN_DEFAULT_FREEZE_STATUS -> Tuple.of(status.getNumber(), tokenDefaultFreezeStatus); case GET_TOKEN_DEFAULT_KYC_STATUS -> Tuple.of(status.getNumber(), tokenDefaultKycStatus); @@ -354,10 +382,18 @@ private Tuple getTupleForGetTokenInfo() { return Tuple.of(status.getNumber(), getTupleForTokenInfo()); } + private Tuple getTupleForGetTokenInfoV2() { + return Tuple.of(status.getNumber(), getTupleForTokenInfoV2()); + } + private Tuple getTupleForGetFungibleTokenInfo() { return Tuple.of(status.getNumber(), Tuple.of(getTupleForTokenInfo(), decimals)); } + private Tuple getTupleForGetFungibleTokenInfoV2() { + return Tuple.of(status.getNumber(), Tuple.of(getTupleForTokenInfoV2(), decimals)); + } + private Tuple getTupleForGetNonFungibleTokenInfo() { return Tuple.of( status.getNumber(), @@ -370,6 +406,18 @@ private Tuple getTupleForGetNonFungibleTokenInfo() { expandByteArrayTo32Length(Utils.asAddress(nonFungibleTokenInfo.getSpenderId())))); } + private Tuple getTupleForGetNonFungibleTokenInfoV2() { + return Tuple.of( + status.getNumber(), + Tuple.of( + getTupleForTokenInfoV2(), + nonFungibleTokenInfo.getNftID().getSerialNumber(), + expandByteArrayTo32Length(Utils.asAddress(nonFungibleTokenInfo.getAccountID())), + nonFungibleTokenInfo.getCreationTime().getSeconds(), + nonFungibleTokenInfo.getMetadata().toByteArray(), + expandByteArrayTo32Length(Utils.asAddress(nonFungibleTokenInfo.getSpenderId())))); + } + private Tuple getTupleForTokenGetCustomFees() { return getTupleForTokenCustomFees(status.getNumber()); } @@ -435,6 +483,26 @@ private Tuple getTupleForTokenInfo() { Bytes.wrap(tokenInfo.getLedgerId().toByteArray()).toString()); } + private Tuple getTupleForTokenInfoV2() { + final var fixedFees = new ArrayList(); + final var fractionalFees = new ArrayList(); + final var royaltyFees = new ArrayList(); + + for (final var customFee : tokenInfo.getCustomFeesList()) { + extractFees(fixedFees, fractionalFees, royaltyFees, customFee); + } + return Tuple.of( + getHederaTokenTupleV2(), + tokenInfo.getTotalSupply(), + tokenInfo.getDeleted(), + tokenInfo.getDefaultKycStatus().getNumber() == 1, + tokenInfo.getPauseStatus().getNumber() == 1, + fixedFees.toArray(new Tuple[fixedFees.size()]), + fractionalFees.toArray(new Tuple[fractionalFees.size()]), + royaltyFees.toArray(new Tuple[royaltyFees.size()]), + Bytes.wrap(tokenInfo.getLedgerId().toByteArray()).toString()); + } + private Tuple getFixedFeeTuple(final FixedFee fixedFee, final byte[] feeCollector) { return Tuple.of( fixedFee.getAmount(), @@ -483,23 +551,52 @@ private Tuple getHederaTokenTuple() { expiryTuple); } + private Tuple getHederaTokenTupleV2() { + expiry = tokenInfo.getExpiry().getSeconds(); + autoRenewPeriod = tokenInfo.getAutoRenewPeriod().getSeconds(); + final var expiryTuple = Tuple.of( + expiry, expandByteArrayTo32Length(Utils.asAddress(tokenInfo.getAutoRenewAccount())), autoRenewPeriod); + + return Tuple.of( + tokenInfo.getName(), + tokenInfo.getSymbol(), + expandByteArrayTo32Length(Utils.asAddress(tokenInfo.getTreasury())), + tokenInfo.getMemo(), + tokenInfo.getSupplyType().getNumber() == 1, + tokenInfo.getMaxSupply(), + tokenInfo.getDefaultFreezeStatus().getNumber() == 1, + getTokenKeysTuplesV2(), + expiryTuple, + tokenInfo.getMetadata().toByteArray()); + } + private Tuple[] getTokenKeysTuples() { - final var adminKeyToConvert = tokenInfo.getAdminKey(); - final var kycKeyToConvert = tokenInfo.getKycKey(); - final var freezeKeyToConvert = tokenInfo.getFreezeKey(); - final var wipeKeyToConvert = tokenInfo.getWipeKey(); - final var supplyKeyToConvert = tokenInfo.getSupplyKey(); - final var feeScheduleKeyToConvert = tokenInfo.getFeeScheduleKey(); - final var pauseKeyToConvert = tokenInfo.getPauseKey(); - - final Tuple[] tokenKeys = new Tuple[TokenKeyType.values().length - 1]; - tokenKeys[0] = getKeyTuple(BigInteger.valueOf(TokenKeyType.ADMIN_KEY.value()), adminKeyToConvert); - tokenKeys[1] = getKeyTuple(BigInteger.valueOf(TokenKeyType.KYC_KEY.value()), kycKeyToConvert); - tokenKeys[2] = getKeyTuple(BigInteger.valueOf(TokenKeyType.FREEZE_KEY.value()), freezeKeyToConvert); - tokenKeys[3] = getKeyTuple(BigInteger.valueOf(TokenKeyType.WIPE_KEY.value()), wipeKeyToConvert); - tokenKeys[4] = getKeyTuple(BigInteger.valueOf(TokenKeyType.SUPPLY_KEY.value()), supplyKeyToConvert); - tokenKeys[5] = getKeyTuple(BigInteger.valueOf(TokenKeyType.FEE_SCHEDULE_KEY.value()), feeScheduleKeyToConvert); - tokenKeys[6] = getKeyTuple(BigInteger.valueOf(TokenKeyType.PAUSE_KEY.value()), pauseKeyToConvert); + return buildTokenKeysTuples(); + } + + private Tuple[] getTokenKeysTuplesV2() { + return buildTokenKeysTuples( + getKeyTuple(BigInteger.valueOf(TokenKeyType.METADATA_KEY.value()), tokenInfo.getMetadataKey())); + } + + private Tuple[] buildTokenKeysTuples(final Tuple... additionalKeys) { + // -1 for the additional key for METADATA + final int existingKeysLength = TokenKeyType.values().length - 1; + final int additionalKeysLength = additionalKeys.length; + final Tuple[] tokenKeys = new Tuple[existingKeysLength + additionalKeysLength]; + + tokenKeys[0] = getKeyTuple(BigInteger.valueOf(TokenKeyType.ADMIN_KEY.value()), tokenInfo.getAdminKey()); + tokenKeys[1] = getKeyTuple(BigInteger.valueOf(TokenKeyType.KYC_KEY.value()), tokenInfo.getKycKey()); + tokenKeys[2] = getKeyTuple(BigInteger.valueOf(TokenKeyType.FREEZE_KEY.value()), tokenInfo.getFreezeKey()); + tokenKeys[3] = getKeyTuple(BigInteger.valueOf(TokenKeyType.WIPE_KEY.value()), tokenInfo.getWipeKey()); + tokenKeys[4] = getKeyTuple(BigInteger.valueOf(TokenKeyType.SUPPLY_KEY.value()), tokenInfo.getSupplyKey()); + tokenKeys[5] = + getKeyTuple(BigInteger.valueOf(TokenKeyType.FEE_SCHEDULE_KEY.value()), tokenInfo.getFeeScheduleKey()); + tokenKeys[6] = getKeyTuple(BigInteger.valueOf(TokenKeyType.PAUSE_KEY.value()), tokenInfo.getPauseKey()); + + if (additionalKeys.length > 0) { + System.arraycopy(additionalKeys, 0, tokenKeys, existingKeysLength, additionalKeysLength); + } return tokenKeys; } diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.bin b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.bin new file mode 100644 index 000000000000..bb372d1e4c74 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.bin @@ -0,0 +1 @@ +6080604052348015600e575f80fd5b506136678061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610091575f3560e01c80638b02c625116100645780638b02c625146101565780638e5e7996146101865780639b23d3d9146101b6578063cf63bb69146101e6578063e6cbc82a1461021657610091565b806315dacbea1461009557806335589a13146100c557806359c16f5a146100f5578063618dc65e14610125575b5f80fd5b6100af60048036038101906100aa919061116e565b610246565b6040516100bc91906111ed565b60405180910390f35b6100df60048036038101906100da9190611206565b61035e565b6040516100ec91906119ee565b60405180910390f35b61010f600480360381019061010a9190611206565b61038e565b60405161011c9190611b41565b60405180910390f35b61013f600480360381019061013a9190611c8d565b6103be565b60405161014d929190611d47565b60405180910390f35b610170600480360381019061016b9190611206565b610513565b60405161017d9190611f80565b60405180910390f35b6101a0600480360381019061019b9190611fca565b610543565b6040516101ad9190612095565b60405180910390f35b6101d060048036038101906101cb919061116e565b610575565b6040516101dd91906111ed565b60405180910390f35b61020060048036038101906101fb9190611206565b61068d565b60405161020d9190612193565b60405180910390f35b610230600480360381019061022b9190611fca565b6106bd565b60405161023d9190612240565b60405180910390f35b5f805f61016773ffffffffffffffffffffffffffffffffffffffff166315dacbea60e01b88888888604051602401610281949392919061227e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516102eb91906122fb565b5f604051808303815f865af19150503d805f8114610324576040519150601f19603f3d011682016040523d82523d5f602084013e610329565b606091505b50915091508161033a57601561034f565b8080602001905181019061034e919061233b565b5b60030b92505050949350505050565b610366610df3565b5f80610371846106ef565b91509150601660030b8214610384575f80fd5b8092505050919050565b610396610e4a565b5f806103a184610819565b91509150601660030b82146103b4575f80fd5b8092505050919050565b5f60605f8061016773ffffffffffffffffffffffffffffffffffffffff1663618dc65e60e01b87876040516024016103f7929190612366565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161046191906122fb565b5f604051808303815f865af19150503d805f811461049a576040519150601f19603f3d011682016040523d82523d5f602084013e61049f565b606091505b50915091507f4af4780e06fe8cb9df64b0794fa6f01399af979175bb988e35e0e57e594567bc82826040516104d59291906123a3565b60405180910390a1816104f857601560405180602001604052805f8152506104fc565b6016815b8160030b9150809450819550505050509250929050565b61051b610e6c565b5f8061052684610943565b91509150601660030b8214610539575f80fd5b8092505050919050565b61054b610e8e565b5f806105578585610a6d565b91509150601660030b821461056a575f80fd5b809250505092915050565b5f805f61016773ffffffffffffffffffffffffffffffffffffffff16639b23d3d960e01b888888886040516024016105b0949392919061227e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161061a91906122fb565b5f604051808303815f865af19150503d805f8114610653576040519150601f19603f3d011682016040523d82523d5f602084013e610658565b606091505b50915091508161066957601561067e565b8080602001905181019061067d919061233b565b5b60030b92505050949350505050565b610695610ef8565b5f806106a084610b9b565b91509150601660030b82146106b3575f80fd5b8092505050919050565b6106c5610f4f565b5f806106d18585610cc5565b91509150601660030b82146106e4575f80fd5b809250505092915050565b5f6106f8610df3565b5f8061016773ffffffffffffffffffffffffffffffffffffffff16631f69565f60e01b8660405160240161072c91906123d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161079691906122fb565b5f604051808303815f865af19150503d805f81146107cf576040519150601f19603f3d011682016040523d82523d5f602084013e6107d4565b606091505b50915091506107e1610df3565b826107ee57601581610803565b818060200190518101906108029190612e8a565b5b8160030b91508095508196505050505050915091565b5f610822610e4a565b5f8061016773ffffffffffffffffffffffffffffffffffffffff16633f28a19b60e01b8660405160240161085691906123d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516108c091906122fb565b5f604051808303815f865af19150503d805f81146108f9576040519150601f19603f3d011682016040523d82523d5f602084013e6108fe565b606091505b509150915061090b610e4a565b826109185760158161092d565b8180602001905181019061092c9190612f4d565b5b8160030b91508095508196505050505050915091565b5f61094c610e6c565b5f8061016773ffffffffffffffffffffffffffffffffffffffff16633f9dc35360e01b8660405160240161098091906123d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516109ea91906122fb565b5f604051808303815f865af19150503d805f8114610a23576040519150601f19603f3d011682016040523d82523d5f602084013e610a28565b606091505b5091509150610a35610e6c565b82610a4257601581610a57565b81806020019051810190610a5691906132f8565b5b8160030b91508095508196505050505050915091565b5f610a76610e8e565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1663287e1da860e01b8787604051602401610aac929190613352565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610b1691906122fb565b5f604051808303815f865af19150503d805f8114610b4f576040519150601f19603f3d011682016040523d82523d5f602084013e610b54565b606091505b5091509150610b61610e8e565b82610b6e57601581610b83565b81806020019051810190610b82919061344e565b5b8160030b915080955081965050505050509250929050565b5f610ba4610ef8565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1663bc03816f60e01b86604051602401610bd891906123d1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c4291906122fb565b5f604051808303815f865af19150503d805f8114610c7b576040519150601f19603f3d011682016040523d82523d5f602084013e610c80565b606091505b5091509150610c8d610ef8565b82610c9a57601581610caf565b81806020019051810190610cae91906134a8565b5b8160030b91508095508196505050505050915091565b5f610cce610f4f565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1663fb29ac6e60e01b8787604051602401610d04929190613352565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610d6e91906122fb565b5f604051808303815f865af19150503d805f8114610da7576040519150601f19603f3d011682016040523d82523d5f602084013e610dac565b606091505b5091509150610db9610f4f565b82610dc657601581610ddb565b81806020019051810190610dda91906135d7565b5b8160030b915080955081965050505050509250929050565b604051806101200160405280610e07610fb9565b81526020015f60070b81526020015f151581526020015f151581526020015f15158152602001606081526020016060815260200160608152602001606081525090565b6040518060400160405280610e5d610df3565b81526020015f60030b81525090565b6040518060400160405280610e7f610ef8565b81526020015f60030b81525090565b6040518060c00160405280610ea1610df3565b81526020015f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b8152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b604051806101200160405280610f0c611024565b81526020015f60070b81526020015f151581526020015f151581526020015f15158152602001606081526020016060815260200160608152602001606081525090565b6040518060c00160405280610f62610ef8565b81526020015f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b8152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f151581526020016060815260200161101e611096565b81525090565b60405180610140016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f1515815260200160608152602001611089611096565b8152602001606081525090565b60405180606001604052805f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b5f604051905090565b5f80fd5b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61110a826110e1565b9050919050565b61111a81611100565b8114611124575f80fd5b50565b5f8135905061113581611111565b92915050565b5f819050919050565b61114d8161113b565b8114611157575f80fd5b50565b5f8135905061116881611144565b92915050565b5f805f8060808587031215611186576111856110d9565b5b5f61119387828801611127565b94505060206111a487828801611127565b93505060406111b587828801611127565b92505060606111c68782880161115a565b91505092959194509250565b5f8160070b9050919050565b6111e7816111d2565b82525050565b5f6020820190506112005f8301846111de565b92915050565b5f6020828403121561121b5761121a6110d9565b5b5f61122884828501611127565b91505092915050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61127382611231565b61127d818561123b565b935061128d81856020860161124b565b61129681611259565b840191505092915050565b6112aa81611100565b82525050565b5f8115159050919050565b6112c4816112b0565b82525050565b6112d3816111d2565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61130b8161113b565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f61133582611311565b61133f818561131b565b935061134f81856020860161124b565b61135881611259565b840191505092915050565b5f60a083015f8301516113785f8601826112bb565b50602083015161138b60208601826112a1565b50604083015184820360408601526113a3828261132b565b915050606083015184820360608601526113bd828261132b565b91505060808301516113d260808601826112a1565b508091505092915050565b5f604083015f8301516113f25f860182611302565b506020830151848203602086015261140a8282611363565b9150508091505092915050565b5f61142283836113dd565b905092915050565b5f602082019050919050565b5f611440826112d9565b61144a81856112e3565b93508360208202850161145c856112f3565b805f5b8581101561149757848403895281516114788582611417565b94506114838361142a565b925060208a0199505060018101905061145f565b50829750879550505050505092915050565b606082015f8201516114bd5f8501826112ca565b5060208201516114d060208501826112a1565b5060408201516114e360408501826112ca565b50505050565b5f61016083015f8301518482035f8601526115048282611269565b9150506020830151848203602086015261151e8282611269565b915050604083015161153360408601826112a1565b506060830151848203606086015261154b8282611269565b915050608083015161156060808601826112bb565b5060a083015161157360a08601826112ca565b5060c083015161158660c08601826112bb565b5060e083015184820360e086015261159e8282611436565b9150506101008301516115b56101008601826114a9565b508091505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60a082015f8201516115fd5f8501826112ca565b50602082015161161060208501826112a1565b50604082015161162360408501826112bb565b50606082015161163660608501826112bb565b50608082015161164960808501826112a1565b50505050565b5f61165a83836115e9565b60a08301905092915050565b5f602082019050919050565b5f61167c826115c0565b61168681856115ca565b9350611691836115da565b805f5b838110156116c15781516116a8888261164f565b97506116b383611666565b925050600181019050611694565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f82015161170b5f8501826112ca565b50602082015161171e60208501826112ca565b50604082015161173160408501826112ca565b50606082015161174460608501826112ca565b50608082015161175760808501826112bb565b5060a082015161176a60a08501826112a1565b50505050565b5f61177b83836116f7565b60c08301905092915050565b5f602082019050919050565b5f61179d826116ce565b6117a781856116d8565b93506117b2836116e8565b805f5b838110156117e25781516117c98882611770565b97506117d483611787565b9250506001810190506117b5565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f82015161182c5f8501826112ca565b50602082015161183f60208501826112ca565b50604082015161185260408501826112ca565b50606082015161186560608501826112a1565b50608082015161187860808501826112bb565b5060a082015161188b60a08501826112a1565b50505050565b5f61189c8383611818565b60c08301905092915050565b5f602082019050919050565b5f6118be826117ef565b6118c881856117f9565b93506118d383611809565b805f5b838110156119035781516118ea8882611891565b97506118f5836118a8565b9250506001810190506118d6565b5085935050505092915050565b5f61012083015f8301518482035f86015261192b82826114e9565b915050602083015161194060208601826112ca565b50604083015161195360408601826112bb565b50606083015161196660608601826112bb565b50608083015161197960808601826112bb565b5060a083015184820360a08601526119918282611672565b91505060c083015184820360c08601526119ab8282611793565b91505060e083015184820360e08601526119c582826118b4565b9150506101008301518482036101008601526119e18282611269565b9150508091505092915050565b5f6020820190508181035f830152611a068184611910565b905092915050565b5f61012083015f8301518482035f860152611a2982826114e9565b9150506020830151611a3e60208601826112ca565b506040830151611a5160408601826112bb565b506060830151611a6460608601826112bb565b506080830151611a7760808601826112bb565b5060a083015184820360a0860152611a8f8282611672565b91505060c083015184820360c0860152611aa98282611793565b91505060e083015184820360e0860152611ac382826118b4565b915050610100830151848203610100860152611adf8282611269565b9150508091505092915050565b5f8160030b9050919050565b611b0181611aec565b82525050565b5f604083015f8301518482035f860152611b218282611a0e565b9150506020830151611b366020860182611af8565b508091505092915050565b5f6020820190508181035f830152611b598184611b07565b905092915050565b5f80fd5b5f80fd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b611b9f82611259565b810181811067ffffffffffffffff82111715611bbe57611bbd611b69565b5b80604052505050565b5f611bd06110d0565b9050611bdc8282611b96565b919050565b5f67ffffffffffffffff821115611bfb57611bfa611b69565b5b611c0482611259565b9050602081019050919050565b828183375f83830152505050565b5f611c31611c2c84611be1565b611bc7565b905082815260208101848484011115611c4d57611c4c611b65565b5b611c58848285611c11565b509392505050565b5f82601f830112611c7457611c73611b61565b5b8135611c84848260208601611c1f565b91505092915050565b5f8060408385031215611ca357611ca26110d9565b5b5f611cb085828601611127565b925050602083013567ffffffffffffffff811115611cd157611cd06110dd565b5b611cdd85828601611c60565b9150509250929050565b5f819050919050565b611cf981611ce7565b82525050565b5f82825260208201905092915050565b5f611d1982611311565b611d238185611cff565b9350611d3381856020860161124b565b611d3c81611259565b840191505092915050565b5f604082019050611d5a5f830185611cf0565b8181036020830152611d6c8184611d0f565b90509392505050565b5f61018083015f8301518482035f860152611d908282611269565b91505060208301518482036020860152611daa8282611269565b9150506040830151611dbf60408601826112a1565b5060608301518482036060860152611dd78282611269565b9150506080830151611dec60808601826112bb565b5060a0830151611dff60a08601826112ca565b5060c0830151611e1260c08601826112bb565b5060e083015184820360e0860152611e2a8282611436565b915050610100830151611e416101008601826114a9565b50610120830151848203610160860152611e5b828261132b565b9150508091505092915050565b5f61012083015f8301518482035f860152611e838282611d75565b9150506020830151611e9860208601826112ca565b506040830151611eab60408601826112bb565b506060830151611ebe60608601826112bb565b506080830151611ed160808601826112bb565b5060a083015184820360a0860152611ee98282611672565b91505060c083015184820360c0860152611f038282611793565b91505060e083015184820360e0860152611f1d82826118b4565b915050610100830151848203610100860152611f398282611269565b9150508091505092915050565b5f604083015f8301518482035f860152611f608282611e68565b9150506020830151611f756020860182611af8565b508091505092915050565b5f6020820190508181035f830152611f988184611f46565b905092915050565b611fa9816111d2565b8114611fb3575f80fd5b50565b5f81359050611fc481611fa0565b92915050565b5f8060408385031215611fe057611fdf6110d9565b5b5f611fed85828601611127565b9250506020611ffe85828601611fb6565b9150509250929050565b5f60c083015f8301518482035f8601526120228282611a0e565b915050602083015161203760208601826112ca565b50604083015161204a60408601826112a1565b50606083015161205d60608601826112ca565b5060808301518482036080860152612075828261132b565b91505060a083015161208a60a08601826112a1565b508091505092915050565b5f6020820190508181035f8301526120ad8184612008565b905092915050565b5f61012083015f8301518482035f8601526120d08282611d75565b91505060208301516120e560208601826112ca565b5060408301516120f860408601826112bb565b50606083015161210b60608601826112bb565b50608083015161211e60808601826112bb565b5060a083015184820360a08601526121368282611672565b91505060c083015184820360c08601526121508282611793565b91505060e083015184820360e086015261216a82826118b4565b9150506101008301518482036101008601526121868282611269565b9150508091505092915050565b5f6020820190508181035f8301526121ab81846120b5565b905092915050565b5f60c083015f8301518482035f8601526121cd8282611e68565b91505060208301516121e260208601826112ca565b5060408301516121f560408601826112a1565b50606083015161220860608601826112ca565b5060808301518482036080860152612220828261132b565b91505060a083015161223560a08601826112a1565b508091505092915050565b5f6020820190508181035f83015261225881846121b3565b905092915050565b61226981611100565b82525050565b6122788161113b565b82525050565b5f6080820190506122915f830187612260565b61229e6020830186612260565b6122ab6040830185612260565b6122b8606083018461226f565b95945050505050565b5f81905092915050565b5f6122d582611311565b6122df81856122c1565b93506122ef81856020860161124b565b80840191505092915050565b5f61230682846122cb565b915081905092915050565b61231a81611aec565b8114612324575f80fd5b50565b5f8151905061233581612311565b92915050565b5f602082840312156123505761234f6110d9565b5b5f61235d84828501612327565b91505092915050565b5f6040820190506123795f830185612260565b818103602083015261238b8184611d0f565b90509392505050565b61239d816112b0565b82525050565b5f6040820190506123b65f830185612394565b81810360208301526123c88184611d0f565b90509392505050565b5f6020820190506123e45f830184612260565b92915050565b5f80fd5b5f80fd5b5f67ffffffffffffffff82111561240c5761240b611b69565b5b61241582611259565b9050602081019050919050565b5f61243461242f846123f2565b611bc7565b9050828152602081018484840111156124505761244f611b65565b5b61245b84828561124b565b509392505050565b5f82601f83011261247757612476611b61565b5b8151612487848260208601612422565b91505092915050565b5f8151905061249e81611111565b92915050565b6124ad816112b0565b81146124b7575f80fd5b50565b5f815190506124c8816124a4565b92915050565b5f815190506124dc81611fa0565b92915050565b5f67ffffffffffffffff8211156124fc576124fb611b69565b5b602082029050602081019050919050565b5f80fd5b5f8151905061251f81611144565b92915050565b5f61253761253284611be1565b611bc7565b90508281526020810184848401111561255357612552611b65565b5b61255e84828561124b565b509392505050565b5f82601f83011261257a57612579611b61565b5b815161258a848260208601612525565b91505092915050565b5f60a082840312156125a8576125a76123ea565b5b6125b260a0611bc7565b90505f6125c1848285016124ba565b5f8301525060206125d484828501612490565b602083015250604082015167ffffffffffffffff8111156125f8576125f76123ee565b5b61260484828501612566565b604083015250606082015167ffffffffffffffff811115612628576126276123ee565b5b61263484828501612566565b606083015250608061264884828501612490565b60808301525092915050565b5f60408284031215612669576126686123ea565b5b6126736040611bc7565b90505f61268284828501612511565b5f83015250602082015167ffffffffffffffff8111156126a5576126a46123ee565b5b6126b184828501612593565b60208301525092915050565b5f6126cf6126ca846124e2565b611bc7565b905080838252602082019050602084028301858111156126f2576126f161250d565b5b835b8181101561273957805167ffffffffffffffff81111561271757612716611b61565b5b8086016127248982612654565b855260208501945050506020810190506126f4565b5050509392505050565b5f82601f83011261275757612756611b61565b5b81516127678482602086016126bd565b91505092915050565b5f60608284031215612785576127846123ea565b5b61278f6060611bc7565b90505f61279e848285016124ce565b5f8301525060206127b184828501612490565b60208301525060406127c5848285016124ce565b60408301525092915050565b5f61016082840312156127e7576127e66123ea565b5b6127f2610120611bc7565b90505f82015167ffffffffffffffff811115612811576128106123ee565b5b61281d84828501612463565b5f83015250602082015167ffffffffffffffff8111156128405761283f6123ee565b5b61284c84828501612463565b602083015250604061286084828501612490565b604083015250606082015167ffffffffffffffff811115612884576128836123ee565b5b61289084828501612463565b60608301525060806128a4848285016124ba565b60808301525060a06128b8848285016124ce565b60a08301525060c06128cc848285016124ba565b60c08301525060e082015167ffffffffffffffff8111156128f0576128ef6123ee565b5b6128fc84828501612743565b60e08301525061010061291184828501612770565b6101008301525092915050565b5f67ffffffffffffffff82111561293857612937611b69565b5b602082029050602081019050919050565b5f60a0828403121561295e5761295d6123ea565b5b61296860a0611bc7565b90505f612977848285016124ce565b5f83015250602061298a84828501612490565b602083015250604061299e848285016124ba565b60408301525060606129b2848285016124ba565b60608301525060806129c684828501612490565b60808301525092915050565b5f6129e46129df8461291e565b611bc7565b90508083825260208201905060a08402830185811115612a0757612a0661250d565b5b835b81811015612a305780612a1c8882612949565b84526020840193505060a081019050612a09565b5050509392505050565b5f82601f830112612a4e57612a4d611b61565b5b8151612a5e8482602086016129d2565b91505092915050565b5f67ffffffffffffffff821115612a8157612a80611b69565b5b602082029050602081019050919050565b5f60c08284031215612aa757612aa66123ea565b5b612ab160c0611bc7565b90505f612ac0848285016124ce565b5f830152506020612ad3848285016124ce565b6020830152506040612ae7848285016124ce565b6040830152506060612afb848285016124ce565b6060830152506080612b0f848285016124ba565b60808301525060a0612b2384828501612490565b60a08301525092915050565b5f612b41612b3c84612a67565b611bc7565b90508083825260208201905060c08402830185811115612b6457612b6361250d565b5b835b81811015612b8d5780612b798882612a92565b84526020840193505060c081019050612b66565b5050509392505050565b5f82601f830112612bab57612baa611b61565b5b8151612bbb848260208601612b2f565b91505092915050565b5f67ffffffffffffffff821115612bde57612bdd611b69565b5b602082029050602081019050919050565b5f60c08284031215612c0457612c036123ea565b5b612c0e60c0611bc7565b90505f612c1d848285016124ce565b5f830152506020612c30848285016124ce565b6020830152506040612c44848285016124ce565b6040830152506060612c5884828501612490565b6060830152506080612c6c848285016124ba565b60808301525060a0612c8084828501612490565b60a08301525092915050565b5f612c9e612c9984612bc4565b611bc7565b90508083825260208201905060c08402830185811115612cc157612cc061250d565b5b835b81811015612cea5780612cd68882612bef565b84526020840193505060c081019050612cc3565b5050509392505050565b5f82601f830112612d0857612d07611b61565b5b8151612d18848260208601612c8c565b91505092915050565b5f6101208284031215612d3757612d366123ea565b5b612d42610120611bc7565b90505f82015167ffffffffffffffff811115612d6157612d606123ee565b5b612d6d848285016127d1565b5f830152506020612d80848285016124ce565b6020830152506040612d94848285016124ba565b6040830152506060612da8848285016124ba565b6060830152506080612dbc848285016124ba565b60808301525060a082015167ffffffffffffffff811115612de057612ddf6123ee565b5b612dec84828501612a3a565b60a08301525060c082015167ffffffffffffffff811115612e1057612e0f6123ee565b5b612e1c84828501612b97565b60c08301525060e082015167ffffffffffffffff811115612e4057612e3f6123ee565b5b612e4c84828501612cf4565b60e08301525061010082015167ffffffffffffffff811115612e7157612e706123ee565b5b612e7d84828501612463565b6101008301525092915050565b5f8060408385031215612ea057612e9f6110d9565b5b5f612ead85828601612327565b925050602083015167ffffffffffffffff811115612ece57612ecd6110dd565b5b612eda85828601612d21565b9150509250929050565b5f60408284031215612ef957612ef86123ea565b5b612f036040611bc7565b90505f82015167ffffffffffffffff811115612f2257612f216123ee565b5b612f2e84828501612d21565b5f830152506020612f4184828501612327565b60208301525092915050565b5f8060408385031215612f6357612f626110d9565b5b5f612f7085828601612327565b925050602083015167ffffffffffffffff811115612f9157612f906110dd565b5b612f9d85828601612ee4565b9150509250929050565b5f6101808284031215612fbd57612fbc6123ea565b5b612fc8610140611bc7565b90505f82015167ffffffffffffffff811115612fe757612fe66123ee565b5b612ff384828501612463565b5f83015250602082015167ffffffffffffffff811115613016576130156123ee565b5b61302284828501612463565b602083015250604061303684828501612490565b604083015250606082015167ffffffffffffffff81111561305a576130596123ee565b5b61306684828501612463565b606083015250608061307a848285016124ba565b60808301525060a061308e848285016124ce565b60a08301525060c06130a2848285016124ba565b60c08301525060e082015167ffffffffffffffff8111156130c6576130c56123ee565b5b6130d284828501612743565b60e0830152506101006130e784828501612770565b6101008301525061016082015167ffffffffffffffff81111561310d5761310c6123ee565b5b61311984828501612566565b6101208301525092915050565b5f610120828403121561313c5761313b6123ea565b5b613147610120611bc7565b90505f82015167ffffffffffffffff811115613166576131656123ee565b5b61317284828501612fa7565b5f830152506020613185848285016124ce565b6020830152506040613199848285016124ba565b60408301525060606131ad848285016124ba565b60608301525060806131c1848285016124ba565b60808301525060a082015167ffffffffffffffff8111156131e5576131e46123ee565b5b6131f184828501612a3a565b60a08301525060c082015167ffffffffffffffff811115613215576132146123ee565b5b61322184828501612b97565b60c08301525060e082015167ffffffffffffffff811115613245576132446123ee565b5b61325184828501612cf4565b60e08301525061010082015167ffffffffffffffff811115613276576132756123ee565b5b61328284828501612463565b6101008301525092915050565b5f604082840312156132a4576132a36123ea565b5b6132ae6040611bc7565b90505f82015167ffffffffffffffff8111156132cd576132cc6123ee565b5b6132d984828501613126565b5f8301525060206132ec84828501612327565b60208301525092915050565b5f806040838503121561330e5761330d6110d9565b5b5f61331b85828601612327565b925050602083015167ffffffffffffffff81111561333c5761333b6110dd565b5b6133488582860161328f565b9150509250929050565b5f6040820190506133655f830185612260565b61337260208301846111de565b9392505050565b5f60c0828403121561338e5761338d6123ea565b5b61339860c0611bc7565b90505f82015167ffffffffffffffff8111156133b7576133b66123ee565b5b6133c384828501612d21565b5f8301525060206133d6848285016124ce565b60208301525060406133ea84828501612490565b60408301525060606133fe848285016124ce565b606083015250608082015167ffffffffffffffff811115613422576134216123ee565b5b61342e84828501612566565b60808301525060a061344284828501612490565b60a08301525092915050565b5f8060408385031215613464576134636110d9565b5b5f61347185828601612327565b925050602083015167ffffffffffffffff811115613492576134916110dd565b5b61349e85828601613379565b9150509250929050565b5f80604083850312156134be576134bd6110d9565b5b5f6134cb85828601612327565b925050602083015167ffffffffffffffff8111156134ec576134eb6110dd565b5b6134f885828601613126565b9150509250929050565b5f60c08284031215613517576135166123ea565b5b61352160c0611bc7565b90505f82015167ffffffffffffffff8111156135405761353f6123ee565b5b61354c84828501613126565b5f83015250602061355f848285016124ce565b602083015250604061357384828501612490565b6040830152506060613587848285016124ce565b606083015250608082015167ffffffffffffffff8111156135ab576135aa6123ee565b5b6135b784828501612566565b60808301525060a06135cb84828501612490565b60a08301525092915050565b5f80604083850312156135ed576135ec6110d9565b5b5f6135fa85828601612327565b925050602083015167ffffffffffffffff81111561361b5761361a6110dd565b5b61362785828601613502565b915050925092905056fea2646970667358221220033c7c6b30f2a93c121a8bb4e95b3f9d62b77f2c6dad222bd1a5be9e41d5638764736f6c634300081a0033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.json b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.json new file mode 100644 index 000000000000..e649a0b8e364 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.json @@ -0,0 +1,1791 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "CallResponseEvent", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getInformationForFungibleToken", + "outputs": [ + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.HederaToken", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfo", + "name": "tokenInfo", + "type": "tuple" + }, + { + "internalType": "int32", + "name": "decimals", + "type": "int32" + } + ], + "internalType": "struct IHederaTokenService.FungibleTokenInfo", + "name": "fungibleTokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getInformationForFungibleTokenV2", + "outputs": [ + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes" + } + ], + "internalType": "struct IHederaTokenService.HederaTokenV2", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfoV2", + "name": "tokenInfo", + "type": "tuple" + }, + { + "internalType": "int32", + "name": "decimals", + "type": "int32" + } + ], + "internalType": "struct IHederaTokenService.FungibleTokenInfoV2", + "name": "fungibleTokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "serialNumber", + "type": "int64" + } + ], + "name": "getInformationForNonFungibleToken", + "outputs": [ + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.HederaToken", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfo", + "name": "tokenInfo", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "serialNumber", + "type": "int64" + }, + { + "internalType": "address", + "name": "ownerId", + "type": "address" + }, + { + "internalType": "int64", + "name": "creationTime", + "type": "int64" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes" + }, + { + "internalType": "address", + "name": "spenderId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.NonFungibleTokenInfo", + "name": "nonFungibleTokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "int64", + "name": "serialNumber", + "type": "int64" + } + ], + "name": "getInformationForNonFungibleTokenV2", + "outputs": [ + { + "components": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes" + } + ], + "internalType": "struct IHederaTokenService.HederaTokenV2", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfoV2", + "name": "tokenInfo", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "serialNumber", + "type": "int64" + }, + { + "internalType": "address", + "name": "ownerId", + "type": "address" + }, + { + "internalType": "int64", + "name": "creationTime", + "type": "int64" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes" + }, + { + "internalType": "address", + "name": "spenderId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.NonFungibleTokenInfoV2", + "name": "nonFungibleTokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getInformationForToken", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.HederaToken", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfo", + "name": "tokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getInformationForTokenV2", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "address", + "name": "treasury", + "type": "address" + }, + { + "internalType": "string", + "name": "memo", + "type": "string" + }, + { + "internalType": "bool", + "name": "tokenSupplyType", + "type": "bool" + }, + { + "internalType": "int64", + "name": "maxSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "freezeDefault", + "type": "bool" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "keyType", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bool", + "name": "inheritAccountKey", + "type": "bool" + }, + { + "internalType": "address", + "name": "contractId", + "type": "address" + }, + { + "internalType": "bytes", + "name": "ed25519", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "ECDSA_secp256k1", + "type": "bytes" + }, + { + "internalType": "address", + "name": "delegatableContractId", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.KeyValue", + "name": "key", + "type": "tuple" + } + ], + "internalType": "struct IHederaTokenService.TokenKey[]", + "name": "tokenKeys", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "second", + "type": "int64" + }, + { + "internalType": "address", + "name": "autoRenewAccount", + "type": "address" + }, + { + "internalType": "int64", + "name": "autoRenewPeriod", + "type": "int64" + } + ], + "internalType": "struct IHederaTokenService.Expiry", + "name": "expiry", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "metadata", + "type": "bytes" + } + ], + "internalType": "struct IHederaTokenService.HederaTokenV2", + "name": "token", + "type": "tuple" + }, + { + "internalType": "int64", + "name": "totalSupply", + "type": "int64" + }, + { + "internalType": "bool", + "name": "deleted", + "type": "bool" + }, + { + "internalType": "bool", + "name": "defaultKycStatus", + "type": "bool" + }, + { + "internalType": "bool", + "name": "pauseStatus", + "type": "bool" + }, + { + "components": [ + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "bool", + "name": "useCurrentTokenForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FixedFee[]", + "name": "fixedFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "minimumAmount", + "type": "int64" + }, + { + "internalType": "int64", + "name": "maximumAmount", + "type": "int64" + }, + { + "internalType": "bool", + "name": "netOfTransfers", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.FractionalFee[]", + "name": "fractionalFees", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "int64", + "name": "numerator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "denominator", + "type": "int64" + }, + { + "internalType": "int64", + "name": "amount", + "type": "int64" + }, + { + "internalType": "address", + "name": "tokenId", + "type": "address" + }, + { + "internalType": "bool", + "name": "useHbarsForPayment", + "type": "bool" + }, + { + "internalType": "address", + "name": "feeCollector", + "type": "address" + } + ], + "internalType": "struct IHederaTokenService.RoyaltyFee[]", + "name": "royaltyFees", + "type": "tuple[]" + }, + { + "internalType": "string", + "name": "ledgerId", + "type": "string" + } + ], + "internalType": "struct IHederaTokenService.TokenInfoV2", + "name": "tokenInfo", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bytes", + "name": "encodedFunctionSelector", + "type": "bytes" + } + ], + "name": "redirectForToken", + "outputs": [ + { + "internalType": "int256", + "name": "responseCode", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "serialNumber", + "type": "uint256" + } + ], + "name": "transferFromNFT", + "outputs": [ + { + "internalType": "int64", + "name": "responseCode", + "type": "int64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.sol new file mode 100644 index 000000000000..4f2984300a43 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/TokenInfo/TokenInfo.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.5.0 <0.9.0; +pragma experimental ABIEncoderV2; + +import "./HederaResponseCodes.sol"; +import "./HederaTokenService.sol"; + +contract TokenInfo is HederaTokenService { + + function getInformationForToken(address token) external returns (IHederaTokenService.TokenInfo memory tokenInfo) { + (int responseCode, IHederaTokenService.TokenInfo memory retrievedTokenInfo) = HederaTokenService.getTokenInfo(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + tokenInfo = retrievedTokenInfo; + } + + function getInformationForFungibleToken(address token) external returns (IHederaTokenService.FungibleTokenInfo memory fungibleTokenInfo) { + (int responseCode, IHederaTokenService.FungibleTokenInfo memory retrievedTokenInfo) = HederaTokenService.getFungibleTokenInfo(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + fungibleTokenInfo = retrievedTokenInfo; + } + + function getInformationForNonFungibleToken(address token, int64 serialNumber) external returns (IHederaTokenService.NonFungibleTokenInfo memory nonFungibleTokenInfo) { + (int responseCode, IHederaTokenService.NonFungibleTokenInfo memory retrievedTokenInfo) = HederaTokenService.getNonFungibleTokenInfo(token, serialNumber); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + nonFungibleTokenInfo = retrievedTokenInfo; + } + + function getInformationForTokenV2(address token) external returns (IHederaTokenService.TokenInfoV2 memory tokenInfo) { + (int responseCode, IHederaTokenService.TokenInfoV2 memory retrievedTokenInfo) = HederaTokenService.getTokenInfoV2(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + tokenInfo = retrievedTokenInfo; + } + + function getInformationForFungibleTokenV2(address token) external returns (IHederaTokenService.FungibleTokenInfoV2 memory fungibleTokenInfo) { + (int responseCode, IHederaTokenService.FungibleTokenInfoV2 memory retrievedTokenInfo) = HederaTokenService.getFungibleTokenInfoV2(token); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + fungibleTokenInfo = retrievedTokenInfo; + } + + function getInformationForNonFungibleTokenV2(address token, int64 serialNumber) external returns (IHederaTokenService.NonFungibleTokenInfoV2 memory nonFungibleTokenInfo) { + (int responseCode, IHederaTokenService.NonFungibleTokenInfoV2 memory retrievedTokenInfo) = HederaTokenService.getNonFungibleTokenInfoV2(token, serialNumber); + + if (responseCode != HederaResponseCodes.SUCCESS) { + revert (); + } + + nonFungibleTokenInfo = retrievedTokenInfo; + } + + +} \ No newline at end of file From 4eb9c5b5f03e0d3cbaad7193c0f54d036c40ac2b Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Fri, 13 Sep 2024 10:31:14 -0500 Subject: [PATCH 13/16] fix: freeze time reset check (#15429) Signed-off-by: Michael Tinker --- .../node/app/records/impl/BlockRecordManagerImpl.java | 2 +- .../bdd/suites/regression/system/LifecycleTest.java | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java index 170956e57f42..f7523a1841b9 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/records/impl/BlockRecordManagerImpl.java @@ -187,7 +187,7 @@ public boolean startUserTransaction(@NonNull final Instant consensusTime, @NonNu // Also check to see if this is the first transaction we're handling after a freeze restart. If so, we also // start a new block. final var isFirstTransactionAfterFreezeRestart = platformState.freezeTime() != null - && platformState.freezeTimeOrThrow().equals(platformState.freezeTime()); + && platformState.freezeTimeOrThrow().equals(platformState.lastFrozenTime()); if (isFirstTransactionAfterFreezeRestart) { new WritablePlatformStateStore(state.getWritableStates(PlatformStateService.NAME)).setFreezeTime(null); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/LifecycleTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/LifecycleTest.java index 08cf5a1c80f3..69c6bb11a785 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/LifecycleTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/regression/system/LifecycleTest.java @@ -18,6 +18,8 @@ import static com.hedera.services.bdd.junit.hedera.MarkerFile.EXEC_IMMEDIATE_MF; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getVersionInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.blockingOrder; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.buildUpgradeZipFrom; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doAdhoc; @@ -33,6 +35,7 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForFrozenNetwork; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.waitForMf; import static com.hedera.services.bdd.spec.utilops.upgrade.BuildUpgradeZipOp.FAKE_UPGRADE_ZIP_LOC; +import static com.hedera.services.bdd.suites.HapiSuite.FUNDING; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.DEFAULT_UPGRADE_FILE_ID; import static com.hedera.services.bdd.suites.freeze.CommonUpgradeResources.FAKE_ASSETS_LOC; @@ -132,7 +135,10 @@ default SpecOperation upgradeToNextConfigVersion() { */ default SpecOperation restartAtNextConfigVersion() { return blockingOrder( - freezeOnly().startingIn(5).seconds().payingWith(GENESIS), + freezeOnly().startingIn(5).seconds().payingWith(GENESIS).deferStatusResolution(), + // Immediately submit a transaction in the same round to ensure freeze time is only + // reset when last frozen time matches it (i.e., in a post-upgrade transaction) + cryptoTransfer(tinyBarsFromTo(GENESIS, FUNDING, 1)), confirmFreezeAndShutdown(), sourcing(() -> FakeNmt.restartNetwork(CURRENT_CONFIG_VERSION.incrementAndGet())), waitForActiveNetwork(RESTART_TIMEOUT)); From 68fd76862cff500cb2bf77e371fdad3ef12c1dc9 Mon Sep 17 00:00:00 2001 From: Edward Wertz <123979964+edward-swirldslabs@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:25:37 -0500 Subject: [PATCH 14/16] chore: correct the variable name in roster.proto (#15465) Signed-off-by: Edward Wertz --- .../services/state/roster/roster.proto | 2 +- .../platform/roster/RosterRetriever.java | 2 +- .../platform/roster/RosterValidator.java | 2 +- .../system/address/AddressBookUtils.java | 2 +- .../platform/roster/RosterRetrieverTests.java | 2 +- .../platform/roster/RosterValidatorTests.java | 20 +++++++++---------- .../util/AddressBookNetworkUtilsTests.java | 18 ++++++++--------- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/hapi/hedera-protobufs/services/state/roster/roster.proto b/hapi/hedera-protobufs/services/state/roster/roster.proto index 3d822062643b..d6b7f6ab1af7 100644 --- a/hapi/hedera-protobufs/services/state/roster/roster.proto +++ b/hapi/hedera-protobufs/services/state/roster/roster.proto @@ -37,7 +37,7 @@ message Roster { * This list SHALL contain roster entries in natural order of ascending node ids. * This list SHALL NOT be empty.
      */ - repeated RosterEntry rosters = 1; + repeated RosterEntry roster_entries = 1; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterRetriever.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterRetriever.java index bbf288a20a3a..dc8851fa5b1e 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterRetriever.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterRetriever.java @@ -147,7 +147,7 @@ public static long getRound(@NonNull final State state) { @NonNull public static Roster buildRoster(@NonNull final AddressBook addressBook) { return Roster.newBuilder() - .rosters(addressBook.getNodeIdSet().stream() + .rosterEntries(addressBook.getNodeIdSet().stream() .map(addressBook::getAddress) .map(address -> { try { diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterValidator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterValidator.java index 8b88b95f13d8..1d931014dac9 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterValidator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterValidator.java @@ -41,7 +41,7 @@ public static void validate(@NonNull final Roster roster) { throw new InvalidRosterException("roster is null"); } - final List rosterEntries = roster.rosters(); + final List rosterEntries = roster.rosterEntries(); if (rosterEntries.isEmpty()) { throw new InvalidRosterException("roster is empty"); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java index 4cf817fecc50..1d4be49cfff3 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/system/address/AddressBookUtils.java @@ -343,7 +343,7 @@ public static Roster createRoster(@NonNull final AddressBook bootstrapAddressBoo final RosterEntry rosterEntry = AddressBookUtils.toRosterEntry(address, nodeId); rosterEntries.add(rosterEntry); } - return Roster.newBuilder().rosters(rosterEntries).build(); + return Roster.newBuilder().rosterEntries(rosterEntries).build(); } /** diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterRetrieverTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterRetrieverTests.java index 7a35d468968f..51671c0705b6 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterRetrieverTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterRetrieverTests.java @@ -108,7 +108,7 @@ public class RosterRetrieverTests { .build(); private static final Roster ROSTER_FROM_ADDRESS_BOOK = Roster.newBuilder() - .rosters(List.of( + .rosterEntries(List.of( RosterEntry.newBuilder() .nodeId(1L) .weight(1L) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java index b5d9da5b7813..38edd21a1e64 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java @@ -43,7 +43,7 @@ void zeroWeightTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(0) @@ -83,7 +83,7 @@ void negativeWeightTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(0) @@ -123,7 +123,7 @@ void duplicateNodeIdTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -163,7 +163,7 @@ void invalidGossipCertTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -203,7 +203,7 @@ void emptyGossipEndpointsTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -239,7 +239,7 @@ void zeroPortTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -281,7 +281,7 @@ void domainAndIpTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -324,7 +324,7 @@ void wrongNodeIdOrderTest() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -364,7 +364,7 @@ void invalidIPv4Test() { final Exception ex = assertThrows( InvalidRosterException.class, () -> RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) @@ -404,7 +404,7 @@ void invalidIPv4Test() { @Test void validTest() { RosterValidator.validate(Roster.newBuilder() - .rosters( + .rosterEntries( RosterEntry.newBuilder() .nodeId(1) .weight(1) diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java index 89c4fa4f7ce9..5d5e6ae72e5a 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/AddressBookNetworkUtilsTests.java @@ -110,9 +110,9 @@ void testCreateRosterFromNonEmptyAddressBook() { final Roster roster = AddressBookUtils.createRoster(addressBook); assertNotNull(roster); - assertEquals(2, roster.rosters().size()); - assertEquals(1L, roster.rosters().getFirst().nodeId()); - assertEquals(2L, roster.rosters().getLast().nodeId()); + assertEquals(2, roster.rosterEntries().size()); + assertEquals(1L, roster.rosterEntries().getFirst().nodeId()); + assertEquals(2L, roster.rosterEntries().getLast().nodeId()); } @Test @@ -136,7 +136,7 @@ void testCreateRosterFromEmptyAddressBook() { final Roster roster = AddressBookUtils.createRoster(addressBook); assertNotNull(roster); - assertTrue(roster.rosters().isEmpty()); + assertTrue(roster.rosterEntries().isEmpty()); } @Test @@ -148,7 +148,7 @@ void testToRosterEntryWithCertificateEncodingException() throws CertificateEncod final AddressBook addressBook = new AddressBook(List.of(address)); final Roster roster = AddressBookUtils.createRoster(addressBook); - assertEquals(Bytes.EMPTY, roster.rosters().getFirst().gossipCaCertificate()); + assertEquals(Bytes.EMPTY, roster.rosterEntries().getFirst().gossipCaCertificate()); } @Test @@ -157,10 +157,10 @@ void testToRosterEntryWithExternalHostname() { final AddressBook addressBook = new AddressBook(List.of(address)); final Roster roster = AddressBookUtils.createRoster(addressBook); - assertEquals(1, roster.rosters().size()); + assertEquals(1, roster.rosterEntries().size()); assertEquals( "hostnameExternal", - roster.rosters().getFirst().gossipEndpoint().getFirst().domainName()); + roster.rosterEntries().getFirst().gossipEndpoint().getFirst().domainName()); } @Test @@ -169,10 +169,10 @@ void testToRosterEntryWithInternalHostname() { final AddressBook addressBook = new AddressBook(List.of(address)); final Roster roster = AddressBookUtils.createRoster(addressBook); - assertEquals(1, roster.rosters().size()); + assertEquals(1, roster.rosterEntries().size()); assertEquals( "hostnameInternal", - roster.rosters().getFirst().gossipEndpoint().getFirst().domainName()); + roster.rosterEntries().getFirst().gossipEndpoint().getFirst().domainName()); } @Test From 1a31fa668fd9b0b2423e03cae817c2805ed440bb Mon Sep 17 00:00:00 2001 From: Stanimir Stoyanov Date: Fri, 13 Sep 2024 22:43:27 +0300 Subject: [PATCH 15/16] fix: Precision loss for gas calculation of HTS system contracts v2 (#15446) Signed-off-by: Stanimir Stoyanov --- .../node/config/data/ContractsConfig.java | 4 + .../contract/impl/exec/QueryModule.java | 6 +- .../contract/impl/exec/TransactionModule.java | 9 +- .../impl/exec/gas/CustomGasCalculator.java | 10 +- .../contract/impl/exec/gas/DispatchType.java | 1 + .../exec/gas/SystemContractGasCalculator.java | 163 +++++-- .../contract/impl/exec/gas/TinybarValues.java | 95 +++- .../exec/scope/HandleHederaOperations.java | 10 +- .../hts/create/ClassicCreatesCall.java | 2 +- .../hts/transfer/ClassicTransfersCall.java | 46 +- .../contract/impl/utils/ConversionUtils.java | 5 + .../impl/test/exec/TransactionModuleTest.java | 29 +- .../gas/SystemContractGasCalculatorTest.java | 4 +- .../impl/test/exec/gas/TinybarValuesTest.java | 14 +- .../scope/HandleHederaOperationsTest.java | 4 +- .../hts/create/ClassicCreatesCallTest.java | 2 +- .../transfer/ClassicTransfersGasCalcTest.java | 22 +- .../bdd/spec/dsl/entities/SpecAccount.java | 45 ++ .../transactions/CallContractOperation.java | 7 + .../transactions/TransferTokensOperation.java | 70 ++- .../contract/hapi/ContractDeleteSuite.java | 3 +- .../ContractBurnHTSV2SecurityModelSuite.java | 4 +- .../precompile/RedirectPrecompileSuite.java | 4 +- .../precompile/TokenInfoHTSSuite.java | 1 + .../WipeTokenAccountPrecompileSuite.java | 4 +- .../token/GasCalculationIntegrityTest.java | 450 ++++++++++++++++++ .../bdd/suites/ethereum/NonceSuite.java | 2 +- .../NumericContractComplex.bin | 2 +- .../NumericContractComplex.sol | 4 +- 29 files changed, 894 insertions(+), 128 deletions(-) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/token/GasCalculationIntegrityTest.java diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java index 63f38e8c1ded..e1d3b8580d38 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/ContractsConfig.java @@ -85,6 +85,10 @@ public record ContractsConfig( boolean systemContractUpdateCustomFeesEnabled, @ConfigProperty(value = "systemContract.tokenInfo.v2.enabled", defaultValue = "false") @NetworkProperty boolean systemContractTokenInfoV2Enabled, + @ConfigProperty(value = "systemContract.precisionLossFixForGas.enabled", defaultValue = "true") @NetworkProperty + boolean isGasPrecisionLossFixEnabled, + @ConfigProperty(value = "systemContract.canonicalViewGas.enabled", defaultValue = "true") @NetworkProperty + boolean isCanonicalViewGasEnabled, @ConfigProperty(value = "evm.version.dynamic", defaultValue = "false") @NetworkProperty boolean evmVersionDynamic, @ConfigProperty(value = "evm.allowCallsToNonContractAccounts", defaultValue = "true") @NetworkProperty diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java index 75b1584546e4..4228c245b3df 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/QueryModule.java @@ -39,6 +39,7 @@ import com.hedera.node.app.service.contract.impl.state.ProxyWorldUpdater; import com.hedera.node.app.service.contract.impl.state.ScopedEvmFrameStateFactory; import com.hedera.node.app.spi.workflows.QueryContext; +import com.hedera.node.config.data.ContractsConfig; import com.hedera.node.config.data.HederaConfig; import dagger.Binds; import dagger.Module; @@ -57,8 +58,9 @@ static HederaConfig provideHederaConfig(@NonNull final QueryContext context) { @Provides @QueryScope - static TinybarValues provideTinybarValues(@NonNull final ExchangeRate exchangeRate) { - return TinybarValues.forQueryWith(exchangeRate); + static TinybarValues provideTinybarValues( + @NonNull final ExchangeRate exchangeRate, @NonNull final QueryContext context) { + return TinybarValues.forQueryWith(exchangeRate, context.configuration().getConfigData(ContractsConfig.class)); } @Provides diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java index b2abc3ae9631..2d82e37a2fc1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/TransactionModule.java @@ -91,8 +91,13 @@ static FeatureFlags provideFeatureFlags(@NonNull final TransactionProcessor proc static TinybarValues provideTinybarValues( @TopLevelResourcePrices @NonNull final FunctionalityResourcePrices topLevelResourcePrices, @ChildTransactionResourcePrices @NonNull final FunctionalityResourcePrices childTransactionResourcePrices, - @NonNull final ExchangeRate exchangeRate) { - return TinybarValues.forTransactionWith(exchangeRate, topLevelResourcePrices, childTransactionResourcePrices); + @NonNull final ExchangeRate exchangeRate, + @NonNull final HandleContext context) { + return TinybarValues.forTransactionWith( + exchangeRate, + context.configuration().getConfigData(ContractsConfig.class), + topLevelResourcePrices, + childTransactionResourcePrices); } @Provides diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCalculator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCalculator.java index 02496e95b9fd..58f9e27f7c28 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCalculator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/CustomGasCalculator.java @@ -79,8 +79,8 @@ public long logOperationGasCost( final var hevmGasCost = gasCostOfStoring( logSize(numTopics, dataLength), lifetime, - tinybarValues.topLevelTinybarRbhPrice(), - tinybarValues.topLevelTinybarGasPrice()); + tinybarValues.topLevelTinycentRbhPrice(), + tinybarValues.topLevelTinycentGasPrice()); return Math.max(evmGasCost, hevmGasCost); } @@ -104,15 +104,15 @@ public long getEdSignatureVerificationSystemContractGasCost() { /** * Logically, would return the gas cost of storing the given number of bytes for the given number of seconds, - * given the relative prices of a byte-hour and a gas unit in tinybar. + * given the relative prices of a byte-hour and a gas unit in tinycent. * *

      But for differential testing, ignores the {@code numBytes} and returns the gas cost of storing just a * single byte for the given number of seconds. * * @param numBytes ignored * @param lifetime the number of seconds to store a single byte - * @param rbhPrice the price of a byte-hour in tinybar - * @param gasPrice the price of a gas unit in tinybar + * @param rbhPrice the price of a byte-hour in tinycent + * @param gasPrice the price of a gas unit in tinycent * @return the gas cost of storing a single byte for the given number of seconds */ private static long gasCostOfStoring( diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java index 07c7bc61df4e..373fd6d9d8e5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/DispatchType.java @@ -57,6 +57,7 @@ public enum DispatchType { WIPE_NFT(HederaFunctionality.TOKEN_ACCOUNT_WIPE, TOKEN_NON_FUNGIBLE_UNIQUE), UPDATE(HederaFunctionality.TOKEN_UPDATE, DEFAULT), UTIL_PRNG(HederaFunctionality.UTIL_PRNG, DEFAULT), + TOKEN_INFO(HederaFunctionality.TOKEN_GET_INFO, DEFAULT), UPDATE_TOKEN_CUSTOM_FEES(HederaFunctionality.TOKEN_FEE_SCHEDULE_UPDATE, DEFAULT); private final HederaFunctionality functionality; diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java index 45cb5fea8bf1..a14475a5b40e 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/SystemContractGasCalculator.java @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.exec.gas; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.FEE_SCHEDULE_UNITS_PER_TINYCENT; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; @@ -29,6 +30,12 @@ public class SystemContractGasCalculator { private static final long FIXED_VIEW_GAS_COST = 100L; + // This represents the predefined gas price which is $0.000_000_0852 per unit of gas. + // 852_000 = $0.000_000_0852 * 100(cents per dollars) * 100_000_000 (tiny cents per cents) * 1000 (Fee schedule + // units + // per tiny cents). For more info -> https://hedera.com/blog/rolling-smart-contract-hedera-api-fees-into-gas-fees + private static final long FIXED_TINY_CENT_GAS_PRICE_COST = 852_000L; + private final TinybarValues tinybarValues; private final CanonicalDispatchPrices dispatchPrices; private final ToLongBiFunction feeCalculator; @@ -57,68 +64,85 @@ public long gasRequirement( @NonNull final AccountID payer) { requireNonNull(body); requireNonNull(dispatchType); - return gasRequirement(body, payer, canonicalPriceInTinybars(dispatchType)); + requireNonNull(payer); + // isGasPrecisionLossFixEnabled is a temporary feature flag that will be removed in the future. + if (!tinybarValues.isGasPrecisionLossFixEnabled()) { + return gasRequirementOldWithPrecisionLoss(body, payer, canonicalPriceInTinybars(dispatchType)); + } + return gasRequirementWithTinycents(body, payer, dispatchPrices.canonicalPriceInTinycents(dispatchType)); } /** - * Given a transaction body whose dispatch gas requirement must be at least equivalent to a given minimum - * amount of tinybars, returns the gas requirement for the transaction to be dispatched. - * - * @param body the transaction body to be dispatched + * Compares the canonical price and the feeCalculator's calculated price and uses the maximum of the two to + * calculate the gas requirement and returns it. + * @param body the transaction body * @param payer the payer of the transaction - * @param minimumPriceInTinybars the minimum equivalent tinybar cost of the dispatch - * @return the gas requirement for the transaction to be dispatched + * @param minimumPriceInTinycents the minimum price in tiny cents + * @return the gas requirement for the transaction */ - public long gasRequirement( - @NonNull final TransactionBody body, @NonNull final AccountID payer, final long minimumPriceInTinybars) { - final var nominalPriceInTinybars = feeCalculator.applyAsLong(body, payer); - final var priceInTinybars = Math.max(minimumPriceInTinybars, nominalPriceInTinybars); - return asGasRequirement(priceInTinybars); + public long gasRequirementWithTinycents( + @NonNull final TransactionBody body, @NonNull final AccountID payer, final long minimumPriceInTinycents) { + // If not enabled, make the calculation using the old method. + if (!tinybarValues.isGasPrecisionLossFixEnabled()) { + return gasRequirementOldWithPrecisionLoss(body, payer, minimumPriceInTinycents); + } + final var computedPriceInTinybars = feeCalculator.applyAsLong(body, payer); + final var priceInTinycents = + Math.max(minimumPriceInTinycents, tinybarValues.asTinycents(computedPriceInTinybars)); + // For the rare cases where computedPrice > minimumPriceInTinycents: + // Precision loss may occur as we convert between tinyBars and tinycents, but it is typically negligible. + // The minimal computed price is > 1e6 tinycents, ensuring enough precision. + // In most cases, the gas difference is zero. + // In scenarios where we compare significant price fluctuations (200x, 100x), the gas difference should still be + // unlikely to exceed 0 gas. + return gasRequirementFromTinycents(priceInTinycents, tinybarValues.childTransactionTinycentGasPrice()); } /** - * Given a transaction body whose, returns the gas requirement for the transaction to be - * dispatched using the gas price of the top-level HAPI operation and no markup. + * Returns the gas price for the top-level HAPI operation. * - * @param body the transaction body to be dispatched - * @param payer the payer of the transaction - * @return the gas requirement for the transaction to be dispatched + * @return the gas price for the top-level HAPI operation in tinyBars. */ - public long topLevelGasRequirement(@NonNull final TransactionBody body, @NonNull final AccountID payer) { - return feeCalculator.applyAsLong(body, payer) / tinybarValues.topLevelTinybarGasPrice(); + public long topLevelGasPriceInTinyBars() { + return tinybarValues.topLevelTinybarGasPriceFullPrecision(); } /** - * Returns the gas price for the top-level HAPI operation. + * Estimates the gas requirement for a view operation. + * The minimum gas requirement is 100 gas. + * For all view operations, the gas requirement is determined using the canonical gas value + * for the TOKEN_INFO dispatch type, as specified in the canonical-prices.json. + * The TOKEN_INFO operation is representative of view operations. * - * @return the gas price for the top-level HAPI operation + * @return the gas requirement for a view operation */ - public long topLevelGasPrice() { - return tinybarValues.topLevelTinybarGasPrice(); + public long viewGasRequirement() { + // isCanonicalViewGasEnabled is a temporary feature flag that will be removed in the future. + if (!tinybarValues.isCanonicalViewGasEnabled()) { + return FIXED_VIEW_GAS_COST; + } + final var gasRequirement = gasRequirementFromTinycents( + dispatchPrices.canonicalPriceInTinycents(DispatchType.TOKEN_INFO), FIXED_TINY_CENT_GAS_PRICE_COST); + return Math.max(FIXED_VIEW_GAS_COST, gasRequirement); } /** * Given a dispatch type, returns the canonical gas requirement for that dispatch type. * Useful when providing a ballpark gas requirement in the absence of a valid * transaction body for the dispatch type. + * Used for non-query operations. * * @param dispatchType the dispatch type * @return the canonical gas requirement for that dispatch type */ public long canonicalGasRequirement(@NonNull final DispatchType dispatchType) { - return asGasRequirement(canonicalPriceInTinybars(dispatchType)); - } - - /** - * Although mono-service compares the fixed {@code 100 gas} cost to the implied gas requirement of a - * stand-in {@link com.hedera.hapi.node.transaction.TransactionGetRecordQuery}, this stand-in query's - * cost will be less than {@code 100 gas} at any exchange rate that sustains the existence of the network. - * So for simplicity, we drop that comparison and just return the fixed {@code 100 gas} cost. - * - * @return the minimum gas requirement for a view query - */ - public long viewGasRequirement() { - return FIXED_VIEW_GAS_COST; + // isGasPrecisionLossFixEnabled is a temporary feature flag that will be removed in the future. + if (!tinybarValues.isGasPrecisionLossFixEnabled()) { + return asGasRequirement(canonicalPriceInTinybars(dispatchType)); + } + return gasRequirementFromTinycents( + dispatchPrices.canonicalPriceInTinycents(dispatchType), + tinybarValues.childTransactionTinycentGasPrice()); } /** @@ -127,9 +151,14 @@ public long viewGasRequirement() { * @param dispatchType the dispatch type * @return the canonical price for that dispatch type */ - public long canonicalPriceInTinybars(@NonNull final DispatchType dispatchType) { + public long canonicalPriceInTinycents(@NonNull final DispatchType dispatchType) { requireNonNull(dispatchType); - return tinybarValues.asTinybars(dispatchPrices.canonicalPriceInTinycents(dispatchType)); + // If not enabled, return the price in TinyBars. + // This is directly used only in ClassicTransfersCall. However, it is easier to place the feature flag here. + if (!tinybarValues.isGasPrecisionLossFixEnabled()) { + return canonicalPriceInTinybars(dispatchType); + } + return dispatchPrices.canonicalPriceInTinycents(dispatchType); } /** @@ -139,7 +168,7 @@ public long canonicalPriceInTinybars(@NonNull final DispatchType dispatchType) { * @param payer the payer account * @return the canonical price for that dispatch */ - public long canonicalPriceInTinybars(@NonNull final TransactionBody body, @NonNull final AccountID payer) { + public long feeCalculatorPriceInTinyBars(@NonNull final TransactionBody body, @NonNull final AccountID payer) { return feeCalculator.applyAsLong(body, payer); } @@ -154,15 +183,65 @@ public long gasCostInTinybars(final long gas) { } /** - * Given a tinybar price, returns the equivalent gas requirement at the current gas price. + * Calculates the gas requirement for an operation based on the provided tinycents price and gas price. + * The calculation rounds up the result to the nearest gas unit and then adds 20% to the computed gas + * requirement to account for the premium of executing a HAPI operation within the EVM. * - * @param tinybarPrice the tinybar price - * @return the equivalent gas requirement at the current gas price + * @param tinycentsPrice the price of the operation in tinycents + * @param gasPriceInCents the current gas price in cents + * @return the computed gas requirement for the operation */ + private long gasRequirementFromTinycents(long tinycentsPrice, final long gasPriceInCents) { + final var gasRequirement = + (tinycentsPrice + gasPriceInCents - 1) * FEE_SCHEDULE_UNITS_PER_TINYCENT / gasPriceInCents; + return gasRequirement + (gasRequirement / 5); + } + + /** + * After feature flag isGasPrecisionLossFixEnabled is removed, this method should be removed. + * @deprecated + */ + @Deprecated(since = "Precision loss fix was implemented in PR #14842", forRemoval = true) + public long topLevelGasPrice() { + return tinybarValues.topLevelTinybarGasPrice(); + } + + /** + * After feature flag isGasPrecisionLossFixEnabled is removed, this method should be removed. + * @deprecated + */ + @Deprecated(since = "Precision loss fix was implemented in PR #14842", forRemoval = true) + public long canonicalPriceInTinybars(@NonNull final DispatchType dispatchType) { + requireNonNull(dispatchType); + return tinybarValues.asTinybars(dispatchPrices.canonicalPriceInTinycents(dispatchType)); + } + + /** + * After feature flag isGasPrecisionLossFixEnabled is removed, this method should be removed. + * @deprecated + */ + @Deprecated(since = "Precision loss fix was implemented in PR #14842", forRemoval = true) + public long gasRequirementOldWithPrecisionLoss( + @NonNull final TransactionBody body, @NonNull final AccountID payer, final long minimumPriceInTinybars) { + final var nominalPriceInTinybars = feeCalculator.applyAsLong(body, payer); + final var priceInTinybars = Math.max(minimumPriceInTinybars, nominalPriceInTinybars); + return asGasRequirement(priceInTinybars); + } + + /** + * After feature flag isGasPrecisionLossFixEnabled is removed, this method should be removed. + * @deprecated + */ + @Deprecated(since = "Precision loss fix was implemented in PR #14842", forRemoval = true) private long asGasRequirement(final long tinybarPrice) { return asGasRequirement(tinybarPrice, tinybarValues.childTransactionTinybarGasPrice()); } + /** + * After feature flag isGasPrecisionLossFixEnabled is removed, this method should be removed. + * @deprecated + */ + @Deprecated(since = "Precision loss fix was implemented in PR #14842", forRemoval = true) private long asGasRequirement(final long tinybarPrice, final long gasPrice) { // We round up to the nearest gas unit, and then add 20% to account for the premium // of doing a HAPI operation from inside the EVM diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/TinybarValues.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/TinybarValues.java index c0da5838d639..484d06695ca4 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/TinybarValues.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/gas/TinybarValues.java @@ -17,11 +17,13 @@ package com.hedera.node.app.service.contract.impl.exec.gas; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.FEE_SCHEDULE_UNITS_PER_TINYCENT; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromTinybarsToTinycents; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.fromTinycentsToTinybars; import static com.hedera.node.app.spi.workflows.FunctionalityResourcePrices.PREPAID_RESOURCE_PRICES; import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; +import com.hedera.node.config.data.ContractsConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Objects; @@ -31,6 +33,8 @@ */ public class TinybarValues { private final ExchangeRate exchangeRate; + private final boolean isGasPrecisionLossFixEnabled; + private final boolean isCanonicalViewGasEnabled; private final FunctionalityResourcePrices topLevelResourcePrices; // Only non-null for a top-level transaction, since queries cannot have child transactions @Nullable @@ -45,8 +49,9 @@ public class TinybarValues { * @param exchangeRate the current exchange rate * @return a query-appropriate instance of {@link TinybarValues} */ - public static TinybarValues forQueryWith(@NonNull final ExchangeRate exchangeRate) { - return new TinybarValues(exchangeRate, PREPAID_RESOURCE_PRICES, null); + public static TinybarValues forQueryWith( + @NonNull final ExchangeRate exchangeRate, @NonNull final ContractsConfig contractsConfig) { + return new TinybarValues(exchangeRate, contractsConfig, PREPAID_RESOURCE_PRICES, null); } /** @@ -60,18 +65,22 @@ public static TinybarValues forQueryWith(@NonNull final ExchangeRate exchangeRat */ public static TinybarValues forTransactionWith( @NonNull final ExchangeRate exchangeRate, + @NonNull final ContractsConfig contractsConfig, @NonNull final FunctionalityResourcePrices topLevelResourcePrices, @Nullable final FunctionalityResourcePrices childTransactionResourcePrices) { - return new TinybarValues(exchangeRate, topLevelResourcePrices, childTransactionResourcePrices); + return new TinybarValues(exchangeRate, contractsConfig, topLevelResourcePrices, childTransactionResourcePrices); } private TinybarValues( @NonNull final ExchangeRate exchangeRate, + @NonNull final ContractsConfig contractsConfig, @NonNull final FunctionalityResourcePrices topLevelResourcePrices, @Nullable final FunctionalityResourcePrices childTransactionResourcePrices) { this.exchangeRate = Objects.requireNonNull(exchangeRate); this.topLevelResourcePrices = Objects.requireNonNull(topLevelResourcePrices); this.childTransactionResourcePrices = childTransactionResourcePrices; + this.isGasPrecisionLossFixEnabled = contractsConfig.isGasPrecisionLossFixEnabled(); + this.isCanonicalViewGasEnabled = contractsConfig.isCanonicalViewGasEnabled(); } /** @@ -84,6 +93,10 @@ public long asTinybars(final long tinycents) { return fromTinycentsToTinybars(exchangeRate, tinycents); } + public long asTinycents(final long tinyBars) { + return fromTinybarsToTinycents(exchangeRate, tinyBars); + } + /** * Returns the tinybar-denominated price of a unit of gas for the current operation based on the current exchange * rate, the current congestion multiplier, and the tinycent-denominated price of gas in the {@code service} fee @@ -98,6 +111,30 @@ public long topLevelTinybarGasPrice() { * topLevelResourcePrices.congestionMultiplier()); } + /** + * Returns the tinyBar price of a unit of gas for the current operation based on the current exchange + * rate, the current congestion multiplier without being denominated in tinycents units. + * + * @return the full precision tinybar price of a unit of gas for the current operation + */ + public long topLevelTinybarGasPriceFullPrecision() { + return asTinybars( + topLevelResourcePrices.basePrices().servicedataOrThrow().gas() + * topLevelResourcePrices.congestionMultiplier()); + } + + /** + * Returns the topLevel gas price cost in tinycents, without denomination, but with congestion multiplier. + * @return the tinycents gas price + */ + public long topLevelTinycentGasPrice() { + if (!isGasPrecisionLossFixEnabled) { + return topLevelTinybarGasPrice(); + } + return topLevelResourcePrices.basePrices().servicedataOrThrow().gas() + * topLevelResourcePrices.congestionMultiplier(); + } + /** * Returns the tinybar-denominated price of a unit of gas for dispatching a child transaction based on the current exchange * rate, the current congestion multiplier, and the tinycent-denominated price of gas in the {@code service} fee @@ -116,16 +153,52 @@ public long childTransactionTinybarGasPrice() { } /** + * Returns the tinycent gas price for dispatching a child transaction based on the current exchange rate, + * Without denomination, but with congestion multiplier, saving the precision. + * @return the tinycent gas price + */ + public long childTransactionTinycentGasPrice() { + if (childTransactionResourcePrices == null) { + throw new IllegalStateException("Cannot dispatch a child transaction from a query"); + } + return childTransactionResourcePrices.basePrices().servicedataOrThrow().gas() + * childTransactionResourcePrices.congestionMultiplier(); + } + + /** + * Note: this part of the javadoc will be removed after {@code isGasPrecisionLossFixEnabled} flag is removed. * Returns the tinybar-denominated price of a RAM-byte-hour (rbh) for the current operation based on the current - * exchange rate, the current congestion multiplier, and the tinycent-denominated price of a rbh in the - * {@code service} fee component. + * exchange rate, the current congestion multiplier. + * + * Or return the tinycent-denominated price of a RAM-byte-hour (rbh) in the {@code service} fee component + * with the current congestion multiplier. * - * @return the tinybar-denominated price of a rbh for the current operation + * @return the tinybar/tinycent-denominated price of a rbh for the current operation */ - public long topLevelTinybarRbhPrice() { - return asTinybars( - topLevelResourcePrices.basePrices().servicedataOrThrow().rbh() - / FEE_SCHEDULE_UNITS_PER_TINYCENT - * topLevelResourcePrices.congestionMultiplier()); + public long topLevelTinycentRbhPrice() { + if (!isGasPrecisionLossFixEnabled) { + return asTinybars( + topLevelResourcePrices.basePrices().servicedataOrThrow().rbh() + / FEE_SCHEDULE_UNITS_PER_TINYCENT + * topLevelResourcePrices.congestionMultiplier()); + } + return topLevelResourcePrices.basePrices().servicedataOrThrow().rbh() + * topLevelResourcePrices.congestionMultiplier(); + } + + /** + * This can be removed after integrity of the fix is confirmed. + * We have it as a temporary measure to allow for easy rollback in case of issues. + */ + public boolean isGasPrecisionLossFixEnabled() { + return isGasPrecisionLossFixEnabled; + } + + /** + * This can be removed after the dynamic gas for view operations is confirmed. + * We have it as a temporary measure to allow for easy rollback in case of issues. + */ + public boolean isCanonicalViewGasEnabled() { + return isCanonicalViewGasEnabled; } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java index b03ef106544c..d1ca12bf6dde 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/scope/HandleHederaOperations.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.ContractServiceImpl.LAZY_MEMO; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.FEE_SCHEDULE_UNITS_PER_TINYCENT; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; import static com.hedera.node.app.service.contract.impl.utils.SynthTxnUtils.*; import static com.hedera.node.app.spi.key.KeyUtils.IMMUTABILITY_SENTINEL_KEY; @@ -186,8 +187,13 @@ public long lazyCreationCostInGas(@NonNull final Address recipient) { final var synthCreation = TransactionBody.newBuilder() .cryptoCreateAccount(CREATE_TXN_BODY_BUILDER.alias(tuweniToPbjBytes(recipient))) .build(); - final var createFee = gasCalculator.canonicalPriceInTinybars(synthCreation, payerId); - return (createFee) / gasCalculator.topLevelGasPrice(); + final var createFee = gasCalculator.feeCalculatorPriceInTinyBars(synthCreation, payerId); + + // isGasPrecisionLossFixEnabled is a temporary feature flag that will be removed in the future. + if (!contractsConfig.isGasPrecisionLossFixEnabled()) { + return (createFee) / gasCalculator.topLevelGasPrice(); + } + return (createFee) * FEE_SCHEDULE_UNITS_PER_TINYCENT / gasCalculator.topLevelGasPriceInTinyBars(); } /** diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java index 2586626c845d..07d60d1cb8ad 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/create/ClassicCreatesCall.java @@ -113,7 +113,7 @@ private record LegacyActivation(long contractNum, Bytes pbjAddress, Address besu .transactionValidStart(timestamp) .build()) .build(); - final var baseCost = gasCalculator.canonicalPriceInTinybars(syntheticCreateWithId, spenderId); + final var baseCost = gasCalculator.feeCalculatorPriceInTinyBars(syntheticCreateWithId, spenderId); // The non-gas cost is a 20% surcharge on the HAPI TokenCreate price, minus the fee taken as gas long nonGasCost = baseCost + (baseCost / 5) - gasCalculator.gasCostInTinybars(FIXED_GAS_COST); if (frame.getValue().lessThan(Wei.of(nonGasCost))) { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java index 4a15ce19af71..8d2ee2e02ec3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/systemcontracts/hts/transfer/ClassicTransfersCall.java @@ -172,7 +172,7 @@ public ClassicTransfersCall( /** * Simulates the mono-service gas calculation for a classic transfer, which is significantly complicated by our - * current strategy for setting the minimum tinybar price based on the canonical prices of various operations. + * current strategy for setting the minimum tinycent price based on the canonical prices of various operations. * * @param body the transaction body to be dispatched * @param systemContractGasCalculator the gas calculator to use @@ -191,47 +191,47 @@ public static long transferGasRequirement( final var hasCustomFees = enhancement.nativeOperations().checkForCustomFees(op); // For fungible there are always at least two operations, so only charge half for each // operation - final var baseUnitAdjustTinybarPrice = systemContractGasCalculator.canonicalPriceInTinybars( + final var baseUnitAdjustTinycentPrice = systemContractGasCalculator.canonicalPriceInTinycents( hasCustomFees ? DispatchType.TRANSFER_FUNGIBLE_CUSTOM_FEES : DispatchType.TRANSFER_FUNGIBLE) / 2; // NFTs are atomic, one line can do it. - final var baseNftTransferTinybarPrice = systemContractGasCalculator.canonicalPriceInTinybars( + final var baseNftTransferTinycentsPrice = systemContractGasCalculator.canonicalPriceInTinycents( hasCustomFees ? DispatchType.TRANSFER_NFT_CUSTOM_FEES : DispatchType.TRANSFER_NFT); // Hbar transfer is similar to fungible tokens so only charge half for each operation - final var baseHbarAdjustTinybarPrice = - systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_HBAR) / 2; + final var baseAdjustTinycentsPrice = + systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_HBAR) / 2; final var baseLazyCreationPrice = - systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.CRYPTO_CREATE) - + systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.CRYPTO_UPDATE); + systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.CRYPTO_CREATE) + + systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.CRYPTO_UPDATE); final var extantAccounts = enhancement.nativeOperations().readableAccountStore(); - final long minimumTinybarPrice = minimumTinybarPriceGiven( + final long minimumTinycentPrice = minimumTinycentPriceGiven( op, - baseUnitAdjustTinybarPrice, - baseHbarAdjustTinybarPrice, - baseNftTransferTinybarPrice, + baseUnitAdjustTinycentPrice, + baseAdjustTinycentsPrice, + baseNftTransferTinycentsPrice, baseLazyCreationPrice, extantAccounts, selector); - return systemContractGasCalculator.gasRequirement(body, payerId, minimumTinybarPrice); + return systemContractGasCalculator.gasRequirementWithTinycents(body, payerId, minimumTinycentPrice); } - private static long minimumTinybarPriceGiven( + private static long minimumTinycentPriceGiven( @NonNull final CryptoTransferTransactionBody op, - final long baseUnitAdjustTinybarPrice, - final long baseHbarAdjustTinybarPrice, - final long baseNftTransferTinybarPrice, + final long baseUnitAdjustTinyCentPrice, + final long baseAdjustTinyCentsPrice, + final long baseNftTransferTinyCentsPrice, final long baseLazyCreationPrice, @NonNull final ReadableAccountStore extantAccounts, @NonNull final byte[] selector) { - long minimumTinybarPrice = 0L; - final var numHbarAdjusts = + long minimumTinycentPrice = 0L; + final var numTinyCentsAdjusts = op.transfersOrElse(TransferList.DEFAULT).accountAmounts().size(); - minimumTinybarPrice += numHbarAdjusts * baseHbarAdjustTinybarPrice; + minimumTinycentPrice += numTinyCentsAdjusts * baseAdjustTinyCentsPrice; final Set aliasesToLazyCreate = new HashSet<>(); for (final var tokenTransfers : op.tokenTransfers()) { final var unitAdjusts = tokenTransfers.transfers(); - minimumTinybarPrice += unitAdjusts.size() * baseUnitAdjustTinybarPrice; + minimumTinycentPrice += unitAdjusts.size() * baseUnitAdjustTinyCentPrice; for (final var unitAdjust : unitAdjusts) { if (unitAdjust.amount() > 0 && unitAdjust.accountIDOrElse(AccountID.DEFAULT).hasAlias()) { @@ -243,7 +243,7 @@ private static long minimumTinybarPriceGiven( } } final var nftTransfers = tokenTransfers.nftTransfers(); - minimumTinybarPrice += nftTransfers.size() * baseNftTransferTinybarPrice; + minimumTinycentPrice += nftTransfers.size() * baseNftTransferTinyCentsPrice; for (final var nftTransfer : nftTransfers) { if (nftTransfer.receiverAccountIDOrElse(AccountID.DEFAULT).hasAlias()) { final var alias = nftTransfer.receiverAccountIDOrThrow().aliasOrThrow(); @@ -254,8 +254,8 @@ private static long minimumTinybarPriceGiven( } } } - minimumTinybarPrice += aliasesToLazyCreate.size() * baseLazyCreationPrice; - return minimumTinybarPrice; + minimumTinycentPrice += aliasesToLazyCreate.size() * baseLazyCreationPrice; + return minimumTinycentPrice; } private boolean shouldRetryWithApprovals() { diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java index e450c2f6f1cc..9d72f757d5ca 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/utils/ConversionUtils.java @@ -724,6 +724,11 @@ public static long fromTinycentsToTinybars(final ExchangeRate exchangeRate, fina .longValueExact(); } + public static long fromTinybarsToTinycents(final ExchangeRate exchangeRate, final long tinyBars) { + return fromAToB(BigInteger.valueOf(tinyBars), exchangeRate.centEquiv(), exchangeRate.hbarEquiv()) + .longValueExact(); + } + /** * Given an amount in one unit and its conversion rate to another unit, returns the equivalent amount * in the other unit. diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java index 7483e580645d..a44d0edb233a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/TransactionModuleTest.java @@ -24,6 +24,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_CONTRACTS_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.DEFAULT_HEDERA_CONFIG; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.ETH_DATA_WITH_CALL_DATA; +import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.FEE_SCHEDULE_UNITS_PER_TINYCENT; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; @@ -222,8 +223,10 @@ void providesNullEthTxDataIfNotEthereumTransaction() { TransactionModule.maybeProvideHydratedEthTxData(context, hydration, DEFAULT_HEDERA_CONFIG, fileStore)); } + // This test uses deprecated logic for calculation of gas price. + // Conversion of tinyCents to tinyBars is not needed for canonical gas prices. @Test - void providesSystemGasContractCalculator() { + void providesSystemGasContractCalculatorLegacy() { // Given a transaction-specific dispatch cost of 6 tinycent... given(context.dispatchComputeFees(TransactionBody.DEFAULT, AccountID.DEFAULT, ComputeDispatchFeesAsTopLevel.NO)) .willReturn(new Fees(1, 2, 3)); @@ -242,6 +245,30 @@ void providesSystemGasContractCalculator() { assertEquals(4L, result); } + @Test + void providesSystemGasContractCalculator() { + // Fix is enabled, no precision should be lost + given(tinybarValues.isGasPrecisionLossFixEnabled()).willReturn(true); + + // Given a transaction-specific dispatch cost of 6 tinyBars which will be 12000 tinyCents... + given(context.dispatchComputeFees(TransactionBody.DEFAULT, AccountID.DEFAULT, ComputeDispatchFeesAsTopLevel.NO)) + .willReturn(new Fees(1, 2, 3)); + // The 6 tinyBars = 12000 tinyCents + given(tinybarValues.asTinycents(6L)).willReturn(12000L); + + // But a canonical price of 66000 tinyCents for an approve call (which, being + // greater than the above 12000 tinyCents, is the effective price)... + given(canonicalDispatchPrices.canonicalPriceInTinycents(DispatchType.APPROVE)) + .willReturn(66000L); + + // With each gas costing 2000 tinyCents... + given(tinybarValues.childTransactionTinycentGasPrice()).willReturn(2000L * FEE_SCHEDULE_UNITS_PER_TINYCENT); + final var calculator = + TransactionModule.provideSystemContractGasCalculator(context, canonicalDispatchPrices, tinybarValues); + final var result = calculator.gasRequirement(TransactionBody.DEFAULT, DispatchType.APPROVE, AccountID.DEFAULT); + assertEquals(1238L, result); + } + @Test void providesValidators() { given(context.attributeValidator()).willReturn(attributeValidator); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java index f0eaded593c8..ae99e7c9f3c6 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/SystemContractGasCalculatorTest.java @@ -59,14 +59,14 @@ void returnsMinimumGasCostForViews() { void computesCanonicalDispatchType() { given(dispatchPrices.canonicalPriceInTinycents(DispatchType.APPROVE)).willReturn(123L); given(tinybarValues.asTinybars(123L)).willReturn(321L); - assertEquals(321L, subject.canonicalPriceInTinybars(DispatchType.APPROVE)); + assertEquals(321L, subject.canonicalPriceInTinycents(DispatchType.APPROVE)); } @Test void computesCanonicalDispatch() { given(feeCalculator.applyAsLong(TransactionBody.DEFAULT, AccountID.DEFAULT)) .willReturn(123L); - assertEquals(123L, subject.canonicalPriceInTinybars(TransactionBody.DEFAULT, AccountID.DEFAULT)); + assertEquals(123L, subject.feeCalculatorPriceInTinyBars(TransactionBody.DEFAULT, AccountID.DEFAULT)); } @Test diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/TinybarValuesTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/TinybarValuesTest.java index d21cf83250b6..4431aaf35cc1 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/TinybarValuesTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/gas/TinybarValuesTest.java @@ -23,6 +23,9 @@ import com.hedera.hapi.node.transaction.ExchangeRate; import com.hedera.node.app.service.contract.impl.exec.gas.TinybarValues; import com.hedera.node.app.spi.workflows.FunctionalityResourcePrices; +import com.hedera.node.config.data.ContractsConfig; +import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.swirlds.config.api.Configuration; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -49,10 +52,12 @@ class TinybarValuesTest { new FunctionalityResourcePrices(CHILD_TRANSACTION_PRICES_TO_USE, 2); private TinybarValues subject; + private static final Configuration CONFIGURATION = HederaTestConfigBuilder.createConfig(); + private final ContractsConfig contractsConfig = CONFIGURATION.getConfigData(ContractsConfig.class); @BeforeEach void setUp() { - subject = TinybarValues.forTransactionWith(RATE_TO_USE, resourcePrices, childResourcePrices); + subject = TinybarValues.forTransactionWith(RATE_TO_USE, contractsConfig, resourcePrices, childResourcePrices); } @Test @@ -65,8 +70,7 @@ void computesExchangeRateAsExpected() { @Test void computesExpectedRbhServicePrice() { withTransactionSubject(); - final var expectedRbhPrice = RBH_FEE_SCHEDULE_PRICE / (CENTS_PER_HBAR * 1000); - assertEquals(expectedRbhPrice, subject.topLevelTinybarRbhPrice()); + assertEquals(RBH_FEE_SCHEDULE_PRICE, subject.topLevelTinycentRbhPrice()); } @Test @@ -90,10 +94,10 @@ void querySubjectRefusesToComputeChildGasServicePrice() { } private void withTransactionSubject() { - subject = TinybarValues.forTransactionWith(RATE_TO_USE, resourcePrices, childResourcePrices); + subject = TinybarValues.forTransactionWith(RATE_TO_USE, contractsConfig, resourcePrices, childResourcePrices); } private void withQuerySubject() { - subject = TinybarValues.forQueryWith(RATE_TO_USE); + subject = TinybarValues.forQueryWith(RATE_TO_USE, contractsConfig); } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java index 84307493f4ba..1435f317dfe9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaOperationsTest.java @@ -248,9 +248,9 @@ void commitIsNoopUntilSavepointExposesIt() { @Test void lazyCreationCostInGasTest() { given(context.payer()).willReturn(A_NEW_ACCOUNT_ID); - given(gasCalculator.canonicalPriceInTinybars(any(), eq(A_NEW_ACCOUNT_ID))) + given(gasCalculator.feeCalculatorPriceInTinyBars(any(), eq(A_NEW_ACCOUNT_ID))) .willReturn(5L); - given(gasCalculator.topLevelGasPrice()).willReturn(1L); + given(gasCalculator.topLevelGasPriceInTinyBars()).willReturn(1000L); assertEquals(5L, subject.lazyCreationCostInGas(NON_SYSTEM_LONG_ZERO_ADDRESS)); } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java index f2fb149f3c8a..55f4fc941dd3 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/create/ClassicCreatesCallTest.java @@ -312,7 +312,7 @@ private void commonGivens() { private void commonGivens(long baseCost, long value, boolean shouldBePreempted) { given(frame.getValue()).willReturn(Wei.of(value)); - given(gasCalculator.canonicalPriceInTinybars(any(), any())).willReturn(baseCost); + given(gasCalculator.feeCalculatorPriceInTinyBars(any(), any())).willReturn(baseCost); stack.push(frame); given(addressIdConverter.convert(asHeadlongAddress(FRAME_SENDER_ADDRESS))) .willReturn(A_NEW_ACCOUNT_ID); diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java index bb23dd32cf9a..4790c2f22cc5 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hts/transfer/ClassicTransfersGasCalcTest.java @@ -71,7 +71,8 @@ void chargesForPerceivedLazyCreations() { + PRETEND_LAZY_CREATION_TINYBAR_PRICE; final var expectedGasRequirement = 666L; final var body = TransactionBody.newBuilder().cryptoTransfer(op).build(); - given(systemContractGasCalculator.gasRequirement(body, AccountID.DEFAULT, expectedTotalTinybarPrice)) + given(systemContractGasCalculator.gasRequirementWithTinycents( + body, AccountID.DEFAULT, expectedTotalTinybarPrice)) .willReturn(expectedGasRequirement); final var actualGasRequirement = ClassicTransfersCall.transferGasRequirement( @@ -96,7 +97,8 @@ void doesNotChargeForExtantAliases() { PRETEND_NFT_TRANSFER_TINYBAR_PRICE + 2 * expectedFtMinimumPrice + 2 * expectedHbarMinimumPrice; final var expectedGasRequirement = 666L; final var body = TransactionBody.newBuilder().cryptoTransfer(op).build(); - given(systemContractGasCalculator.gasRequirement(body, AccountID.DEFAULT, expectedTotalTinybarPrice)) + given(systemContractGasCalculator.gasRequirementWithTinycents( + body, AccountID.DEFAULT, expectedTotalTinybarPrice)) .willReturn(expectedGasRequirement); final var actualGasRequirement = ClassicTransfersCall.transferGasRequirement( @@ -109,27 +111,27 @@ void doesNotChargeForExtantAliases() { } private void givenPretendPricesWithCustomFees() { - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_HBAR)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_HBAR)) .willReturn(PRETEND_HBAR_TRANSFER_TINYBAR_PRICE); - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_NFT_CUSTOM_FEES)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_NFT_CUSTOM_FEES)) .willReturn(PRETEND_NFT_TRANSFER_CUSTOM_FEES_TINYBAR_PRICE); - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_FUNGIBLE_CUSTOM_FEES)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_FUNGIBLE_CUSTOM_FEES)) .willReturn(PRETEND_FUNGIBLE_TRANSFER_CUSTOM_FEES_TINYBAR_PRICE); } private void givenPretendPricesWithoutCustomFees() { - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_NFT)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_NFT)) .willReturn(PRETEND_NFT_TRANSFER_TINYBAR_PRICE); - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_HBAR)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_HBAR)) .willReturn(PRETEND_HBAR_TRANSFER_TINYBAR_PRICE); - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.TRANSFER_FUNGIBLE)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.TRANSFER_FUNGIBLE)) .willReturn(PRETEND_FUNGIBLE_TRANSFER_TINYBAR_PRICE); } private void givenPretendLazyCreationPrices() { - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.CRYPTO_UPDATE)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.CRYPTO_UPDATE)) .willReturn(PRETEND_CRYPTO_UPDATE_TINYBAR_PRICE); - given(systemContractGasCalculator.canonicalPriceInTinybars(DispatchType.CRYPTO_CREATE)) + given(systemContractGasCalculator.canonicalPriceInTinycents(DispatchType.CRYPTO_CREATE)) .willReturn(PRETEND_CRYPTO_CREATE_TINYBAR_PRICE); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/entities/SpecAccount.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/entities/SpecAccount.java index 03667a1b9551..2b50324c5374 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/entities/SpecAccount.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/entities/SpecAccount.java @@ -102,6 +102,51 @@ public TransferTokensOperation transferUnitsTo( return new TransferTokensOperation(this, to, token, units); } + /** + * Returns an operation to transfer tokens, transferring its balance to the given beneficiary. + * + * @param to the beneficiary + * @param units the number of units to transfer + * @param token the token to transfer + * @return the operation + */ + public TransferTokensOperation transferUnitsTo( + @NonNull final SpecContract to, final long units, @NonNull final SpecFungibleToken token) { + requireNonNull(token); + requireNonNull(to); + return new TransferTokensOperation(this, to, token, units); + } + + /** + * Returns an operation to transfer NFT, transferring the NFT to the given beneficiary. + * + * @param to the beneficiary + * @param serialNumber the specific serial number of the NFT + * @param token the NFT to transfer + * @return the operation + */ + public TransferTokensOperation transferNFTsTo( + @NonNull final SpecContract to, @NonNull final SpecNonFungibleToken token, final long... serialNumber) { + requireNonNull(token); + requireNonNull(to); + return new TransferTokensOperation(this, to, token, serialNumber); + } + + /** + * Returns an operation to transfer NFT, transferring the NFT to the given beneficiary. + * + * @param to the beneficiary + * @param serialNumber the specific serial number of the NFT + * @param token the NFT to transfer + * @return the operation + */ + public TransferTokensOperation transferNFTsTo( + @NonNull final SpecAccount to, @NonNull final SpecNonFungibleToken token, final long... serialNumber) { + requireNonNull(token); + requireNonNull(to); + return new TransferTokensOperation(this, to, token, serialNumber); + } + /** * Returns an operation to perform the given airdrops. * diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/CallContractOperation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/CallContractOperation.java index 9eeff1386893..9aa9e592cf78 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/CallContractOperation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/CallContractOperation.java @@ -39,6 +39,7 @@ public class CallContractOperation extends AbstractSpecTransaction a.accept(op)); return op; @@ -79,6 +81,11 @@ public CallContractOperation sending(final long value) { return this; } + public CallContractOperation via(final String txnName) { + this.txnName = txnName; + return this; + } + @Override protected CallContractOperation self() { return this; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/TransferTokensOperation.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/TransferTokensOperation.java index ecc1c54b38be..77f3738c2b60 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/TransferTokensOperation.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/dsl/operations/transactions/TransferTokensOperation.java @@ -23,8 +23,11 @@ import com.hedera.services.bdd.spec.HapiSpec; import com.hedera.services.bdd.spec.SpecOperation; import com.hedera.services.bdd.spec.dsl.entities.SpecAccount; +import com.hedera.services.bdd.spec.dsl.entities.SpecContract; import com.hedera.services.bdd.spec.dsl.entities.SpecFungibleToken; +import com.hedera.services.bdd.spec.dsl.entities.SpecNonFungibleToken; import com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer; +import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.List; @@ -34,10 +37,12 @@ */ public class TransferTokensOperation extends AbstractSpecTransaction implements SpecOperation { - private final SpecAccount sender; - private final SpecAccount receiver; - private final SpecFungibleToken token; - private final long units; + private final TransferType transferType; + private final String senderName; + private final String receiverName; + private final String tokenName; + private long units; + private long[] serialNumber; public TransferTokensOperation( @NonNull final SpecAccount sender, @@ -45,20 +50,69 @@ public TransferTokensOperation( @NonNull final SpecFungibleToken token, final long units) { super(List.of(sender, receiver, token)); - this.sender = requireNonNull(sender); - this.receiver = requireNonNull(receiver); - this.token = requireNonNull(token); + this.senderName = requireNonNull(sender.name()); + this.receiverName = requireNonNull(receiver.name()); + this.tokenName = requireNonNull(token.name()); this.units = units; + this.transferType = TransferType.FUNGIBLE_TOKEN; + } + + public TransferTokensOperation( + @NonNull final SpecAccount sender, + @NonNull final SpecContract receiver, + @NonNull final SpecFungibleToken token, + final long units) { + super(List.of(sender, receiver, token)); + this.senderName = requireNonNull(sender.name()); + this.receiverName = requireNonNull(receiver.name()); + this.tokenName = requireNonNull(token.name()); + this.units = units; + this.transferType = TransferType.FUNGIBLE_TOKEN; + } + + public TransferTokensOperation( + @NonNull final SpecAccount sender, + @NonNull final SpecContract receiver, + @NonNull final SpecNonFungibleToken token, + final long... serialNumber) { + super(List.of(sender, receiver, token)); + this.senderName = requireNonNull(sender.name()); + this.receiverName = requireNonNull(receiver.name()); + this.tokenName = requireNonNull(token.name()); + this.serialNumber = serialNumber; + this.transferType = TransferType.NFT; + } + + public TransferTokensOperation( + @NonNull final SpecAccount sender, + @NonNull final SpecAccount receiver, + @NonNull final SpecNonFungibleToken token, + final long... serialNumber) { + super(List.of(sender, receiver, token)); + this.senderName = requireNonNull(sender.name()); + this.receiverName = requireNonNull(receiver.name()); + this.tokenName = requireNonNull(token.name()); + this.serialNumber = serialNumber; + this.transferType = TransferType.NFT; } @NonNull @Override protected SpecOperation computeDelegate(@NonNull final HapiSpec spec) { - return cryptoTransfer(moving(units, token.name()).between(sender.name(), receiver.name())); + return switch (transferType) { + case FUNGIBLE_TOKEN -> cryptoTransfer(moving(units, tokenName).between(senderName, receiverName)); + case NFT -> cryptoTransfer( + TokenMovement.movingUnique(tokenName, serialNumber).between(senderName, receiverName)); + }; } @Override protected TransferTokensOperation self() { return this; } + + public enum TransferType { + NFT, + FUNGIBLE_TOKEN + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java index 473849bfbb6b..4566e272fd80 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/hapi/ContractDeleteSuite.java @@ -147,13 +147,14 @@ final Stream cannotUseMoreThanChildContractLimit() { asHeadlongAddress(treasuryMirrorAddr.get()), BigInteger.valueOf(illegalNumChildren)) .via(precompileViolation) + .gas(215_000L) .hasKnownStatus(MAX_CHILD_RECORDS_EXCEEDED), contractCall( contract, "createThingsRepeatedly", BigInteger.valueOf(illegalNumChildren)) .via(internalCreateViolation) - .gas(15_000_000) + .gas(15_000_000L) .hasKnownStatus(MAX_CHILD_RECORDS_EXCEEDED)); })) .then( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java index 8466c2425021..56568b8e8ef1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/ContractBurnHTSV2SecurityModelSuite.java @@ -826,7 +826,7 @@ final Stream V2SecurityHscsPreC020RollbackBurnThatFailsAfterAPrecom @HapiTest final Stream V2SecurityHscsPrec004TokenBurnOfFungibleTokenUnits() { - final var gasUsed = 14085L; + final var gasUsed = 15284L; final var CREATION_TX = "CREATION_TX"; final var MULTI_KEY = "MULTI_KEY"; @@ -990,7 +990,7 @@ final Stream V2SecurityHscsPrec011BurnAfterNestedMint() { @HapiTest final Stream V2SecurityHscsPrec005TokenBurnOfNft() { - final var gasUsed = 14085; + final var gasUsed = 15284L; final var CREATION_TX = "CREATION_TX"; return defaultHapiSpec("V2SecurityHscsPrec005TokenBurnOfNft") .given( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java index 7762d67937f0..46419a29a5c3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/RedirectPrecompileSuite.java @@ -90,7 +90,7 @@ final Stream balanceOf() { .contractCallResult(htsPrecompileResult() .forFunction(ParsingConstants.FunctionType.ERC_BALANCE) .withBalance(totalSupply)) - .gasUsed(100L)))); + .gasUsed(2607L)))); } @HapiTest @@ -128,7 +128,7 @@ final Stream redirectToInvalidToken() { CONTRACT_REVERT_EXECUTED, recordWith() .status(INVALID_TOKEN_ID) - .contractCallResult(resultWith().gasUsed(100L)))); + .contractCallResult(resultWith().gasUsed(2607L)))); } @HapiTest diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java index 9f17dca38cf0..6d7b6125254e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/TokenInfoHTSSuite.java @@ -1579,6 +1579,7 @@ final Stream happyPathUpdateTokenKeysAndReadLatestInformation() { HapiParserUtil.asHeadlongAddress( asAddress(spec.registry().getContractId(TOKEN_INFO_CONTRACT)))) .via(UPDATE_AND_GET_TOKEN_KEYS_INFO_TXN) + .gas(117000L) .alsoSigningWithFullPrefix(MULTI_KEY)))) .then(withOpContext((spec, opLog) -> allRunFor( spec, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java index 46e760a654dc..36fb00907789 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/WipeTokenAccountPrecompileSuite.java @@ -200,7 +200,7 @@ final Stream wipeFungibleTokenScenarios() { .contractCallResult(resultWith() .contractCallResult( htsPrecompileResult().withStatus(SUCCESS)) - .gasUsed(14085L))), + .gasUsed(15284L))), getTokenInfo(VANILLA_TOKEN).hasTotalSupply(990), getAccountBalance(ACCOUNT).hasTokenBalance(VANILLA_TOKEN, 490)); } @@ -335,7 +335,7 @@ final Stream wipeNonFungibleTokenScenarios() { .contractCallResult(resultWith() .contractCallResult( htsPrecompileResult().withStatus(SUCCESS)) - .gasUsed(14085L))), + .gasUsed(15284L))), getTokenInfo(VANILLA_TOKEN).hasTotalSupply(1), getAccountBalance(ACCOUNT).hasTokenBalance(VANILLA_TOKEN, 0)); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/token/GasCalculationIntegrityTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/token/GasCalculationIntegrityTest.java new file mode 100644 index 000000000000..c8dac59cd2dd --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/token/GasCalculationIntegrityTest.java @@ -0,0 +1,450 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.hedera.services.bdd.suites.contract.precompile.token; + +import static com.hedera.services.bdd.junit.ContextRequirement.UPGRADE_FILE_CONTENT; +import static com.hedera.services.bdd.junit.TestTags.SMART_CONTRACT; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.dsl.entities.SpecTokenKey.ADMIN_KEY; +import static com.hedera.services.bdd.spec.dsl.entities.SpecTokenKey.PAUSE_KEY; +import static com.hedera.services.bdd.spec.dsl.entities.SpecTokenKey.SUPPLY_KEY; +import static com.hedera.services.bdd.spec.dsl.entities.SpecTokenKey.WIPE_KEY; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; +import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.suites.HapiSuite.EXCHANGE_RATES; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_MILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THOUSAND_HBAR; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; + +import com.google.protobuf.ByteString; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.junit.OrderedInIsolation; +import com.hedera.services.bdd.junit.support.TestLifecycle; +import com.hedera.services.bdd.spec.dsl.annotations.Account; +import com.hedera.services.bdd.spec.dsl.annotations.Contract; +import com.hedera.services.bdd.spec.dsl.annotations.FungibleToken; +import com.hedera.services.bdd.spec.dsl.annotations.NonFungibleToken; +import com.hedera.services.bdd.spec.dsl.entities.SpecAccount; +import com.hedera.services.bdd.spec.dsl.entities.SpecContract; +import com.hedera.services.bdd.spec.dsl.entities.SpecFungibleToken; +import com.hedera.services.bdd.spec.dsl.entities.SpecNonFungibleToken; +import com.hedera.services.bdd.spec.transactions.file.HapiFileUpdate; +import com.hedera.services.bdd.spec.utilops.CustomSpecAssert; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; + +@Tag(SMART_CONTRACT) +@DisplayName("Gas Integrity Tests for Token Contracts") +@HapiTestLifecycle +@OrderedInIsolation +@SuppressWarnings("java:S1192") +public class GasCalculationIntegrityTest { + + @Contract(contract = "NumericContract", creationGas = 1_000_000L) + static SpecContract numericContract; + + @Contract(contract = "NumericContractComplex", creationGas = 1_000_000L) + static SpecContract numericContractComplex; + + @Contract(contract = "TokenInfoContract", creationGas = 1_000_000L) + static SpecContract tokenInfoContract; + + @Contract(contract = "ERC20Contract", creationGas = 1_000_000L) + static SpecContract erc20Contract; + + @Account(maxAutoAssociations = 10, tinybarBalance = ONE_MILLION_HBARS) + static SpecAccount alice; + + @Account(maxAutoAssociations = 10, tinybarBalance = ONE_MILLION_HBARS) + static SpecAccount bob; + + @FungibleToken + static SpecFungibleToken token; + + @FungibleToken( + initialSupply = 1_000L, + maxSupply = 1_200L, + keys = {SUPPLY_KEY, PAUSE_KEY, ADMIN_KEY, WIPE_KEY}) + static SpecFungibleToken fungibleToken; + + @NonFungibleToken( + numPreMints = 7, + keys = {SUPPLY_KEY, PAUSE_KEY, ADMIN_KEY, WIPE_KEY}) + static SpecNonFungibleToken nft; + + public static final long EXPIRY_RENEW = 3_000_000L; + + private final Stream testCases = Stream.of( + new RatesProvider(30000, 16197), + new RatesProvider(30000, 359789), + new RatesProvider(30000, 2888899), + new RatesProvider(30000, 269100)); + + private record RatesProvider(int hBarEquiv, int centEquiv) {} + + @BeforeAll + public static void beforeAll(final @NonNull TestLifecycle lifecycle) { + // Fetch exchange rates before tests + lifecycle.doAdhoc( + // Save exchange rates before tests + withOpContext((spec, opLog) -> { + var fetch = getFileContents(EXCHANGE_RATES).logged(); + allRunFor(spec, fetch); + final var validRates = fetch.getResponse() + .getFileGetContents() + .getFileContents() + .getContents(); + spec.registry().saveBytes("originalRates", validRates); + }), + + // Authorizations + fungibleToken.authorizeContracts(numericContractComplex), + nft.authorizeContracts(numericContractComplex), + numericContract.associateTokens(fungibleToken, nft), + + // Approvals + fungibleToken.treasury().approveTokenAllowance(fungibleToken, numericContractComplex, 1000L), + nft.treasury().approveNFTAllowance(nft, numericContractComplex, true, List.of(1L, 2L, 3L, 4L, 5L)), + alice.approveCryptoAllowance(numericContractComplex, ONE_HBAR), + // Transfers + fungibleToken.treasury().transferUnitsTo(numericContract, 100L, fungibleToken), + nft.treasury().transferNFTsTo(numericContract, nft, 7L), + alice.transferHBarsTo(numericContractComplex, ONE_HUNDRED_HBARS)); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(1) + @DisplayName("when using nft via redirect proxy contract") + public Stream approveViaProxyNft() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContract + .call("approveRedirect", nft, bob, BigInteger.valueOf(7)) + .gas(756_729L) + .via("approveRedirectTxn") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("approveRedirectTxn").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(2) + @DisplayName("when using fungible token hts system contract") + public Stream approveFungibleToken() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContract + .call("approve", fungibleToken, alice, BigInteger.TWO) + .gas(742_877L) + .via("approveTxn") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("approveTxn").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(3) + @DisplayName("when using createFungibleTokenWithCustomFeesV3 with fractionalFee") + public Stream createFungibleTokenWithCustomFeesV3FractionalFee() { + final long nominator = 1; + final long denominator = 1; + final long maxAmount = 500; + final long minAmount = 100; + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call( + "createFungibleTokenWithCustomFeesV3FractionalFee", + nominator, + denominator, + minAmount, + maxAmount) + .gas(165_038L) + .sending(THOUSAND_HBAR) + .via("createWithCustomFeeFractional") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("createWithCustomFeeFractional").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(4) + @DisplayName("when using createNonFungibleTokenWithCustomFeesV3 with fractionalFee") + public Stream createNonFungibleTokenWithCustomRoyaltyFeesV3() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("createNonFungibleTokenWithCustomRoyaltyFeesV3", alice.getED25519KeyBytes(), 1L, 2L, 10L) + .gas(169_584L) + .sending(THOUSAND_HBAR) + .payingWith(alice) + .via("createWithCustomFeeRoyalty") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("createWithCustomFeeRoyalty").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @DisplayName("when using createFungibleToken") + public Stream createFungible() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("createFungibleToken", EXPIRY_RENEW, EXPIRY_RENEW, 10000L, BigInteger.TEN, BigInteger.TWO) + .gas(165_800L) + .sending(THOUSAND_HBAR) + .via("createFungibleToken") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("createFungibleToken").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(5) + @DisplayName("when using createNonFungibleTokenV3") + public Stream createNonFungibleTokenV3() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("createNonFungibleTokenV3", alice.getED25519KeyBytes(), EXPIRY_RENEW, EXPIRY_RENEW, 10L) + .gas(166_944L) + .sending(THOUSAND_HBAR) + .payingWith(alice) + .via("createNonFungibleTokenV3") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("createNonFungibleTokenV3").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(6) + @DisplayName("when using cryptoTransferV2 for hBar transfer") + public Stream useCryptoTransferV2() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("cryptoTransferV2", new long[] {-5, 5}, alice, bob) + .gas(33_304L) + .via("cryptoTransferV2") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("cryptoTransferV2").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(7) + @DisplayName("when using cryptoTransferFungibleV1 with internal auto associate") + public Stream useCryptoTransferFungibleV1() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call( + "cryptoTransferFungibleV1", + fungibleToken, + new long[] {-5, 5}, + fungibleToken.treasury(), + bob) + .via("cryptoTransferFungibleV1") + .gas(763_480L), + restoreOriginalRates(), + getTxnRecord("cryptoTransferFungibleV1").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(8) + @DisplayName("when using cryptoTransferNonFungible with internal auto associate") + public Stream useCryptoTransferNonFungible() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("cryptoTransferNonFungible", nft, nft.treasury(), bob, 1L) + .gas(761_070L) + .via("cryptoTransferNonFungible") + .andAssert(txn -> txn.hasKnownStatus(SUCCESS)), + restoreOriginalRates(), + getTxnRecord("cryptoTransferNonFungible").logged(), + bob.transferNFTsTo(nft.treasury(), nft, 1L))); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(9) + @DisplayName("when using transferNFTs with internal auto associate") + public Stream useTransferNFTs() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferNFTs", nft, nft.treasury(), alice, new long[] {4L}) + .via("transferNFTs") + .gas(761_519L), + restoreOriginalRates(), + getTxnRecord("transferNFTs").logged(), + alice.transferNFTsTo(nft.treasury(), nft, 4L))); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(10) + @DisplayName("when using transferToken with internal auto associate") + public Stream useTransferToken() { + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferTokenTest", fungibleToken, fungibleToken.treasury(), alice, 1L) + .via("transferTokenTest") + .gas(758_568L), + restoreOriginalRates(), + getTxnRecord("transferTokenTest").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(11) + @DisplayName("when using transferNFT") + public Stream useTransferNFT() { + // Cannot be tested directly as it requires associate from previous test + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferNFTTest", nft, nft.treasury(), alice, 3L) + .via("transferNFTTest") + .gas(42_235L), + restoreOriginalRates(), + getTxnRecord("transferNFTTest").logged(), + alice.transferNFTsTo(nft.treasury(), nft, 3L))); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(12) + @DisplayName("when using transferFrom") + public Stream useTransferFrom() { + // Cannot be tested directly as it requires associate from previous test + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferFrom", fungibleToken, fungibleToken.treasury(), alice, BigInteger.ONE) + .via("transferFrom") + .gas(42_264L), + restoreOriginalRates(), + getTxnRecord("transferFrom").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(13) + @DisplayName("when using transferFromERC") + public Stream useTransferFromERC() { + // Cannot be tested directly as it requires associate from previous test + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferFromERC", fungibleToken, fungibleToken.treasury(), alice, BigInteger.ONE) + .via("transferFromERC") + .gas(44_900L), + restoreOriginalRates(), + getTxnRecord("transferFromERC").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(14) + @DisplayName("when using transferFromNFT") + public Stream useTransferNFTFrom() { + // Cannot be tested directly as it requires associate from previous test + return testCases.flatMap(rates -> hapiTest( + updateRates(rates.hBarEquiv, rates.centEquiv), + numericContractComplex + .call("transferFromNFT", nft, nft.treasury(), alice, BigInteger.TWO) + .via("transferFromNFT") + .gas(42_263L), + getTxnRecord("transferFromNFT").logged(), + restoreOriginalRates(), + alice.transferNFTsTo(nft.treasury(), nft, 2L))); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(15) + @DisplayName("for token info call") + public Stream checkTokenGetInfoGas() { + return testCases.flatMap(ratesProvider -> hapiTest( + updateRates(ratesProvider.hBarEquiv, ratesProvider.centEquiv), + tokenInfoContract + .call("getInformationForToken", token) + .gas(78_805L) + .via("tokenInfo"), + restoreOriginalRates(), + getTxnRecord("tokenInfo").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(16) + @DisplayName("for token custom fees call") + public Stream checkTokenGetCustomFeesGas() { + return testCases.flatMap(ratesProvider -> hapiTest( + updateRates(ratesProvider.hBarEquiv, ratesProvider.centEquiv), + tokenInfoContract + .call("getCustomFeesForToken", token) + .gas(31_421L) + .via("customFees"), + restoreOriginalRates(), + getTxnRecord("customFees").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(17) + @DisplayName("for token name call") + public Stream checkErc20Name() { + return testCases.flatMap(ratesProvider -> hapiTest( + updateRates(ratesProvider.hBarEquiv, ratesProvider.centEquiv), + erc20Contract.call("name", token).gas(30_207L).via("name"), + restoreOriginalRates(), + getTxnRecord("name").logged())); + } + + @LeakyHapiTest(requirement = UPGRADE_FILE_CONTENT) + @Order(18) + @DisplayName("for token balance of call") + public Stream checkErc20BalanceOf() { + return testCases.flatMap(ratesProvider -> hapiTest( + updateRates(ratesProvider.hBarEquiv, ratesProvider.centEquiv), + erc20Contract.call("balanceOf", token, alice).gas(30_074L).via("balance"), + restoreOriginalRates(), + getTxnRecord("balance").logged())); + } + + private static HapiFileUpdate updateRates(final int hBarEquiv, final int centEquiv) { + return fileUpdate(EXCHANGE_RATES) + .contents(spec -> + spec.ratesProvider().rateSetWith(hBarEquiv, centEquiv).toByteString()); + } + + private static CustomSpecAssert restoreOriginalRates() { + return withOpContext((spec, opLog) -> { + var resetRatesOp = fileUpdate(EXCHANGE_RATES) + .contents(contents -> ByteString.copyFrom(spec.registry().getBytes("originalRates"))); + allRunFor(spec, resetRatesOp); + }); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java index e65316c1f733..8883edbd1b25 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/NonceSuite.java @@ -80,7 +80,7 @@ public class NonceSuite { private static final long LOW_GAS_PRICE = 1L; private static final long ENOUGH_GAS_PRICE = 75L; - private static final long ENOUGH_GAS_LIMIT = 150_000L; + private static final long ENOUGH_GAS_LIMIT = 215_000L; private static final String RECEIVER = "receiver"; private static final String INTERNAL_CALLEE_CONTRACT = "InternalCallee"; private static final String INTERNAL_CALLER_CONTRACT = "InternalCaller"; diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.bin b/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.bin index de66edf461bd..e05276a0a8c4 100644 --- a/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.bin +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.bin @@ -1 +1 @@ -6080604052348015600e575f80fd5b50615bf98061001c5f395ff3fe608060405260043610610149575f3560e01c806349da8cd1116100b55780639b53f99b1161006e5780639b53f99b146103df578063b5fd634214610407578063c3cfee041461042f578063d325899b1461044b578063e8bb61e514610473578063ff2895691461048f57610149565b806349da8cd1146102e7578063517527ff1461030f5780635864cc291461032b5780636dadb99f146103535780637c41ad2c1461037b5780639b23d3d9146103b757610149565b806316e62c1e1161010757806316e62c1e146102275780631af22a9d146102435780632637d1611461026b5780633bdc30061461028757806343ef0166146102af5780634495ed93146102cb57610149565b8062c801471461014d578063067362ac146101695780630e43f0b4146101855780630e969a05146101ad57806314167e2e146101d757806315dacbea146101ff575b5f80fd5b61016760048036038101906101629190613be1565b610499565b005b610183600480360381019061017e9190613c3f565b6106ad565b005b348015610190575f80fd5b506101ab60048036038101906101a69190613e96565b6107e7565b005b3480156101b8575f80fd5b506101c1610a50565b6040516101ce9190613f31565b60405180910390f35b3480156101e2575f80fd5b506101fd60048036038101906101f89190613f4a565b610a55565b005b34801561020a575f80fd5b5061022560048036038101906102209190613fae565b610bb3565b005b610241600480360381019061023c9190614012565b610ce4565b005b34801561024e575f80fd5b5061026960048036038101906102649190614076565b610f44565b005b6102856004803603810190610280919061411f565b6111f5565b005b348015610292575f80fd5b506102ad60048036038101906102a89190613fae565b611331565b005b6102c960048036038101906102c4919061421f565b61145e565b005b6102e560048036038101906102e0919061429f565b611707565b005b3480156102f2575f80fd5b5061030d60048036038101906103089190614303565b611a27565b005b6103296004803603810190610324919061421f565b611b58565b005b348015610336575f80fd5b50610351600480360381019061034c9190614367565b611ca5565b005b34801561035e575f80fd5b5061037960048036038101906103749190614303565b611dd3565b005b348015610386575f80fd5b506103a1600480360381019061039c91906143a5565b611f04565b6040516103ae91906143e8565b60405180910390f35b3480156103c2575f80fd5b506103dd60048036038101906103d89190613fae565b612013565b005b3480156103ea575f80fd5b5061040560048036038101906104009190613fae565b612144565b005b348015610412575f80fd5b5061042d60048036038101906104289190614303565b612268565b005b6104496004803603810190610444919061442b565b612537565b005b348015610456575f80fd5b50610471600480360381019061046c91906144a2565b612671565b005b61048d60048036038101906104889190614522565b6129a6565b005b610497612af3565b005b5f6104aa5f622dc6c0612710612d27565b90505f600167ffffffffffffffff8111156104c8576104c7613d24565b5b60405190808252806020026020018201604052801561050157816020015b6104ee61369b565b8152602001906001900390816104e65790505b50905061050d83612f05565b815f815181106105205761051f6145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634634c381ae760e01b8660646002885f67ffffffffffffffff81111561056f5761056e613d24565b5b6040519080825280602002602001820160405280156105a857816020015b6105956136fb565b81526020019060019003908161058d5790505b506040516024016105bd959493929190614b8c565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516106279190614c2c565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50915091505f8083610679575f8061068e565b8280602001905181019061068d9190614c91565b5b91509150601660030b8260030b146106a4575f80fd5b50505050505050565b5f6106b9868686612d27565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff1634637812a04b60e01b8588886040516024016106f493929190614ccf565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161075e9190614c2c565b5f6040518083038185875af1925050503d805f8114610798576040519150601f19603f3d011682016040523d82523d5f602084013e61079d565b606091505b50915091505f80836107b0575f806107c5565b828060200190518101906107c49190614c91565b5b91509150601660030b8260030b146107db575f80fd5b50505050505050505050565b5f600167ffffffffffffffff81111561080357610802613d24565b5b6040519080825280602002602001820160405280156108315781602001602082028036833780820191505090505b50905083815f81518110610848576108476145a2565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f600167ffffffffffffffff81111561089e5761089d613d24565b5b6040519080825280602002602001820160405280156108cc5781602001602082028036833780820191505090505b50905083815f815181106108e3576108e26145a2565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f8061016773ffffffffffffffffffffffffffffffffffffffff168885858860405160240161094f9493929190614e79565b6040516020818303038152906040527f2c4ba191000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516109d99190614c2c565b5f604051808303815f865af19150503d805f8114610a12576040519150601f19603f3d011682016040523d82523d5f602084013e610a17565b606091505b50915091505f81806020019051810190610a319190614ed1565b9050601660030b8160030b14610a45575f80fd5b505050505050505050565b601681565b610a5d61375b565b60405180606001604052808560070b81526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018460070b8152508161010001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff16637d305cfa60e01b8885604051602401610ad3929190615013565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610b3d9190614c2c565b5f604051808303815f865af19150503d805f8114610b76576040519150601f19603f3d011682016040523d82523d5f602084013e610b7b565b606091505b50915091505f81806020019051810190610b959190614ed1565b9050601660030b8160030b14610ba9575f80fd5b5050505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401610be59493929190615041565b6040516020818303038152906040527f15dacbea000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c6f9190614c2c565b5f604051808303815f865af19150503d805f8114610ca8576040519150601f19603f3d011682016040523d82523d5f602084013e610cad565b606091505b50915091505f81806020019051810190610cc79190614ed1565b9050601660030b8160030b14610cdb575f80fd5b50505050505050565b5f610cf6600a622dc6c0612710612f72565b90505f600167ffffffffffffffff811115610d1457610d13613d24565b5b604051908082528060200260200182016040528015610d4d57816020015b610d3a6137c6565b815260200190600190039081610d325790505b5090506040518060c001604052808760070b81526020018660070b81526020018560070b81526020018460070b81526020015f151581526020013073ffffffffffffffffffffffffffffffffffffffff16815250815f81518110610db457610db36145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634632af0c59a60e01b86606460025f67ffffffffffffffff811115610e0257610e01613d24565b5b604051908082528060200260200182016040528015610e3b57816020015b610e2861381a565b815260200190600190039081610e205790505b5089604051602401610e519594939291906152c2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610ebb9190614c2c565b5f6040518083038185875af1925050503d805f8114610ef5576040519150601f19603f3d011682016040523d82523d5f602084013e610efa565b606091505b50915091505f8083610f0d575f80610f22565b82806020019051810190610f219190614c91565b5b91509150601660030b8260030b14610f38575f80fd5b50505050505050505050565b5f600267ffffffffffffffff811115610f6057610f5f613d24565b5b604051908082528060200260200182016040528015610f9957816020015b610f86613877565b815260200190600190039081610f7e5790505b50905060405180606001604052808473ffffffffffffffffffffffffffffffffffffffff168152602001855f81518110610fd657610fd56145a2565b5b602002602001015160070b81526020015f1515815250815f81518110610fff57610ffe6145a2565b5b602002602001018190525060405180606001604052808373ffffffffffffffffffffffffffffffffffffffff16815260200185600181518110611045576110446145a2565b5b602002602001015160070b81526020015f15158152508160018151811061106f5761106e6145a2565b5b60200260200101819052505f60405180602001604052808381525090505f8061016773ffffffffffffffffffffffffffffffffffffffff16630e71804f60e01b845f67ffffffffffffffff8111156110ca576110c9613d24565b5b60405190808252806020026020018201604052801561110357816020015b6110f06138b0565b8152602001906001900390816110e85790505b50604051602401611115929190615703565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161117f9190614c2c565b5f604051808303815f865af19150503d805f81146111b8576040519150601f19603f3d011682016040523d82523d5f602084013e6111bd565b606091505b50915091505f818060200190518101906111d79190614ed1565b9050601660030b8160030b146111eb575f80fd5b5050505050505050565b5f611205600a622dc6c086613109565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff163463c23baeb660e01b8588886040516024016112409392919061582d565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516112aa9190614c2c565b5f6040518083038185875af1925050503d805f81146112e4576040519150601f19603f3d011682016040523d82523d5f602084013e6112e9565b606091505b50915091505f80836112fc575f80611311565b828060200190518101906113109190614c91565b5b91509150601660030b8260030b14611327575f80fd5b5050505050505050565b5f808573ffffffffffffffffffffffffffffffffffffffff1686858560405160240161135f93929190615869565b6040516020818303038152906040527fbeabacc8000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516113e99190614c2c565b5f604051808303815f865af19150503d805f8114611422576040519150601f19603f3d011682016040523d82523d5f602084013e611427565b606091505b50915091505f818060200190518101906114419190614ed1565b9050601660030b8160030b14611455575f80fd5b50505050505050565b5f6114706001622dc6c0612710612f72565b90505f61147e6003876132e4565b9050808260e001819052505f600167ffffffffffffffff8111156114a5576114a4613d24565b5b6040519080825280602002602001820160405280156114de57816020015b6114cb6138e6565b8152602001906001900390816114c35790505b5090506114e96138e6565b86815f019060070b908160070b8152505085816020019060070b908160070b8152505084816040019060070b908160070b815250506001816080019015159081151581525050308160a0019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505080825f8151811061157b5761157a6145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff163463abb54eb560e01b885f67ffffffffffffffff8111156115c5576115c4613d24565b5b6040519080825280602002602001820160405280156115fe57816020015b6115eb61381a565b8152602001906001900390816115e35790505b5088604051602401611612939291906159bf565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161167c9190614c2c565b5f6040518083038185875af1925050503d805f81146116b6576040519150601f19603f3d011682016040523d82523d5f602084013e6116bb565b606091505b50915091505f80836116ce575f806116e3565b828060200190518101906116e29190614c91565b5b91509150601660030b8260030b146116f9575f80fd5b505050505050505050505050565b5f6117158585612710612d27565b90505f600167ffffffffffffffff81111561173357611732613d24565b5b60405190808252806020026020018201604052801561176c57816020015b6117596136fb565b8152602001906001900390816117515790505b50905083815f81518110611783576117826145a2565b5b60200260200101515f019063ffffffff16908163ffffffff168152505082815f815181106117b4576117b36145a2565b5b60200260200101516020019063ffffffff16908163ffffffff16815250506001815f815181106117e7576117e66145a2565b5b6020026020010151608001901515908115158152505030815f81518110611811576118106145a2565b5b602002602001015160a0019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f8061016773ffffffffffffffffffffffffffffffffffffffff1634634c381ae760e01b86606460025f67ffffffffffffffff81111561189257611891613d24565b5b6040519080825280602002602001820160405280156118cb57816020015b6118b861369b565b8152602001906001900390816118b05790505b505f67ffffffffffffffff8111156118e6576118e5613d24565b5b60405190808252806020026020018201604052801561191f57816020015b61190c6136fb565b8152602001906001900390816119045790505b50604051602401611934959493929190614b8c565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161199e9190614c2c565b5f6040518083038185875af1925050503d805f81146119d8576040519150601f19603f3d011682016040523d82523d5f602084013e6119dd565b606091505b50915091505f80836119f0575f80611a05565b82806020019051810190611a049190614c91565b5b91509150601660030b8260030b14611a1b575f80fd5b50505050505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401611a599493929190615a09565b6040516020818303038152906040527feca36917000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611ae39190614c2c565b5f604051808303815f865af19150503d805f8114611b1c576040519150601f19603f3d011682016040523d82523d5f602084013e611b21565b606091505b50915091505f81806020019051810190611b3b9190614ed1565b9050601660030b8160030b14611b4f575f80fd5b50505050505050565b5f611b64848484612f72565b90505f611b726003876132e4565b9050808260e001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff163463ea83f29360e01b86604051602401611bb29190615a4c565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611c1c9190614c2c565b5f6040518083038185875af1925050503d805f8114611c56576040519150601f19603f3d011682016040523d82523d5f602084013e611c5b565b606091505b50915091505f8083611c6e575f80611c83565b82806020019051810190611c829190614c91565b5b91509150601660030b8260030b14611c99575f80fd5b50505050505050505050565b611cad61394d565b818160a0019060070b908160070b815250505f8061016773ffffffffffffffffffffffffffffffffffffffff166318370d3460e01b8685604051602401611cf5929190615a6c565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611d5f9190614c2c565b5f604051808303815f865af19150503d805f8114611d98576040519150601f19603f3d011682016040523d82523d5f602084013e611d9d565b606091505b50915091505f81806020019051810190611db79190614ed1565b9050601660030b8160030b14611dcb575f80fd5b505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401611e059493929190615a09565b6040516020818303038152906040527f5cfc9011000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611e8f9190614c2c565b5f604051808303815f865af19150503d805f8114611ec8576040519150601f19603f3d011682016040523d82523d5f602084013e611ecd565b606091505b50915091505f81806020019051810190611ee79190614ed1565b9050601660030b8160030b14611efb575f80fd5b50505050505050565b5f805f61016773ffffffffffffffffffffffffffffffffffffffff16637c41ad2c60e01b85604051602401611f399190615a9a565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611fa39190614c2c565b5f604051808303815f865af19150503d805f8114611fdc576040519150601f19603f3d011682016040523d82523d5f602084013e611fe1565b606091505b509150915081611ff2576015612007565b808060200190518101906120069190614ed1565b5b60030b92505050919050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff16868686866040516024016120459493929190615041565b6040516020818303038152906040527f9b23d3d9000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516120cf9190614c2c565b5f604051808303815f865af19150503d805f8114612108576040519150601f19603f3d011682016040523d82523d5f602084013e61210d565b606091505b50915091505f818060200190518101906121279190614ed1565b9050601660030b8160030b1461213b575f80fd5b50505050505050565b5f808573ffffffffffffffffffffffffffffffffffffffff1685858560405160240161217293929190615869565b6040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516121fc9190614c2c565b5f604051808303815f865af19150503d805f8114612235576040519150601f19603f3d011682016040523d82523d5f602084013e61223a565b606091505b50915091505f818060200190518101906122549190615add565b90508061225f575f80fd5b50505050505050565b5f600167ffffffffffffffff81111561228457612283613d24565b5b6040519080825280602002602001820160405280156122bd57816020015b6122aa6139b8565b8152602001906001900390816122a25790505b50905060405180606001604052808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018360070b815250815f8151811061231d5761231c6145a2565b5b60200260200101819052505f600167ffffffffffffffff81111561234457612343613d24565b5b60405190808252806020026020018201604052801561237d57816020015b61236a6138b0565b8152602001906001900390816123625790505b50905060405180606001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020015f67ffffffffffffffff8111156123c1576123c0613d24565b5b6040519080825280602002602001820160405280156123fa57816020015b6123e7613a05565b8152602001906001900390816123df5790505b50815260200183815250815f81518110612417576124166145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1663189a554c60e01b846040516024016124569190615b08565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516124c09190614c2c565b5f604051808303815f865af19150503d805f81146124f9576040519150601f19603f3d011682016040523d82523d5f602084013e6124fe565b606091505b50915091505f818060200190518101906125189190614ed1565b9050601660030b8160030b1461252c575f80fd5b505050505050505050565b5f612543868686612f72565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff1634630fb65bf360e01b85888860405160240161257e93929190615b28565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516125e89190614c2c565b5f6040518083038185875af1925050503d805f8114612622576040519150601f19603f3d011682016040523d82523d5f602084013e612627565b606091505b50915091505f808361263a575f8061264f565b8280602001905181019061264e9190614c91565b5b91509150601660030b8260030b14612665575f80fd5b50505050505050505050565b5f600267ffffffffffffffff81111561268d5761268c613d24565b5b6040519080825280602002602001820160405280156126c657816020015b6126b3613a05565b8152602001906001900390816126ab5790505b50905060405180604001604052808473ffffffffffffffffffffffffffffffffffffffff168152602001855f81518110612703576127026145a2565b5b602002602001015160070b815250815f81518110612724576127236145a2565b5b602002602001018190525060405180604001604052808373ffffffffffffffffffffffffffffffffffffffff1681526020018560018151811061276a576127696145a2565b5b602002602001015160070b8152508160018151811061278c5761278b6145a2565b5b60200260200101819052505f600167ffffffffffffffff8111156127b3576127b2613d24565b5b6040519080825280602002602001820160405280156127ec57816020015b6127d96138b0565b8152602001906001900390816127d15790505b50905060405180606001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020018381526020015f67ffffffffffffffff81111561283657612835613d24565b5b60405190808252806020026020018201604052801561286f57816020015b61285c6139b8565b8152602001906001900390816128545790505b50815250815f81518110612886576128856145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1663189a554c60e01b846040516024016128c59190615b08565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161292f9190614c2c565b5f604051808303815f865af19150503d805f8114612968576040519150601f19603f3d011682016040523d82523d5f602084013e61296d565b606091505b50915091505f818060200190518101906129879190615b78565b9050601660030b8160070b1461299b575f80fd5b505050505050505050565b5f6129b2848484613109565b90505f6129c06003876132e4565b9050808260e001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634639c89bb3560e01b86604051602401612a009190615ba3565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612a6a9190614c2c565b5f6040518083038185875af1925050503d805f8114612aa4576040519150601f19603f3d011682016040523d82523d5f602084013e612aa9565b606091505b50915091505f8083612abc575f80612ad1565b82806020019051810190612ad09190614c91565b5b91509150601660030b8260030b14612ae7575f80fd5b50505050505050505050565b5f612b05600a622dc6c0612710612f72565b90505f600167ffffffffffffffff811115612b2357612b22613d24565b5b604051908082528060200260200182016040528015612b5c57816020015b612b4961381a565b815260200190600190039081612b415790505b509050612b887fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613386565b815f81518110612b9b57612b9a6145a2565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634632af0c59a60e01b8660646002885f67ffffffffffffffff811115612bea57612be9613d24565b5b604051908082528060200260200182016040528015612c2357816020015b612c106137c6565b815260200190600190039081612c085790505b50604051602401612c389594939291906152c2565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612ca29190614c2c565b5f6040518083038185875af1925050503d805f8114612cdc576040519150601f19603f3d011682016040523d82523d5f602084013e612ce1565b606091505b50915091505f8083612cf4575f80612d09565b82806020019051810190612d089190614c91565b5b91509150601660030b8260030b14612d1f575f80fd5b505050505050565b612d2f613a36565b5f600167ffffffffffffffff811115612d4b57612d4a613d24565b5b604051908082528060200260200182016040528015612d8457816020015b612d71613aa4565b815260200190600190039081612d695790505b509050612da15f600160405180602001604052805f8152506133f1565b815f81518110612db457612db36145a2565b5b60200260200101819052506040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018463ffffffff1681526020015f1515815260200182815260200160405180606001604052808863ffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018763ffffffff168152508152509150509392505050565b612f0d61369b565b81815f019063ffffffff16908163ffffffff1681525050600181604001901515908115158152505033816080019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050919050565b612f7a61375b565b6040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018360070b81526020015f151581526020015f67ffffffffffffffff81111561308757613086613d24565b5b6040519080825280602002602001820160405280156130c057816020015b6130ad613aa4565b8152602001906001900390816130a55790505b50815260200160405180606001604052808760070b81526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018660070b81525081525090509392505050565b61311161394d565b5f600167ffffffffffffffff81111561312d5761312c613d24565b5b60405190808252806020026020018201604052801561316657816020015b613153613aa4565b81526020019060019003908161314b5790505b5090506131835f600160405180602001604052805f8152506133f1565b815f81518110613196576131956145a2565b5b60200260200101819052506040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018460070b81526020015f1515815260200182815260200160405180606001604052808863ffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018763ffffffff168152508152509150509392505050565b6060600167ffffffffffffffff81111561330157613300613d24565b5b60405190808252806020026020018201604052801561333a57816020015b613327613aa4565b81526020019060019003908161331f5790505b5090506040518060400160405280613350613428565b815260200161335f85856134c5565b815250815f81518110613375576133746145a2565b5b602002602001018190525092915050565b61338e61381a565b6040518060a001604052808360070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020016001151581526020016001151581526020013373ffffffffffffffffffffffffffffffffffffffff168152509050919050565b6133f9613aa4565b604051806040016040528061340d866135f3565b815260200161341c85856134c5565b81525090509392505050565b5f61343c5f8261368890919063ffffffff16565b905061345260018261368890919063ffffffff16565b905061346860028261368890919063ffffffff16565b905061347e60038261368890919063ffffffff16565b905061349460048261368890919063ffffffff16565b90506134aa60058261368890919063ffffffff16565b90506134c060068261368890919063ffffffff16565b905090565b6134cd613ac3565b60018360ff16036134ed576001815f0190151590811515815250506135ed565b60028360ff1603613554575f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff16816020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250506135ec565b60038360ff160361356d578181604001819052506135eb565b60048360ff1603613586578181606001819052506135ea565b60058360ff16036135e9575f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff16816080019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505b5b5b5b5b92915050565b5f808260ff16036136075760019050613683565b60018260ff160361361b5760029050613683565b60028260ff160361362f5760049050613683565b60038260ff16036136435760089050613683565b60048260ff16036136575760109050613683565b60058260ff160361366b5760209050613683565b60068260ff160361367f5760409050613683565b5f90505b919050565b5f8160ff166001901b8317905092915050565b6040518060a001604052805f63ffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060c001604052805f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f15158152602001606081526020016137c0613b1d565b81525090565b6040518060c001604052805f60070b81526020015f60070b81526020015f60070b81526020015f60070b81526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060a001604052805f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81526020015f151581525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff16815260200160608152602001606081525090565b6040518060c001604052805f60070b81526020015f60070b81526020015f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f15158152602001606081526020016139b2613b57565b81525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180604001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f63ffffffff1681526020015f1515815260200160608152602001613a9e613b57565b81525090565b60405180604001604052805f8152602001613abd613ac3565b81525090565b6040518060a001604052805f151581526020015f73ffffffffffffffffffffffffffffffffffffffff16815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180606001604052805f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180606001604052805f63ffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f63ffffffff1681525090565b5f604051905090565b5f80fd5b5f80fd5b5f63ffffffff82169050919050565b613bc081613ba8565b8114613bca575f80fd5b50565b5f81359050613bdb81613bb7565b92915050565b5f60208284031215613bf657613bf5613ba0565b5b5f613c0384828501613bcd565b91505092915050565b5f819050919050565b613c1e81613c0c565b8114613c28575f80fd5b50565b5f81359050613c3981613c15565b92915050565b5f805f805f60a08688031215613c5857613c57613ba0565b5b5f613c6588828901613bcd565b9550506020613c7688828901613bcd565b9450506040613c8788828901613bcd565b9350506060613c9888828901613c2b565b9250506080613ca988828901613c2b565b9150509295509295909350565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f613cdf82613cb6565b9050919050565b613cef81613cd5565b8114613cf9575f80fd5b50565b5f81359050613d0a81613ce6565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613d5a82613d14565b810181811067ffffffffffffffff82111715613d7957613d78613d24565b5b80604052505050565b5f613d8b613b97565b9050613d978282613d51565b919050565b5f67ffffffffffffffff821115613db657613db5613d24565b5b602082029050602081019050919050565b5f80fd5b5f8160070b9050919050565b613de081613dcb565b8114613dea575f80fd5b50565b5f81359050613dfb81613dd7565b92915050565b5f613e13613e0e84613d9c565b613d82565b90508083825260208201905060208402830185811115613e3657613e35613dc7565b5b835b81811015613e5f5780613e4b8882613ded565b845260208401935050602081019050613e38565b5050509392505050565b5f82601f830112613e7d57613e7c613d10565b5b8135613e8d848260208601613e01565b91505092915050565b5f805f8060808587031215613eae57613ead613ba0565b5b5f613ebb87828801613cfc565b9450506020613ecc87828801613cfc565b9350506040613edd87828801613cfc565b925050606085013567ffffffffffffffff811115613efe57613efd613ba4565b5b613f0a87828801613e69565b91505092959194509250565b5f8160030b9050919050565b613f2b81613f16565b82525050565b5f602082019050613f445f830184613f22565b92915050565b5f805f8060808587031215613f6257613f61613ba0565b5b5f613f6f87828801613cfc565b9450506020613f8087828801613ded565b9350506040613f9187828801613ded565b9250506060613fa287828801613ded565b91505092959194509250565b5f805f8060808587031215613fc657613fc5613ba0565b5b5f613fd387828801613cfc565b9450506020613fe487828801613cfc565b9350506040613ff587828801613cfc565b925050606061400687828801613c2b565b91505092959194509250565b5f805f806080858703121561402a57614029613ba0565b5b5f61403787828801613ded565b945050602061404887828801613ded565b935050604061405987828801613ded565b925050606061406a87828801613ded565b91505092959194509250565b5f805f6060848603121561408d5761408c613ba0565b5b5f84013567ffffffffffffffff8111156140aa576140a9613ba4565b5b6140b686828701613e69565b93505060206140c786828701613cfc565b92505060406140d886828701613cfc565b9150509250925092565b5f67ffffffffffffffff82169050919050565b6140fe816140e2565b8114614108575f80fd5b50565b5f81359050614119816140f5565b92915050565b5f805f6060848603121561413657614135613ba0565b5b5f61414386828701613ded565b93505060206141548682870161410b565b925050604061416586828701613bcd565b9150509250925092565b5f80fd5b5f67ffffffffffffffff82111561418d5761418c613d24565b5b61419682613d14565b9050602081019050919050565b828183375f83830152505050565b5f6141c36141be84614173565b613d82565b9050828152602081018484840111156141df576141de61416f565b5b6141ea8482856141a3565b509392505050565b5f82601f83011261420657614205613d10565b5b81356142168482602086016141b1565b91505092915050565b5f805f806080858703121561423757614236613ba0565b5b5f85013567ffffffffffffffff81111561425457614253613ba4565b5b614260878288016141f2565b945050602061427187828801613ded565b935050604061428287828801613ded565b925050606061429387828801613ded565b91505092959194509250565b5f805f80608085870312156142b7576142b6613ba0565b5b5f6142c487828801613bcd565b94505060206142d587828801613bcd565b93505060406142e687828801613bcd565b92505060606142f787828801613bcd565b91505092959194509250565b5f805f806080858703121561431b5761431a613ba0565b5b5f61432887828801613cfc565b945050602061433987828801613cfc565b935050604061434a87828801613cfc565b925050606061435b87828801613ded565b91505092959194509250565b5f806040838503121561437d5761437c613ba0565b5b5f61438a85828601613cfc565b925050602061439b85828601613ded565b9150509250929050565b5f602082840312156143ba576143b9613ba0565b5b5f6143c784828501613cfc565b91505092915050565b5f819050919050565b6143e2816143d0565b82525050565b5f6020820190506143fb5f8301846143d9565b92915050565b61440a81613f16565b8114614414575f80fd5b50565b5f8135905061442581614401565b92915050565b5f805f805f60a0868803121561444457614443613ba0565b5b5f61445188828901613ded565b955050602061446288828901613ded565b945050604061447388828901613ded565b935050606061448488828901613ded565b925050608061449588828901614417565b9150509295509295909350565b5f805f80608085870312156144ba576144b9613ba0565b5b5f6144c787828801613cfc565b945050602085013567ffffffffffffffff8111156144e8576144e7613ba4565b5b6144f487828801613e69565b935050604061450587828801613cfc565b925050606061451687828801613cfc565b91505092959194509250565b5f805f806080858703121561453a57614539613ba0565b5b5f85013567ffffffffffffffff81111561455757614556613ba4565b5b614563878288016141f2565b945050602061457487828801613bcd565b935050604061458587828801613bcd565b925050606061459687828801613ded565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f614601826145cf565b61460b81856145d9565b935061461b8185602086016145e9565b61462481613d14565b840191505092915050565b61463881613cd5565b82525050565b5f8115159050919050565b6146528161463e565b82525050565b61466181613ba8565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61469981613c0c565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f6146c38261469f565b6146cd81856146a9565b93506146dd8185602086016145e9565b6146e681613d14565b840191505092915050565b5f60a083015f8301516147065f860182614649565b506020830151614719602086018261462f565b506040830151848203604086015261473182826146b9565b9150506060830151848203606086015261474b82826146b9565b9150506080830151614760608086018261462f565b508091505092915050565b5f604083015f8301516147805f860182614690565b506020830151848203602086015261479882826146f1565b9150508091505092915050565b5f6147b0838361476b565b905092915050565b5f602082019050919050565b5f6147ce82614667565b6147d88185614671565b9350836020820285016147ea85614681565b805f5b85811015614825578484038952815161480685826147a5565b9450614811836147b8565b925060208a019950506001810190506147ed565b50829750879550505050505092915050565b606082015f82015161484b5f850182614658565b50602082015161485e602085018261462f565b5060408201516148716040850182614658565b50505050565b5f61016083015f8301518482035f86015261489282826145f7565b915050602083015184820360208601526148ac82826145f7565b91505060408301516148c1604086018261462f565b50606083015184820360608601526148d982826145f7565b91505060808301516148ee6080860182614649565b5060a083015161490160a0860182614658565b5060c083015161491460c0860182614649565b5060e083015184820360e086015261492c82826147c4565b915050610100830151614943610100860182614837565b508091505092915050565b61495781613c0c565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60a082015f82015161499a5f850182614658565b5060208201516149ad602085018261462f565b5060408201516149c06040850182614649565b5060608201516149d36060850182614649565b5060808201516149e6608085018261462f565b50505050565b5f6149f78383614986565b60a08301905092915050565b5f602082019050919050565b5f614a198261495d565b614a238185614967565b9350614a2e83614977565b805f5b83811015614a5e578151614a4588826149ec565b9750614a5083614a03565b925050600181019050614a31565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f820151614aa85f850182614658565b506020820151614abb6020850182614658565b506040820151614ace6040850182614658565b506060820151614ae16060850182614658565b506080820151614af46080850182614649565b5060a0820151614b0760a085018261462f565b50505050565b5f614b188383614a94565b60c08301905092915050565b5f602082019050919050565b5f614b3a82614a6b565b614b448185614a75565b9350614b4f83614a85565b805f5b83811015614b7f578151614b668882614b0d565b9750614b7183614b24565b925050600181019050614b52565b5085935050505092915050565b5f60a0820190508181035f830152614ba48188614877565b9050614bb3602083018761494e565b614bc0604083018661494e565b8181036060830152614bd28185614a0f565b90508181036080830152614be68184614b30565b90509695505050505050565b5f81905092915050565b5f614c068261469f565b614c108185614bf2565b9350614c208185602086016145e9565b80840191505092915050565b5f614c378284614bfc565b915081905092915050565b5f81519050614c5081614401565b92915050565b5f614c6082613cb6565b9050919050565b614c7081614c56565b8114614c7a575f80fd5b50565b5f81519050614c8b81614c67565b92915050565b5f8060408385031215614ca757614ca6613ba0565b5b5f614cb485828601614c42565b9250506020614cc585828601614c7d565b9150509250929050565b5f6060820190508181035f830152614ce78186614877565b9050614cf6602083018561494e565b614d03604083018461494e565b949350505050565b614d1481613cd5565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f614d4e838361462f565b60208301905092915050565b5f602082019050919050565b5f614d7082614d1a565b614d7a8185614d24565b9350614d8583614d34565b805f5b83811015614db5578151614d9c8882614d43565b9750614da783614d5a565b925050600181019050614d88565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b614df481613dcb565b82525050565b5f614e058383614deb565b60208301905092915050565b5f602082019050919050565b5f614e2782614dc2565b614e318185614dcc565b9350614e3c83614ddc565b805f5b83811015614e6c578151614e538882614dfa565b9750614e5e83614e11565b925050600181019050614e3f565b5085935050505092915050565b5f608082019050614e8c5f830187614d0b565b8181036020830152614e9e8186614d66565b90508181036040830152614eb28185614d66565b90508181036060830152614ec68184614e1d565b905095945050505050565b5f60208284031215614ee657614ee5613ba0565b5b5f614ef384828501614c42565b91505092915050565b606082015f820151614f105f850182614deb565b506020820151614f23602085018261462f565b506040820151614f366040850182614deb565b50505050565b5f61016083015f8301518482035f860152614f5782826145f7565b91505060208301518482036020860152614f7182826145f7565b9150506040830151614f86604086018261462f565b5060608301518482036060860152614f9e82826145f7565b9150506080830151614fb36080860182614649565b5060a0830151614fc660a0860182614deb565b5060c0830151614fd960c0860182614649565b5060e083015184820360e0860152614ff182826147c4565b915050610100830151615008610100860182614efc565b508091505092915050565b5f6040820190506150265f830185614d0b565b81810360208301526150388184614f3c565b90509392505050565b5f6080820190506150545f830187614d0b565b6150616020830186614d0b565b61506e6040830185614d0b565b61507b606083018461494e565b95945050505050565b61508d81613dcb565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60a082015f8201516150d05f850182614deb565b5060208201516150e3602085018261462f565b5060408201516150f66040850182614649565b5060608201516151096060850182614649565b50608082015161511c608085018261462f565b50505050565b5f61512d83836150bc565b60a08301905092915050565b5f602082019050919050565b5f61514f82615093565b615159818561509d565b9350615164836150ad565b805f5b8381101561519457815161517b8882615122565b975061518683615139565b925050600181019050615167565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f8201516151de5f850182614deb565b5060208201516151f16020850182614deb565b5060408201516152046040850182614deb565b5060608201516152176060850182614deb565b50608082015161522a6080850182614649565b5060a082015161523d60a085018261462f565b50505050565b5f61524e83836151ca565b60c08301905092915050565b5f602082019050919050565b5f615270826151a1565b61527a81856151ab565b9350615285836151bb565b805f5b838110156152b557815161529c8882615243565b97506152a78361525a565b925050600181019050615288565b5085935050505092915050565b5f60a0820190508181035f8301526152da8188614f3c565b90506152e96020830187615084565b6152f66040830186615084565b81810360608301526153088185615145565b9050818103608083015261531c8184615266565b90509695505050505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b606082015f8201516153655f85018261462f565b5060208201516153786020850182614deb565b50604082015161538b6040850182614649565b50505050565b5f61539c8383615351565b60608301905092915050565b5f602082019050919050565b5f6153be82615328565b6153c88185615332565b93506153d383615342565b805f5b838110156154035781516153ea8882615391565b97506153f5836153a8565b9250506001810190506153d6565b5085935050505092915050565b5f602083015f8301518482035f86015261542a82826153b4565b9150508091505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b604082015f82015161549d5f85018261462f565b5060208201516154b06020850182614deb565b50505050565b5f6154c18383615489565b60408301905092915050565b5f602082019050919050565b5f6154e382615460565b6154ed818561546a565b93506154f88361547a565b805f5b8381101561552857815161550f88826154b6565b975061551a836154cd565b9250506001810190506154fb565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b606082015f8201516155725f85018261462f565b506020820151615585602085018261462f565b5060408201516155986040850182614deb565b50505050565b5f6155a9838361555e565b60608301905092915050565b5f602082019050919050565b5f6155cb82615535565b6155d5818561553f565b93506155e08361554f565b805f5b838110156156105781516155f7888261559e565b9750615602836155b5565b9250506001810190506155e3565b5085935050505092915050565b5f606083015f8301516156325f86018261462f565b506020830151848203602086015261564a82826154d9565b9150506040830151848203604086015261566482826155c1565b9150508091505092915050565b5f61567c838361561d565b905092915050565b5f602082019050919050565b5f61569a82615437565b6156a48185615441565b9350836020820285016156b685615451565b805f5b858110156156f157848403895281516156d28582615671565b94506156dd83615684565b925060208a019950506001810190506156b9565b50829750879550505050505092915050565b5f6040820190508181035f83015261571b8185615410565b9050818103602083015261572f8184615690565b90509392505050565b5f61016083015f8301518482035f86015261575382826145f7565b9150506020830151848203602086015261576d82826145f7565b9150506040830151615782604086018261462f565b506060830151848203606086015261579a82826145f7565b91505060808301516157af6080860182614649565b5060a08301516157c260a0860182614deb565b5060c08301516157d560c0860182614649565b5060e083015184820360e08601526157ed82826147c4565b915050610100830151615804610100860182614837565b508091505092915050565b615818816140e2565b82525050565b61582781613ba8565b82525050565b5f6060820190508181035f8301526158458186615738565b9050615854602083018561580f565b615861604083018461581e565b949350505050565b5f60608201905061587c5f830186614d0b565b6158896020830185614d0b565b615896604083018461494e565b949350505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f8201516158db5f850182614deb565b5060208201516158ee6020850182614deb565b5060408201516159016040850182614deb565b506060820151615914606085018261462f565b5060808201516159276080850182614649565b5060a082015161593a60a085018261462f565b50505050565b5f61594b83836158c7565b60c08301905092915050565b5f602082019050919050565b5f61596d8261589e565b61597781856158a8565b9350615982836158b8565b805f5b838110156159b25781516159998882615940565b97506159a483615957565b925050600181019050615985565b5085935050505092915050565b5f6060820190508181035f8301526159d78186614f3c565b905081810360208301526159eb8185615145565b905081810360408301526159ff8184615963565b9050949350505050565b5f608082019050615a1c5f830187614d0b565b615a296020830186614d0b565b615a366040830185614d0b565b615a436060830184615084565b95945050505050565b5f6020820190508181035f830152615a648184614f3c565b905092915050565b5f604082019050615a7f5f830185614d0b565b8181036020830152615a918184615738565b90509392505050565b5f602082019050615aad5f830184614d0b565b92915050565b615abc8161463e565b8114615ac6575f80fd5b50565b5f81519050615ad781615ab3565b92915050565b5f60208284031215615af257615af1613ba0565b5b5f615aff84828501615ac9565b91505092915050565b5f6020820190508181035f830152615b208184615690565b905092915050565b5f6060820190508181035f830152615b408186614f3c565b9050615b4f6020830185615084565b615b5c6040830184613f22565b949350505050565b5f81519050615b7281613dd7565b92915050565b5f60208284031215615b8d57615b8c613ba0565b5b5f615b9a84828501615b64565b91505092915050565b5f6020820190508181035f830152615bbb8184615738565b90509291505056fea26469706673582212205f72a0446fb80a8cc1ea7d5157bbb7b0ea4ab3b8c5bf9e3e8b034022973c97f364736f6c634300081a0033 \ No newline at end of file +6080604052348015600e575f80fd5b50615bfb8061001c5f395ff3fe608060405260043610610149575f3560e01c806349da8cd1116100b55780639b53f99b1161006e5780639b53f99b146103df578063b5fd634214610407578063c3cfee041461042f578063d325899b1461044b578063e8bb61e514610473578063ff2895691461048f57610149565b806349da8cd1146102e7578063517527ff1461030f5780635864cc291461032b5780636dadb99f146103535780637c41ad2c1461037b5780639b23d3d9146103b757610149565b806316e62c1e1161010757806316e62c1e146102275780631af22a9d146102435780632637d1611461026b5780633bdc30061461028757806343ef0166146102af5780634495ed93146102cb57610149565b8062c801471461014d578063067362ac146101695780630e43f0b4146101855780630e969a05146101ad57806314167e2e146101d757806315dacbea146101ff575b5f80fd5b61016760048036038101906101629190613be3565b610499565b005b610183600480360381019061017e9190613c41565b6106ad565b005b348015610190575f80fd5b506101ab60048036038101906101a69190613e98565b6107e7565b005b3480156101b8575f80fd5b506101c1610a50565b6040516101ce9190613f33565b60405180910390f35b3480156101e2575f80fd5b506101fd60048036038101906101f89190613f4c565b610a55565b005b34801561020a575f80fd5b5061022560048036038101906102209190613fb0565b610bb3565b005b610241600480360381019061023c9190614014565b610ce4565b005b34801561024e575f80fd5b5061026960048036038101906102649190614078565b610f44565b005b61028560048036038101906102809190614121565b6111f5565b005b348015610292575f80fd5b506102ad60048036038101906102a89190613fb0565b611331565b005b6102c960048036038101906102c49190614221565b61145e565b005b6102e560048036038101906102e091906142a1565b611709565b005b3480156102f2575f80fd5b5061030d60048036038101906103089190614305565b611a29565b005b61032960048036038101906103249190614221565b611b5a565b005b348015610336575f80fd5b50610351600480360381019061034c9190614369565b611ca7565b005b34801561035e575f80fd5b5061037960048036038101906103749190614305565b611dd5565b005b348015610386575f80fd5b506103a1600480360381019061039c91906143a7565b611f06565b6040516103ae91906143ea565b60405180910390f35b3480156103c2575f80fd5b506103dd60048036038101906103d89190613fb0565b612015565b005b3480156103ea575f80fd5b5061040560048036038101906104009190613fb0565b612146565b005b348015610412575f80fd5b5061042d60048036038101906104289190614305565b61226a565b005b6104496004803603810190610444919061442d565b612539565b005b348015610456575f80fd5b50610471600480360381019061046c91906144a4565b612673565b005b61048d60048036038101906104889190614524565b6129a8565b005b610497612af5565b005b5f6104aa5f622dc6c0612710612d29565b90505f600167ffffffffffffffff8111156104c8576104c7613d26565b5b60405190808252806020026020018201604052801561050157816020015b6104ee61369d565b8152602001906001900390816104e65790505b50905061050d83612f07565b815f815181106105205761051f6145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634634c381ae760e01b8660646002885f67ffffffffffffffff81111561056f5761056e613d26565b5b6040519080825280602002602001820160405280156105a857816020015b6105956136fd565b81526020019060019003908161058d5790505b506040516024016105bd959493929190614b8e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516106279190614c2e565b5f6040518083038185875af1925050503d805f8114610661576040519150601f19603f3d011682016040523d82523d5f602084013e610666565b606091505b50915091505f8083610679575f8061068e565b8280602001905181019061068d9190614c93565b5b91509150601660030b8260030b146106a4575f80fd5b50505050505050565b5f6106b9868686612d29565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff1634637812a04b60e01b8588886040516024016106f493929190614cd1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161075e9190614c2e565b5f6040518083038185875af1925050503d805f8114610798576040519150601f19603f3d011682016040523d82523d5f602084013e61079d565b606091505b50915091505f80836107b0575f806107c5565b828060200190518101906107c49190614c93565b5b91509150601660030b8260030b146107db575f80fd5b50505050505050505050565b5f600167ffffffffffffffff81111561080357610802613d26565b5b6040519080825280602002602001820160405280156108315781602001602082028036833780820191505090505b50905083815f81518110610848576108476145a4565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f600167ffffffffffffffff81111561089e5761089d613d26565b5b6040519080825280602002602001820160405280156108cc5781602001602082028036833780820191505090505b50905083815f815181106108e3576108e26145a4565b5b602002602001019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f8061016773ffffffffffffffffffffffffffffffffffffffff168885858860405160240161094f9493929190614e7b565b6040516020818303038152906040527f2c4ba191000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516109d99190614c2e565b5f604051808303815f865af19150503d805f8114610a12576040519150601f19603f3d011682016040523d82523d5f602084013e610a17565b606091505b50915091505f81806020019051810190610a319190614ed3565b9050601660030b8160030b14610a45575f80fd5b505050505050505050565b601681565b610a5d61375d565b60405180606001604052808560070b81526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018460070b8152508161010001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff16637d305cfa60e01b8885604051602401610ad3929190615015565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610b3d9190614c2e565b5f604051808303815f865af19150503d805f8114610b76576040519150601f19603f3d011682016040523d82523d5f602084013e610b7b565b606091505b50915091505f81806020019051810190610b959190614ed3565b9050601660030b8160030b14610ba9575f80fd5b5050505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401610be59493929190615043565b6040516020818303038152906040527f15dacbea000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610c6f9190614c2e565b5f604051808303815f865af19150503d805f8114610ca8576040519150601f19603f3d011682016040523d82523d5f602084013e610cad565b606091505b50915091505f81806020019051810190610cc79190614ed3565b9050601660030b8160030b14610cdb575f80fd5b50505050505050565b5f610cf6600a622dc6c0612710612f74565b90505f600167ffffffffffffffff811115610d1457610d13613d26565b5b604051908082528060200260200182016040528015610d4d57816020015b610d3a6137c8565b815260200190600190039081610d325790505b5090506040518060c001604052808760070b81526020018660070b81526020018560070b81526020018460070b81526020015f151581526020013073ffffffffffffffffffffffffffffffffffffffff16815250815f81518110610db457610db36145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634632af0c59a60e01b86606460025f67ffffffffffffffff811115610e0257610e01613d26565b5b604051908082528060200260200182016040528015610e3b57816020015b610e2861381c565b815260200190600190039081610e205790505b5089604051602401610e519594939291906152c4565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051610ebb9190614c2e565b5f6040518083038185875af1925050503d805f8114610ef5576040519150601f19603f3d011682016040523d82523d5f602084013e610efa565b606091505b50915091505f8083610f0d575f80610f22565b82806020019051810190610f219190614c93565b5b91509150601660030b8260030b14610f38575f80fd5b50505050505050505050565b5f600267ffffffffffffffff811115610f6057610f5f613d26565b5b604051908082528060200260200182016040528015610f9957816020015b610f86613879565b815260200190600190039081610f7e5790505b50905060405180606001604052808473ffffffffffffffffffffffffffffffffffffffff168152602001855f81518110610fd657610fd56145a4565b5b602002602001015160070b81526020015f1515815250815f81518110610fff57610ffe6145a4565b5b602002602001018190525060405180606001604052808373ffffffffffffffffffffffffffffffffffffffff16815260200185600181518110611045576110446145a4565b5b602002602001015160070b81526020015f15158152508160018151811061106f5761106e6145a4565b5b60200260200101819052505f60405180602001604052808381525090505f8061016773ffffffffffffffffffffffffffffffffffffffff16630e71804f60e01b845f67ffffffffffffffff8111156110ca576110c9613d26565b5b60405190808252806020026020018201604052801561110357816020015b6110f06138b2565b8152602001906001900390816110e85790505b50604051602401611115929190615705565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161117f9190614c2e565b5f604051808303815f865af19150503d805f81146111b8576040519150601f19603f3d011682016040523d82523d5f602084013e6111bd565b606091505b50915091505f818060200190518101906111d79190614ed3565b9050601660030b8160030b146111eb575f80fd5b5050505050505050565b5f611205600a622dc6c08661310b565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff163463c23baeb660e01b8588886040516024016112409392919061582f565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516112aa9190614c2e565b5f6040518083038185875af1925050503d805f81146112e4576040519150601f19603f3d011682016040523d82523d5f602084013e6112e9565b606091505b50915091505f80836112fc575f80611311565b828060200190518101906113109190614c93565b5b91509150601660030b8260030b14611327575f80fd5b5050505050505050565b5f808573ffffffffffffffffffffffffffffffffffffffff1685858560405160240161135f9392919061586b565b6040516020818303038152906040527fbeabacc8000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516113e99190614c2e565b5f604051808303815f865af19150503d805f8114611422576040519150601f19603f3d011682016040523d82523d5f602084013e611427565b606091505b50915091505f818060200190518101906114419190614ed3565b9050601660030b8160030b14611455575f80fd5b50505050505050565b5f611472620f4240622dc6c0612710612f74565b90505f6114806003876132e6565b9050808260e001819052505f600167ffffffffffffffff8111156114a7576114a6613d26565b5b6040519080825280602002602001820160405280156114e057816020015b6114cd6138e8565b8152602001906001900390816114c55790505b5090506114eb6138e8565b86815f019060070b908160070b8152505085816020019060070b908160070b8152505084816040019060070b908160070b815250506001816080019015159081151581525050308160a0019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff168152505080825f8151811061157d5761157c6145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff163463abb54eb560e01b885f67ffffffffffffffff8111156115c7576115c6613d26565b5b60405190808252806020026020018201604052801561160057816020015b6115ed61381c565b8152602001906001900390816115e55790505b5088604051602401611614939291906159c1565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161167e9190614c2e565b5f6040518083038185875af1925050503d805f81146116b8576040519150601f19603f3d011682016040523d82523d5f602084013e6116bd565b606091505b50915091505f80836116d0575f806116e5565b828060200190518101906116e49190614c93565b5b91509150601660030b8260030b146116fb575f80fd5b505050505050505050505050565b5f6117178585612710612d29565b90505f600167ffffffffffffffff81111561173557611734613d26565b5b60405190808252806020026020018201604052801561176e57816020015b61175b6136fd565b8152602001906001900390816117535790505b50905083815f81518110611785576117846145a4565b5b60200260200101515f019063ffffffff16908163ffffffff168152505082815f815181106117b6576117b56145a4565b5b60200260200101516020019063ffffffff16908163ffffffff16815250506001815f815181106117e9576117e86145a4565b5b6020026020010151608001901515908115158152505030815f81518110611813576118126145a4565b5b602002602001015160a0019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505f8061016773ffffffffffffffffffffffffffffffffffffffff1634634c381ae760e01b86606460025f67ffffffffffffffff81111561189457611893613d26565b5b6040519080825280602002602001820160405280156118cd57816020015b6118ba61369d565b8152602001906001900390816118b25790505b505f67ffffffffffffffff8111156118e8576118e7613d26565b5b60405190808252806020026020018201604052801561192157816020015b61190e6136fd565b8152602001906001900390816119065790505b50604051602401611936959493929190614b8e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516119a09190614c2e565b5f6040518083038185875af1925050503d805f81146119da576040519150601f19603f3d011682016040523d82523d5f602084013e6119df565b606091505b50915091505f80836119f2575f80611a07565b82806020019051810190611a069190614c93565b5b91509150601660030b8260030b14611a1d575f80fd5b50505050505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401611a5b9493929190615a0b565b6040516020818303038152906040527feca36917000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611ae59190614c2e565b5f604051808303815f865af19150503d805f8114611b1e576040519150601f19603f3d011682016040523d82523d5f602084013e611b23565b606091505b50915091505f81806020019051810190611b3d9190614ed3565b9050601660030b8160030b14611b51575f80fd5b50505050505050565b5f611b66848484612f74565b90505f611b746003876132e6565b9050808260e001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff163463ea83f29360e01b86604051602401611bb49190615a4e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611c1e9190614c2e565b5f6040518083038185875af1925050503d805f8114611c58576040519150601f19603f3d011682016040523d82523d5f602084013e611c5d565b606091505b50915091505f8083611c70575f80611c85565b82806020019051810190611c849190614c93565b5b91509150601660030b8260030b14611c9b575f80fd5b50505050505050505050565b611caf61394f565b818160a0019060070b908160070b815250505f8061016773ffffffffffffffffffffffffffffffffffffffff166318370d3460e01b8685604051602401611cf7929190615a6e565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611d619190614c2e565b5f604051808303815f865af19150503d805f8114611d9a576040519150601f19603f3d011682016040523d82523d5f602084013e611d9f565b606091505b50915091505f81806020019051810190611db99190614ed3565b9050601660030b8160030b14611dcd575f80fd5b505050505050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff1686868686604051602401611e079493929190615a0b565b6040516020818303038152906040527f5cfc9011000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611e919190614c2e565b5f604051808303815f865af19150503d805f8114611eca576040519150601f19603f3d011682016040523d82523d5f602084013e611ecf565b606091505b50915091505f81806020019051810190611ee99190614ed3565b9050601660030b8160030b14611efd575f80fd5b50505050505050565b5f805f61016773ffffffffffffffffffffffffffffffffffffffff16637c41ad2c60e01b85604051602401611f3b9190615a9c565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051611fa59190614c2e565b5f604051808303815f865af19150503d805f8114611fde576040519150601f19603f3d011682016040523d82523d5f602084013e611fe3565b606091505b509150915081611ff4576015612009565b808060200190518101906120089190614ed3565b5b60030b92505050919050565b5f8061016773ffffffffffffffffffffffffffffffffffffffff16868686866040516024016120479493929190615043565b6040516020818303038152906040527f9b23d3d9000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516120d19190614c2e565b5f604051808303815f865af19150503d805f811461210a576040519150601f19603f3d011682016040523d82523d5f602084013e61210f565b606091505b50915091505f818060200190518101906121299190614ed3565b9050601660030b8160030b1461213d575f80fd5b50505050505050565b5f808573ffffffffffffffffffffffffffffffffffffffff168585856040516024016121749392919061586b565b6040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516121fe9190614c2e565b5f604051808303815f865af19150503d805f8114612237576040519150601f19603f3d011682016040523d82523d5f602084013e61223c565b606091505b50915091505f818060200190518101906122569190615adf565b905080612261575f80fd5b50505050505050565b5f600167ffffffffffffffff81111561228657612285613d26565b5b6040519080825280602002602001820160405280156122bf57816020015b6122ac6139ba565b8152602001906001900390816122a45790505b50905060405180606001604052808573ffffffffffffffffffffffffffffffffffffffff1681526020018473ffffffffffffffffffffffffffffffffffffffff1681526020018360070b815250815f8151811061231f5761231e6145a4565b5b60200260200101819052505f600167ffffffffffffffff81111561234657612345613d26565b5b60405190808252806020026020018201604052801561237f57816020015b61236c6138b2565b8152602001906001900390816123645790505b50905060405180606001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020015f67ffffffffffffffff8111156123c3576123c2613d26565b5b6040519080825280602002602001820160405280156123fc57816020015b6123e9613a07565b8152602001906001900390816123e15790505b50815260200183815250815f81518110612419576124186145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1663189a554c60e01b846040516024016124589190615b0a565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516124c29190614c2e565b5f604051808303815f865af19150503d805f81146124fb576040519150601f19603f3d011682016040523d82523d5f602084013e612500565b606091505b50915091505f8180602001905181019061251a9190614ed3565b9050601660030b8160030b1461252e575f80fd5b505050505050505050565b5f612545868686612f74565b90505f8061016773ffffffffffffffffffffffffffffffffffffffff1634630fb65bf360e01b85888860405160240161258093929190615b2a565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516125ea9190614c2e565b5f6040518083038185875af1925050503d805f8114612624576040519150601f19603f3d011682016040523d82523d5f602084013e612629565b606091505b50915091505f808361263c575f80612651565b828060200190518101906126509190614c93565b5b91509150601660030b8260030b14612667575f80fd5b50505050505050505050565b5f600267ffffffffffffffff81111561268f5761268e613d26565b5b6040519080825280602002602001820160405280156126c857816020015b6126b5613a07565b8152602001906001900390816126ad5790505b50905060405180604001604052808473ffffffffffffffffffffffffffffffffffffffff168152602001855f81518110612705576127046145a4565b5b602002602001015160070b815250815f81518110612726576127256145a4565b5b602002602001018190525060405180604001604052808373ffffffffffffffffffffffffffffffffffffffff1681526020018560018151811061276c5761276b6145a4565b5b602002602001015160070b8152508160018151811061278e5761278d6145a4565b5b60200260200101819052505f600167ffffffffffffffff8111156127b5576127b4613d26565b5b6040519080825280602002602001820160405280156127ee57816020015b6127db6138b2565b8152602001906001900390816127d35790505b50905060405180606001604052808773ffffffffffffffffffffffffffffffffffffffff1681526020018381526020015f67ffffffffffffffff81111561283857612837613d26565b5b60405190808252806020026020018201604052801561287157816020015b61285e6139ba565b8152602001906001900390816128565790505b50815250815f81518110612888576128876145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1663189a554c60e01b846040516024016128c79190615b0a565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506040516129319190614c2e565b5f604051808303815f865af19150503d805f811461296a576040519150601f19603f3d011682016040523d82523d5f602084013e61296f565b606091505b50915091505f818060200190518101906129899190615b7a565b9050601660030b8160070b1461299d575f80fd5b505050505050505050565b5f6129b484848461310b565b90505f6129c26003876132e6565b9050808260e001819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634639c89bb3560e01b86604051602401612a029190615ba5565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612a6c9190614c2e565b5f6040518083038185875af1925050503d805f8114612aa6576040519150601f19603f3d011682016040523d82523d5f602084013e612aab565b606091505b50915091505f8083612abe575f80612ad3565b82806020019051810190612ad29190614c93565b5b91509150601660030b8260030b14612ae9575f80fd5b50505050505050505050565b5f612b07600a622dc6c0612710612f74565b90505f600167ffffffffffffffff811115612b2557612b24613d26565b5b604051908082528060200260200182016040528015612b5e57816020015b612b4b61381c565b815260200190600190039081612b435790505b509050612b8a7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff613388565b815f81518110612b9d57612b9c6145a4565b5b60200260200101819052505f8061016773ffffffffffffffffffffffffffffffffffffffff1634632af0c59a60e01b8660646002885f67ffffffffffffffff811115612bec57612beb613d26565b5b604051908082528060200260200182016040528015612c2557816020015b612c126137c8565b815260200190600190039081612c0a5790505b50604051602401612c3a9594939291906152c4565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff8381831617835250505050604051612ca49190614c2e565b5f6040518083038185875af1925050503d805f8114612cde576040519150601f19603f3d011682016040523d82523d5f602084013e612ce3565b606091505b50915091505f8083612cf6575f80612d0b565b82806020019051810190612d0a9190614c93565b5b91509150601660030b8260030b14612d21575f80fd5b505050505050565b612d31613a38565b5f600167ffffffffffffffff811115612d4d57612d4c613d26565b5b604051908082528060200260200182016040528015612d8657816020015b612d73613aa6565b815260200190600190039081612d6b5790505b509050612da35f600160405180602001604052805f8152506133f3565b815f81518110612db657612db56145a4565b5b60200260200101819052506040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018463ffffffff1681526020015f1515815260200182815260200160405180606001604052808863ffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018763ffffffff168152508152509150509392505050565b612f0f61369d565b81815f019063ffffffff16908163ffffffff1681525050600181604001901515908115158152505033816080019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050919050565b612f7c61375d565b6040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018360070b81526020015f151581526020015f67ffffffffffffffff81111561308957613088613d26565b5b6040519080825280602002602001820160405280156130c257816020015b6130af613aa6565b8152602001906001900390816130a75790505b50815260200160405180606001604052808760070b81526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018660070b81525081525090509392505050565b61311361394f565b5f600167ffffffffffffffff81111561312f5761312e613d26565b5b60405190808252806020026020018201604052801561316857816020015b613155613aa6565b81526020019060019003908161314d5790505b5090506131855f600160405180602001604052805f8152506133f3565b815f81518110613198576131976145a4565b5b60200260200101819052506040518061012001604052806040518060400160405280600481526020017f4e414d450000000000000000000000000000000000000000000000000000000081525081526020016040518060400160405280600681526020017f53594d424f4c000000000000000000000000000000000000000000000000000081525081526020013073ffffffffffffffffffffffffffffffffffffffff1681526020016040518060400160405280600481526020017f4d454d4f0000000000000000000000000000000000000000000000000000000081525081526020016001151581526020018460070b81526020015f1515815260200182815260200160405180606001604052808863ffffffff1681526020013073ffffffffffffffffffffffffffffffffffffffff1681526020018763ffffffff168152508152509150509392505050565b6060600167ffffffffffffffff81111561330357613302613d26565b5b60405190808252806020026020018201604052801561333c57816020015b613329613aa6565b8152602001906001900390816133215790505b509050604051806040016040528061335261342a565b815260200161336185856134c7565b815250815f81518110613377576133766145a4565b5b602002602001018190525092915050565b61339061381c565b6040518060a001604052808360070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020016001151581526020016001151581526020013373ffffffffffffffffffffffffffffffffffffffff168152509050919050565b6133fb613aa6565b604051806040016040528061340f866135f5565b815260200161341e85856134c7565b81525090509392505050565b5f61343e5f8261368a90919063ffffffff16565b905061345460018261368a90919063ffffffff16565b905061346a60028261368a90919063ffffffff16565b905061348060038261368a90919063ffffffff16565b905061349660048261368a90919063ffffffff16565b90506134ac60058261368a90919063ffffffff16565b90506134c260068261368a90919063ffffffff16565b905090565b6134cf613ac5565b60018360ff16036134ef576001815f0190151590811515815250506135ef565b60028360ff1603613556575f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff16816020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250506135ee565b60038360ff160361356f578181604001819052506135ed565b60048360ff1603613588578181606001819052506135ec565b60058360ff16036135eb575f8054906101000a900473ffffffffffffffffffffffffffffffffffffffff16816080019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff16815250505b5b5b5b5b92915050565b5f808260ff16036136095760019050613685565b60018260ff160361361d5760029050613685565b60028260ff16036136315760049050613685565b60038260ff16036136455760089050613685565b60048260ff16036136595760109050613685565b60058260ff160361366d5760209050613685565b60068260ff16036136815760409050613685565b5f90505b919050565b5f8160ff166001901b8317905092915050565b6040518060a001604052805f63ffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060c001604052805f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681526020015f63ffffffff1681526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f15158152602001606081526020016137c2613b1f565b81525090565b6040518060c001604052805f60070b81526020015f60070b81526020015f60070b81526020015f60070b81526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b6040518060a001604052805f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81526020015f151581525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff16815260200160608152602001606081525090565b6040518060c001604052805f60070b81526020015f60070b81526020015f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f60070b81526020015f15158152602001606081526020016139b4613b59565b81525090565b60405180606001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180604001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180610120016040528060608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff168152602001606081526020015f151581526020015f63ffffffff1681526020015f1515815260200160608152602001613aa0613b59565b81525090565b60405180604001604052805f8152602001613abf613ac5565b81525090565b6040518060a001604052805f151581526020015f73ffffffffffffffffffffffffffffffffffffffff16815260200160608152602001606081526020015f73ffffffffffffffffffffffffffffffffffffffff1681525090565b60405180606001604052805f60070b81526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f60070b81525090565b60405180606001604052805f63ffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f63ffffffff1681525090565b5f604051905090565b5f80fd5b5f80fd5b5f63ffffffff82169050919050565b613bc281613baa565b8114613bcc575f80fd5b50565b5f81359050613bdd81613bb9565b92915050565b5f60208284031215613bf857613bf7613ba2565b5b5f613c0584828501613bcf565b91505092915050565b5f819050919050565b613c2081613c0e565b8114613c2a575f80fd5b50565b5f81359050613c3b81613c17565b92915050565b5f805f805f60a08688031215613c5a57613c59613ba2565b5b5f613c6788828901613bcf565b9550506020613c7888828901613bcf565b9450506040613c8988828901613bcf565b9350506060613c9a88828901613c2d565b9250506080613cab88828901613c2d565b9150509295509295909350565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f613ce182613cb8565b9050919050565b613cf181613cd7565b8114613cfb575f80fd5b50565b5f81359050613d0c81613ce8565b92915050565b5f80fd5b5f601f19601f8301169050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b613d5c82613d16565b810181811067ffffffffffffffff82111715613d7b57613d7a613d26565b5b80604052505050565b5f613d8d613b99565b9050613d998282613d53565b919050565b5f67ffffffffffffffff821115613db857613db7613d26565b5b602082029050602081019050919050565b5f80fd5b5f8160070b9050919050565b613de281613dcd565b8114613dec575f80fd5b50565b5f81359050613dfd81613dd9565b92915050565b5f613e15613e1084613d9e565b613d84565b90508083825260208201905060208402830185811115613e3857613e37613dc9565b5b835b81811015613e615780613e4d8882613def565b845260208401935050602081019050613e3a565b5050509392505050565b5f82601f830112613e7f57613e7e613d12565b5b8135613e8f848260208601613e03565b91505092915050565b5f805f8060808587031215613eb057613eaf613ba2565b5b5f613ebd87828801613cfe565b9450506020613ece87828801613cfe565b9350506040613edf87828801613cfe565b925050606085013567ffffffffffffffff811115613f0057613eff613ba6565b5b613f0c87828801613e6b565b91505092959194509250565b5f8160030b9050919050565b613f2d81613f18565b82525050565b5f602082019050613f465f830184613f24565b92915050565b5f805f8060808587031215613f6457613f63613ba2565b5b5f613f7187828801613cfe565b9450506020613f8287828801613def565b9350506040613f9387828801613def565b9250506060613fa487828801613def565b91505092959194509250565b5f805f8060808587031215613fc857613fc7613ba2565b5b5f613fd587828801613cfe565b9450506020613fe687828801613cfe565b9350506040613ff787828801613cfe565b925050606061400887828801613c2d565b91505092959194509250565b5f805f806080858703121561402c5761402b613ba2565b5b5f61403987828801613def565b945050602061404a87828801613def565b935050604061405b87828801613def565b925050606061406c87828801613def565b91505092959194509250565b5f805f6060848603121561408f5761408e613ba2565b5b5f84013567ffffffffffffffff8111156140ac576140ab613ba6565b5b6140b886828701613e6b565b93505060206140c986828701613cfe565b92505060406140da86828701613cfe565b9150509250925092565b5f67ffffffffffffffff82169050919050565b614100816140e4565b811461410a575f80fd5b50565b5f8135905061411b816140f7565b92915050565b5f805f6060848603121561413857614137613ba2565b5b5f61414586828701613def565b93505060206141568682870161410d565b925050604061416786828701613bcf565b9150509250925092565b5f80fd5b5f67ffffffffffffffff82111561418f5761418e613d26565b5b61419882613d16565b9050602081019050919050565b828183375f83830152505050565b5f6141c56141c084614175565b613d84565b9050828152602081018484840111156141e1576141e0614171565b5b6141ec8482856141a5565b509392505050565b5f82601f83011261420857614207613d12565b5b81356142188482602086016141b3565b91505092915050565b5f805f806080858703121561423957614238613ba2565b5b5f85013567ffffffffffffffff81111561425657614255613ba6565b5b614262878288016141f4565b945050602061427387828801613def565b935050604061428487828801613def565b925050606061429587828801613def565b91505092959194509250565b5f805f80608085870312156142b9576142b8613ba2565b5b5f6142c687828801613bcf565b94505060206142d787828801613bcf565b93505060406142e887828801613bcf565b92505060606142f987828801613bcf565b91505092959194509250565b5f805f806080858703121561431d5761431c613ba2565b5b5f61432a87828801613cfe565b945050602061433b87828801613cfe565b935050604061434c87828801613cfe565b925050606061435d87828801613def565b91505092959194509250565b5f806040838503121561437f5761437e613ba2565b5b5f61438c85828601613cfe565b925050602061439d85828601613def565b9150509250929050565b5f602082840312156143bc576143bb613ba2565b5b5f6143c984828501613cfe565b91505092915050565b5f819050919050565b6143e4816143d2565b82525050565b5f6020820190506143fd5f8301846143db565b92915050565b61440c81613f18565b8114614416575f80fd5b50565b5f8135905061442781614403565b92915050565b5f805f805f60a0868803121561444657614445613ba2565b5b5f61445388828901613def565b955050602061446488828901613def565b945050604061447588828901613def565b935050606061448688828901613def565b925050608061449788828901614419565b9150509295509295909350565b5f805f80608085870312156144bc576144bb613ba2565b5b5f6144c987828801613cfe565b945050602085013567ffffffffffffffff8111156144ea576144e9613ba6565b5b6144f687828801613e6b565b935050604061450787828801613cfe565b925050606061451887828801613cfe565b91505092959194509250565b5f805f806080858703121561453c5761453b613ba2565b5b5f85013567ffffffffffffffff81111561455957614558613ba6565b5b614565878288016141f4565b945050602061457687828801613bcf565b935050604061458787828801613bcf565b925050606061459887828801613def565b91505092959194509250565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f614603826145d1565b61460d81856145db565b935061461d8185602086016145eb565b61462681613d16565b840191505092915050565b61463a81613cd7565b82525050565b5f8115159050919050565b61465481614640565b82525050565b61466381613baa565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b61469b81613c0e565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f6146c5826146a1565b6146cf81856146ab565b93506146df8185602086016145eb565b6146e881613d16565b840191505092915050565b5f60a083015f8301516147085f86018261464b565b50602083015161471b6020860182614631565b506040830151848203604086015261473382826146bb565b9150506060830151848203606086015261474d82826146bb565b91505060808301516147626080860182614631565b508091505092915050565b5f604083015f8301516147825f860182614692565b506020830151848203602086015261479a82826146f3565b9150508091505092915050565b5f6147b2838361476d565b905092915050565b5f602082019050919050565b5f6147d082614669565b6147da8185614673565b9350836020820285016147ec85614683565b805f5b85811015614827578484038952815161480885826147a7565b9450614813836147ba565b925060208a019950506001810190506147ef565b50829750879550505050505092915050565b606082015f82015161484d5f85018261465a565b5060208201516148606020850182614631565b506040820151614873604085018261465a565b50505050565b5f61016083015f8301518482035f86015261489482826145f9565b915050602083015184820360208601526148ae82826145f9565b91505060408301516148c36040860182614631565b50606083015184820360608601526148db82826145f9565b91505060808301516148f0608086018261464b565b5060a083015161490360a086018261465a565b5060c083015161491660c086018261464b565b5060e083015184820360e086015261492e82826147c6565b915050610100830151614945610100860182614839565b508091505092915050565b61495981613c0e565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60a082015f82015161499c5f85018261465a565b5060208201516149af6020850182614631565b5060408201516149c2604085018261464b565b5060608201516149d5606085018261464b565b5060808201516149e86080850182614631565b50505050565b5f6149f98383614988565b60a08301905092915050565b5f602082019050919050565b5f614a1b8261495f565b614a258185614969565b9350614a3083614979565b805f5b83811015614a60578151614a4788826149ee565b9750614a5283614a05565b925050600181019050614a33565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f820151614aaa5f85018261465a565b506020820151614abd602085018261465a565b506040820151614ad0604085018261465a565b506060820151614ae3606085018261465a565b506080820151614af6608085018261464b565b5060a0820151614b0960a0850182614631565b50505050565b5f614b1a8383614a96565b60c08301905092915050565b5f602082019050919050565b5f614b3c82614a6d565b614b468185614a77565b9350614b5183614a87565b805f5b83811015614b81578151614b688882614b0f565b9750614b7383614b26565b925050600181019050614b54565b5085935050505092915050565b5f60a0820190508181035f830152614ba68188614879565b9050614bb56020830187614950565b614bc26040830186614950565b8181036060830152614bd48185614a11565b90508181036080830152614be88184614b32565b90509695505050505050565b5f81905092915050565b5f614c08826146a1565b614c128185614bf4565b9350614c228185602086016145eb565b80840191505092915050565b5f614c398284614bfe565b915081905092915050565b5f81519050614c5281614403565b92915050565b5f614c6282613cb8565b9050919050565b614c7281614c58565b8114614c7c575f80fd5b50565b5f81519050614c8d81614c69565b92915050565b5f8060408385031215614ca957614ca8613ba2565b5b5f614cb685828601614c44565b9250506020614cc785828601614c7f565b9150509250929050565b5f6060820190508181035f830152614ce98186614879565b9050614cf86020830185614950565b614d056040830184614950565b949350505050565b614d1681613cd7565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f614d508383614631565b60208301905092915050565b5f602082019050919050565b5f614d7282614d1c565b614d7c8185614d26565b9350614d8783614d36565b805f5b83811015614db7578151614d9e8882614d45565b9750614da983614d5c565b925050600181019050614d8a565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b614df681613dcd565b82525050565b5f614e078383614ded565b60208301905092915050565b5f602082019050919050565b5f614e2982614dc4565b614e338185614dce565b9350614e3e83614dde565b805f5b83811015614e6e578151614e558882614dfc565b9750614e6083614e13565b925050600181019050614e41565b5085935050505092915050565b5f608082019050614e8e5f830187614d0d565b8181036020830152614ea08186614d68565b90508181036040830152614eb48185614d68565b90508181036060830152614ec88184614e1f565b905095945050505050565b5f60208284031215614ee857614ee7613ba2565b5b5f614ef584828501614c44565b91505092915050565b606082015f820151614f125f850182614ded565b506020820151614f256020850182614631565b506040820151614f386040850182614ded565b50505050565b5f61016083015f8301518482035f860152614f5982826145f9565b91505060208301518482036020860152614f7382826145f9565b9150506040830151614f886040860182614631565b5060608301518482036060860152614fa082826145f9565b9150506080830151614fb5608086018261464b565b5060a0830151614fc860a0860182614ded565b5060c0830151614fdb60c086018261464b565b5060e083015184820360e0860152614ff382826147c6565b91505061010083015161500a610100860182614efe565b508091505092915050565b5f6040820190506150285f830185614d0d565b818103602083015261503a8184614f3e565b90509392505050565b5f6080820190506150565f830187614d0d565b6150636020830186614d0d565b6150706040830185614d0d565b61507d6060830184614950565b95945050505050565b61508f81613dcd565b82525050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60a082015f8201516150d25f850182614ded565b5060208201516150e56020850182614631565b5060408201516150f8604085018261464b565b50606082015161510b606085018261464b565b50608082015161511e6080850182614631565b50505050565b5f61512f83836150be565b60a08301905092915050565b5f602082019050919050565b5f61515182615095565b61515b818561509f565b9350615166836150af565b805f5b8381101561519657815161517d8882615124565b97506151888361513b565b925050600181019050615169565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f8201516151e05f850182614ded565b5060208201516151f36020850182614ded565b5060408201516152066040850182614ded565b5060608201516152196060850182614ded565b50608082015161522c608085018261464b565b5060a082015161523f60a0850182614631565b50505050565b5f61525083836151cc565b60c08301905092915050565b5f602082019050919050565b5f615272826151a3565b61527c81856151ad565b9350615287836151bd565b805f5b838110156152b757815161529e8882615245565b97506152a98361525c565b92505060018101905061528a565b5085935050505092915050565b5f60a0820190508181035f8301526152dc8188614f3e565b90506152eb6020830187615086565b6152f86040830186615086565b818103606083015261530a8185615147565b9050818103608083015261531e8184615268565b90509695505050505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b606082015f8201516153675f850182614631565b50602082015161537a6020850182614ded565b50604082015161538d604085018261464b565b50505050565b5f61539e8383615353565b60608301905092915050565b5f602082019050919050565b5f6153c08261532a565b6153ca8185615334565b93506153d583615344565b805f5b838110156154055781516153ec8882615393565b97506153f7836153aa565b9250506001810190506153d8565b5085935050505092915050565b5f602083015f8301518482035f86015261542c82826153b6565b9150508091505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b604082015f82015161549f5f850182614631565b5060208201516154b26020850182614ded565b50505050565b5f6154c3838361548b565b60408301905092915050565b5f602082019050919050565b5f6154e582615462565b6154ef818561546c565b93506154fa8361547c565b805f5b8381101561552a57815161551188826154b8565b975061551c836154cf565b9250506001810190506154fd565b5085935050505092915050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b606082015f8201516155745f850182614631565b5060208201516155876020850182614631565b50604082015161559a6040850182614ded565b50505050565b5f6155ab8383615560565b60608301905092915050565b5f602082019050919050565b5f6155cd82615537565b6155d78185615541565b93506155e283615551565b805f5b838110156156125781516155f988826155a0565b9750615604836155b7565b9250506001810190506155e5565b5085935050505092915050565b5f606083015f8301516156345f860182614631565b506020830151848203602086015261564c82826154db565b9150506040830151848203604086015261566682826155c3565b9150508091505092915050565b5f61567e838361561f565b905092915050565b5f602082019050919050565b5f61569c82615439565b6156a68185615443565b9350836020820285016156b885615453565b805f5b858110156156f357848403895281516156d48582615673565b94506156df83615686565b925060208a019950506001810190506156bb565b50829750879550505050505092915050565b5f6040820190508181035f83015261571d8185615412565b905081810360208301526157318184615692565b90509392505050565b5f61016083015f8301518482035f86015261575582826145f9565b9150506020830151848203602086015261576f82826145f9565b91505060408301516157846040860182614631565b506060830151848203606086015261579c82826145f9565b91505060808301516157b1608086018261464b565b5060a08301516157c460a0860182614ded565b5060c08301516157d760c086018261464b565b5060e083015184820360e08601526157ef82826147c6565b915050610100830151615806610100860182614839565b508091505092915050565b61581a816140e4565b82525050565b61582981613baa565b82525050565b5f6060820190508181035f830152615847818661573a565b90506158566020830185615811565b6158636040830184615820565b949350505050565b5f60608201905061587e5f830186614d0d565b61588b6020830185614d0d565b6158986040830184614950565b949350505050565b5f81519050919050565b5f82825260208201905092915050565b5f819050602082019050919050565b60c082015f8201516158dd5f850182614ded565b5060208201516158f06020850182614ded565b5060408201516159036040850182614ded565b5060608201516159166060850182614631565b506080820151615929608085018261464b565b5060a082015161593c60a0850182614631565b50505050565b5f61594d83836158c9565b60c08301905092915050565b5f602082019050919050565b5f61596f826158a0565b61597981856158aa565b9350615984836158ba565b805f5b838110156159b457815161599b8882615942565b97506159a683615959565b925050600181019050615987565b5085935050505092915050565b5f6060820190508181035f8301526159d98186614f3e565b905081810360208301526159ed8185615147565b90508181036040830152615a018184615965565b9050949350505050565b5f608082019050615a1e5f830187614d0d565b615a2b6020830186614d0d565b615a386040830185614d0d565b615a456060830184615086565b95945050505050565b5f6020820190508181035f830152615a668184614f3e565b905092915050565b5f604082019050615a815f830185614d0d565b8181036020830152615a93818461573a565b90509392505050565b5f602082019050615aaf5f830184614d0d565b92915050565b615abe81614640565b8114615ac8575f80fd5b50565b5f81519050615ad981615ab5565b92915050565b5f60208284031215615af457615af3613ba2565b5b5f615b0184828501615acb565b91505092915050565b5f6020820190508181035f830152615b228184615692565b905092915050565b5f6060820190508181035f830152615b428186614f3e565b9050615b516020830185615086565b615b5e6040830184613f24565b949350505050565b5f81519050615b7481613dd9565b92915050565b5f60208284031215615b8f57615b8e613ba2565b5b5f615b9c84828501615b66565b91505092915050565b5f6020820190508181035f830152615bbd818461573a565b90509291505056fea2646970667358221220ac905ca27430056976ae15cae1b1e3e0bb9e9fd5ac38706cb51f3abea980d9d364736f6c634300081a0033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.sol index 6009337fc18d..c6e8d7459d0c 100644 --- a/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.sol +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/NumericContractComplex/NumericContractComplex.sol @@ -178,7 +178,7 @@ contract NumericContractComplex is KeyHelper { function createNonFungibleTokenWithCustomRoyaltyFeesV3(bytes memory key, int64 numerator, int64 denominator, int64 amount) public payable { Structs.HederaTokenV3 memory token = buildTokenV3({ - expirySecond: 1, expiryRenew: 3_000_000, maxSupply: 10000}); + expirySecond: 1_000_000, expiryRenew: 3_000_000, maxSupply: 10000}); IHederaTokenService.TokenKey[] memory keys = getAllTypeKeys(3, key); token.tokenKeys = keys; @@ -361,7 +361,7 @@ contract NumericContractComplex is KeyHelper { function transferTokenERC(address token, address sender, address receiver, uint256 amount) public { (bool success, bytes memory result) = - address(token).call(abi.encodeWithSignature("transfer(address,address,uint256)", token, receiver, amount)); + address(token).call(abi.encodeWithSignature("transfer(address,address,uint256)", sender, receiver, amount)); int32 responseCode = abi.decode(result, (int32)); require(responseCode == SUCCESS_CODE); From f51f65174d7de87d5cebf66e5f1de683e7607e37 Mon Sep 17 00:00:00 2001 From: anthony-swirldslabs <152534762+anthony-swirldslabs@users.noreply.github.com> Date: Fri, 13 Sep 2024 14:08:07 -0700 Subject: [PATCH 16/16] feat: introduce PbjRecordHasher and RosterUtils.hash(Roster) (#15457) Signed-off-by: Anthony Petrov Signed-off-by: anthony-swirldslabs <152534762+anthony-swirldslabs@users.noreply.github.com> Co-authored-by: Joseph S. <121976561+jsync-swirlds@users.noreply.github.com> --- .../swirlds/platform/roster/RosterUtils.java | 16 +++++ .../platform/util/PbjRecordHasher.java | 70 +++++++++++++++++++ .../platform/roster/RosterUtilsTest.java | 45 ++++++++++++ .../platform/roster/RosterValidatorTests.java | 12 ++-- .../platform/util/PbjRecordHasherTest.java | 42 +++++++++++ 5 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/PbjRecordHasher.java create mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterUtilsTest.java create mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/PbjRecordHasherTest.java diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java index 837b9526151b..56fab834e360 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/roster/RosterUtils.java @@ -16,12 +16,17 @@ package com.swirlds.platform.roster; +import com.hedera.hapi.node.state.roster.Roster; +import com.swirlds.common.crypto.Hash; +import com.swirlds.platform.util.PbjRecordHasher; import edu.umd.cs.findbugs.annotations.NonNull; /** * A utility class to help use Rooster and RosterEntry instances. */ public final class RosterUtils { + private static final PbjRecordHasher PBJ_RECORD_HASHER = new PbjRecordHasher(); + private RosterUtils() {} /** @@ -36,4 +41,15 @@ private RosterUtils() {} public static String formatNodeName(final long nodeId) { return "node" + (nodeId + 1); } + + /** + * Create a Hash object for a given Roster instance. + * + * @param roster a roster + * @return its Hash + */ + @NonNull + public static Hash hash(@NonNull final Roster roster) { + return PBJ_RECORD_HASHER.hash(roster, Roster.PROTOBUF); + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/PbjRecordHasher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/PbjRecordHasher.java new file mode 100644 index 000000000000..2b820d6716a2 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/util/PbjRecordHasher.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.swirlds.platform.util; + +import com.hedera.pbj.runtime.Codec; +import com.hedera.pbj.runtime.io.WritableSequentialData; +import com.hedera.pbj.runtime.io.stream.WritableStreamingData; +import com.swirlds.common.crypto.DigestType; +import com.swirlds.common.crypto.Hash; +import com.swirlds.common.crypto.HashingOutputStream; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; +import java.security.MessageDigest; + +/** + * A hasher for PBJ records. + * + * An instance of this class can be used to hash multiple records sequentially, + * on a single thread or with external synchronization. + * The implementation is not thread-safe. The caller code is responsible for synchronization. + */ +public class PbjRecordHasher { + // The choice of the digest type may be reevaluated in the future based on hashing capabilities of various runtimes + // (e.g. the EVM.) If necessary, the digest type may become a parameter to this class at that time. + // However, currently we use the same digest type that is used throughout the majority of this code base. + private static final DigestType DIGEST_TYPE = DigestType.SHA_384; + + private final MessageDigest digest = DIGEST_TYPE.buildDigest(); + private final WritableSequentialData stream = new WritableStreamingData(new HashingOutputStream(digest)); + + /** + * Computes a Hash object for a given PBJ record and its codec. + * + * The caller code must ensure the codec is appropriate for the given record. For convenience, + * PBJ offers a public static final PROTOBUF reference to a Codec instance in every model class. + * + * It is expected that the codec produces a deterministic representation for the record + * to make the computed hash value stable and useful. PBJ's Protobuf codecs are deterministic. + * + * @param the type of the records to hash. Must be a PBJ model type that extends Record. + * @param record a PBJ model + * @param codec a codec for the given model + * @return a Hash object + */ + @NonNull + public Hash hash(@NonNull final T record, @NonNull final Codec codec) { + try { + codec.write(record, stream); + } catch (final IOException e) { + throw new RuntimeException("An exception occurred while trying to hash a record!", e); + } + // Reminder, MessageDigest.digest resets the digest, so subsequent writes + // will calculate an independent hash value. + return new Hash(digest.digest(), DIGEST_TYPE); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterUtilsTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterUtilsTest.java new file mode 100644 index 000000000000..7748e85e0578 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterUtilsTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.swirlds.platform.roster; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.swirlds.common.crypto.Hash; +import org.junit.jupiter.api.Test; + +public class RosterUtilsTest { + @Test + void tesetHash() { + final Hash hash = RosterUtils.hash(Roster.DEFAULT); + assertEquals( + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + hash.toString()); + + final Hash anotherHash = RosterUtils.hash( + Roster.DEFAULT.copyBuilder().rosterEntries(RosterEntry.DEFAULT).build()); + assertEquals( + "5d693ce2c5d445194faee6054b4d8fe4a4adc1225cf0afc2ecd7866ea895a0093ea3037951b75ab7340b75699aa1db1d", + anotherHash.toString()); + + final Hash validRosterHash = RosterUtils.hash(RosterValidatorTests.buildValidRoster()); + assertEquals( + "1b8414aa690d96ce79e972abfc58c7ca04052996f89c5e6789b25b9051ee85fccb7c8ed3fc6ebacef177adfdcbbb5709", + validRosterHash.toString()); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java index 38edd21a1e64..a5a3c0c7500c 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/roster/RosterValidatorTests.java @@ -401,9 +401,8 @@ void invalidIPv4Test() { ex.getMessage()); } - @Test - void validTest() { - RosterValidator.validate(Roster.newBuilder() + static Roster buildValidRoster() { + return Roster.newBuilder() .rosterEntries( RosterEntry.newBuilder() .nodeId(1) @@ -435,6 +434,11 @@ void validTest() { .port(666) .build()) .build()) - .build()); + .build(); + } + + @Test + void validTest() { + RosterValidator.validate(buildValidRoster()); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/PbjRecordHasherTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/PbjRecordHasherTest.java new file mode 100644 index 000000000000..400c6c9eec44 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/util/PbjRecordHasherTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * 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 com.swirlds.platform.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.swirlds.common.crypto.Hash; +import org.junit.jupiter.api.Test; + +public class PbjRecordHasherTest { + @Test + void testHash() { + final PbjRecordHasher hasher = new PbjRecordHasher(); + + final Hash hash = hasher.hash(Roster.DEFAULT, Roster.PROTOBUF); + assertEquals( + "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", + hash.toString()); + + final Hash anotherHash = hasher.hash( + Roster.DEFAULT.copyBuilder().rosterEntries(RosterEntry.DEFAULT).build(), Roster.PROTOBUF); + assertEquals( + "5d693ce2c5d445194faee6054b4d8fe4a4adc1225cf0afc2ecd7866ea895a0093ea3037951b75ab7340b75699aa1db1d", + anotherHash.toString()); + } +}