From f8127d5ddd3640f56578da5c52f83b056117b4bd Mon Sep 17 00:00:00 2001 From: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:25:48 +0000 Subject: [PATCH 01/39] chore: Schedule release 0.58 branch (#17058) Signed-off-by: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> --- .github/workflows/config/node-release.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/config/node-release.yaml b/.github/workflows/config/node-release.yaml index fd24a53f35db..18a52d366e68 100644 --- a/.github/workflows/config/node-release.yaml +++ b/.github/workflows/config/node-release.yaml @@ -1,11 +1,11 @@ release: branching: execution: - time: "20:00:00" + time: "18:00:00" schedule: - - on: "2024-11-22" - name: release/0.57 + - on: "2024-12-13" + name: release/0.58 initial-tag: create: true - name: v0.57.0-alpha.0 + name: v0.58.0-alpha.0 From 9a350d8c3350a1bac6794e980b629d772b4b0760 Mon Sep 17 00:00:00 2001 From: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:46:54 +0000 Subject: [PATCH 02/39] chore: 17060 bump services version 0.58 (#17061) Signed-off-by: Thomas Moran <152873392+thomas-swirlds-labs@users.noreply.github.com> --- version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.txt b/version.txt index 46448c71b9df..a60476bfe1c7 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.57.0 +0.58.0 From e20b66ae32012a018bf82ae4913966966ce2a00c Mon Sep 17 00:00:00 2001 From: Alex Kuzmin Date: Fri, 13 Dec 2024 09:57:30 -0800 Subject: [PATCH 03/39] test: fixed 17050 swirlds-merkledb:timingSensitive (#17051) Signed-off-by: Alex Kuzmin --- .../merkledb/files/DataFileCollectionTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/platform-sdk/swirlds-merkledb/src/timingSensitive/java/com/swirlds/merkledb/files/DataFileCollectionTest.java b/platform-sdk/swirlds-merkledb/src/timingSensitive/java/com/swirlds/merkledb/files/DataFileCollectionTest.java index 07ee00499c97..0a3f7f000e1d 100644 --- a/platform-sdk/swirlds-merkledb/src/timingSensitive/java/com/swirlds/merkledb/files/DataFileCollectionTest.java +++ b/platform-sdk/swirlds-merkledb/src/timingSensitive/java/com/swirlds/merkledb/files/DataFileCollectionTest.java @@ -679,14 +679,15 @@ void mergeWorksAfterOpen(final FilesTestType testType) throws Exception { fileCompactor.compactFiles(storedOffsets, fileCollection2.getAllCompletedFiles(), 1); // check 1 files were opened and data is correct assertSame(1, fileCollection2.getAllCompletedFiles().size(), "Should be 1 files"); - try (Stream list = Files.list(dbDir)) { - assertEquals( - 1, - list.filter(file -> file.getFileName().toString().matches(storeName + ".*pbj")) - .filter(f -> !f.toString().contains("metadata")) - .count(), - "expected 1 db files but had [" + Arrays.toString(list.toArray()) + "]"); - } + assertEquals( + 1, + Files.list(dbDir) + .filter(file -> file.getFileName().toString().matches(storeName + ".*pbj")) + .filter(f -> !f.toString().contains("metadata")) + .count(), + "expected 1 db files but had [" + + Arrays.toString(Files.list(dbDir).toArray()) + + "]"); checkData(fileCollectionMap.get(testType), storedOffsetsMap.get(testType), testType, 0, 1000, 10_000); // close db fileCollection2.close(); From d0ac51ba84ab4b5931728fdf3f99cf41ba38eb6c Mon Sep 17 00:00:00 2001 From: Pavel Borisov <37436896+PavelSBorisov@users.noreply.github.com> Date: Mon, 16 Dec 2024 14:54:43 +0200 Subject: [PATCH 04/39] chore: add mirror node team to a dir in codeowners (#17053) Signed-off-by: PavelSBorisov --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 807ab3ee3500..0b5c0029e05e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -16,7 +16,7 @@ ######################### /hapi/ @hashgraph/hedera-services @hashgraph/hedera-smart-contracts-core @hashgraph/platform-hashgraph @hashgraph/platform-data @hashgraph/platform-base @hashgraph/platform-architects -/hapi/hedera-protobufs/services @hashgraph/hedera-services @hashgraph/hedera-smart-contracts-core @jsync-swirlds +/hapi/hedera-protobufs/services @hashgraph/hedera-services @hashgraph/hedera-smart-contracts-core @jsync-swirlds @hashgraph/mirror-node ######################### From 293a28d4d605805ffdfdabc74f665acb2cf1f94d Mon Sep 17 00:00:00 2001 From: Mihail Mihov Date: Mon, 16 Dec 2024 16:18:36 +0200 Subject: [PATCH 05/39] ci: Change secrets for protobufs access (#17071) Signed-off-by: Mihail Mihov --- .github/workflows/node-flow-deploy-release-artifact.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node-flow-deploy-release-artifact.yaml b/.github/workflows/node-flow-deploy-release-artifact.yaml index 836f754e54f0..50e13ca7da0d 100644 --- a/.github/workflows/node-flow-deploy-release-artifact.yaml +++ b/.github/workflows/node-flow-deploy-release-artifact.yaml @@ -201,7 +201,7 @@ jobs: - name: Checkout Hedera Protobufs Code uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: - token: ${{ secrets.GH_ACCESS_TOKEN }} + token: ${{ secrets.PROTOBUFS_GH_ACCESS_TOKEN }} fetch-depth: '0' repository: hashgraph/hedera-protobufs path: hedera-protobufs @@ -220,8 +220,8 @@ jobs: id: gpg_import uses: step-security/ghaction-import-gpg@6c8fe4d0126a59d57c21f87c9ae5dd3451fa3cca # v6.1.0 with: - gpg_private_key: ${{ secrets.SVCS_GPG_KEY_CONTENTS }} - passphrase: ${{ secrets.SVCS_GPG_KEY_PASSPHRASE }} + gpg_private_key: ${{ secrets.PROTOBUFS_GPG_KEY_CONTENTS }} + passphrase: ${{ secrets.PROTOBUFS_GPG_KEY_PASSPHRASE }} git_user_signingkey: true git_commit_gpgsign: true git_tag_gpgsign: true @@ -231,7 +231,7 @@ jobs: with: cwd: 'hedera-protobufs' author_name: swirlds-eng-automation - author_email: ${{ secrets.SVCS_GIT_USER_EMAIL }} + author_email: ${{ secrets.PROTOBUFS_GPG_USER_EMAIL }} commit: --signoff message: "ci: Copied recent protobuf changes from hedera-services" new_branch: "update-recent-protobuf-changes-${{ github.run_number }}" From 4460aa8241da6ed0ba289ce712089823433229f2 Mon Sep 17 00:00:00 2001 From: Michael Tinker Date: Mon, 16 Dec 2024 12:18:52 -0600 Subject: [PATCH 06/39] chore: Add `@RestartHapiTest` (#16843) Signed-off-by: Neeharika-Sompalli Signed-off-by: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Signed-off-by: Michael Tinker Signed-off-by: Matt Hess Co-authored-by: Neeharika-Sompalli Co-authored-by: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Co-authored-by: Joseph S. <121976561+jsync-swirlds@users.noreply.github.com> Co-authored-by: Matt Hess --- .../block/stream/output/state_changes.proto | 10 +- .../state/tss/tss_encryption_keys.proto | 51 +++ .../services/state/tss/tss_status.proto | 117 ------- .../java/com/hedera/hapi/util/HapiUtils.java | 2 - .../configuration/dev/genesis-network.json | 54 --- .../impl/ReadableNodeStoreImpl.java | 6 +- .../impl/helpers/AddressBookHelper.java | 2 +- .../schemas/AddressBookTransplantSchema.java | 63 ++++ .../impl/schemas/V053AddressBookSchema.java | 10 +- .../impl/schemas/V057AddressBookSchema.java | 44 +-- .../schemas/V057AddressBookSchemaTest.java | 16 +- .../impl/test/AddressBookServiceImplTest.java | 2 + .../test/handlers/AddressBookTestBase.java | 3 + .../schemas/V053AddressBookSchemaTest.java | 8 +- .../spi/fixtures/info/FakeNetworkInfo.java | 12 +- .../main/java/com/hedera/node/app/Hedera.java | 85 ++--- .../com/hedera/node/app/ServicesMain.java | 313 ++++++++++-------- .../com/hedera/node/app/StartupAssets.java | 52 --- .../blocks/impl/BlockStreamManagerImpl.java | 7 +- .../impl/BoundaryStateChangeListener.java | 4 - .../blocks/impl/KVStateChangeListener.java | 6 +- .../schemas/V0560BlockStreamSchema.java | 2 +- .../node/app/fees/schemas/V0490FeeSchema.java | 2 +- .../app/ids/schemas/V0490EntityIdSchema.java | 2 +- .../node/app/info/DiskStartupNetworks.java | 146 ++++++-- .../node/app/info/GenesisNetworkInfo.java | 66 +--- .../hedera/node/app/info/NodeInfoImpl.java | 2 +- .../node/app/info/StateNetworkInfo.java | 65 ++-- .../node/app/info/UnavailableNetworkInfo.java | 6 - .../hedera/node/app/roster/RosterService.java | 18 +- .../schemas/RosterTransplantSchema.java | 66 ++++ .../app/roster/schemas/V0540RosterSchema.java | 134 ++++++++ .../app/roster/schemas/V057RosterSchema.java | 156 --------- .../app/services/MigrationContextImpl.java | 8 +- .../app/services/OrderedServiceMigrator.java | 22 +- .../node/app/services/ServiceMigrator.java | 8 +- .../state/merkle/MerkleSchemaRegistry.java | 19 +- .../hedera/node/app/tss/TssBaseService.java | 7 +- .../node/app/tss/TssBaseServiceImpl.java | 38 ++- .../node/app/tss/TssCryptographyManager.java | 1 + .../node/app/tss/TssDirectoryAccessor.java | 56 +++- .../hedera/node/app/tss/TssKeysAccessor.java | 2 +- .../app/tss/handlers/TssMessageHandler.java | 2 +- .../node/app/tss/handlers/TssUtils.java | 39 ++- .../tss/schemas/TssBaseTransplantSchema.java | 122 +++++++ .../app/tss/schemas/V0570TssBaseSchema.java | 76 ----- .../app/tss/schemas/V0580TssBaseSchema.java | 58 ++++ .../node/app/tss/stores/ReadableTssStore.java | 13 +- .../app/tss/stores/ReadableTssStoreImpl.java | 21 +- .../node/app/tss/stores/WritableTssStore.java | 16 +- .../app/workflows/handle/HandleWorkflow.java | 2 +- .../impl/StandaloneNetworkInfo.java | 6 - .../hedera/node/app/StartupAssetsTest.java | 117 ------- .../schemas/V0560BlockStreamSchemaTest.java | 2 +- .../app/info/DiskStartupNetworksTest.java | 70 +++- .../node/app/info/StateNetworkInfoTest.java | 36 +- .../node/app/roster/RosterServiceTest.java | 18 +- .../roster/schemas/V0540RosterSchemaTest.java | 219 ++++++++++-- .../roster/schemas/V057RosterSchemaTest.java | 232 ------------- .../app/state/merkle/SerializationTest.java | 4 - .../node/app/tss/TssBaseServiceImplTest.java | 16 +- .../tss/handlers/TssMessageHandlerTest.java | 2 +- .../node/app/tss/handlers/TssUtilsTest.java | 21 +- .../schemas/TssBaseTransplantSchemaTest.java | 175 ++++++++++ .../app/tss/stores/ReadableTssStoreTest.java | 33 +- .../app/tss/stores/WritableTssStoreTest.java | 19 +- .../handle/record/BlockRecordManagerTest.java | 11 +- .../handle/steps/NodeStakeUpdatesTest.java | 2 +- .../steps/PlatformStateUpdatesTest.java | 6 +- .../standalone/TransactionExecutorsTest.java | 27 +- .../src/test/resources/bootstrap/network.json | 12 +- .../hedera/node/app/fixtures/AppTestBase.java | 14 +- .../fixtures/state/FakeSchemaRegistry.java | 32 +- .../fixtures/state/FakeServiceMigrator.java | 18 +- .../hedera/node/config/data/TssConfig.java | 2 +- .../file/impl/schemas/V0490FileSchema.java | 2 +- .../impl/handlers/HandlerUtility.java | 2 +- .../token/impl/schemas/V0490TokenSchema.java | 10 +- .../token/impl/schemas/V0530TokenSchema.java | 25 +- .../test/schemas/V0490TokenSchemaTest.java | 5 + .../test/schemas/V0530TokenSchemaTest.java | 1 + ...strapOverride.java => ConfigOverride.java} | 4 +- .../services/bdd/junit/GenesisHapiTest.java | 2 +- .../bdd/junit/extensions/ExtensionUtils.java | 2 + .../extensions/NetworkTargetingExtension.java | 136 +++++++- .../bdd/junit/hedera/AbstractLocalNode.java | 68 +++- .../bdd/junit/hedera/HederaNetwork.java | 15 +- .../services/bdd/junit/hedera/HederaNode.java | 31 +- .../bdd/junit/hedera/TssKeyMaterial.java | 31 ++ .../embedded/AbstractEmbeddedHedera.java | 86 +++-- .../embedded/ConcurrentEmbeddedHedera.java | 4 +- .../junit/hedera/embedded/EmbeddedHedera.java | 14 + .../hedera/embedded/EmbeddedNetwork.java | 58 +++- .../embedded/RepeatableEmbeddedHedera.java | 9 +- .../embedded/fakes/AbstractFakePlatform.java | 8 +- .../embedded/fakes/FakeTssBaseService.java | 4 +- .../bdd/junit/hedera/remote/RemoteNode.java | 16 + .../hedera/subprocess/SubProcessNetwork.java | 4 + .../hedera/subprocess/SubProcessNode.java | 8 - .../junit/hedera/utils/AddressBookUtils.java | 71 ++++ .../junit/hedera/utils/WorkingDirUtils.java | 95 +++++- .../bdd/junit/restart/NoopSavedStateSpec.java | 32 ++ .../bdd/junit/restart/RestartHapiTest.java | 86 +++++ .../bdd/junit/restart/RestartType.java | 35 ++ .../bdd/junit/restart/SavedStateSpec.java | 26 ++ .../bdd/junit/restart/StartupAssets.java | 40 +++ .../support/translators/BaseTranslator.java | 36 +- .../BlockTransactionalUnitTranslator.java | 1 + .../block/StateChangesValidator.java | 139 ++++---- .../hedera/services/bdd/spec/HapiSpec.java | 2 +- .../bdd/spec/utilops/tss/RekeyScenarioOp.java | 20 +- .../suites/hip423/LongTermScheduleUtils.java | 2 +- .../bdd/suites/hip869/NodeCreateTest.java | 4 +- .../ConcurrentIntegrationTests.java | 8 +- .../integration/RepeatableHip423Tests.java | 3 - ...RepeatableRosterLifecycleRestartTests.java | 44 +++ .../src/main/java/module-info.java | 2 +- .../resources/hapi-test-gossip-certs.json | 7 + .../swirlds/platform/roster/RosterUtils.java | 13 + .../platform/state/GenesisStateBuilder.java | 85 ----- .../state/PlatformMerkleStateRoot.java | 2 +- .../state/service/PlatformStateService.java | 59 ++-- .../state/service/WritableRosterStore.java | 14 +- .../schemas/V0540PlatformStateSchema.java | 85 ++++- ...Schema.java => V0540RosterBaseSchema.java} | 6 +- .../schemas/V057PlatformStateSchema.java | 109 ------ .../V058RosterLifecycleTransitionSchema.java | 106 ++++++ .../state/signed/StartupStateUtils.java | 64 +++- .../state/snapshot/SignedStateFileReader.java | 4 +- .../service/PlatformStateServiceTest.java | 12 +- .../schemas/V057PlatformStateSchemaTest.java | 189 ----------- ...58RosterLifecycleTransitionSchemaTest.java | 146 ++++++++ .../state/FakeMerkleStateLifecycles.java | 12 +- .../state/lifecycle/MigrationContext.java | 46 ++- .../state/lifecycle/info/NetworkInfo.java | 7 - .../state/lifecycle/info/NodeInfo.java | 34 +- 136 files changed, 3146 insertions(+), 2181 deletions(-) create mode 100644 hapi/hedera-protobufs/services/state/tss/tss_encryption_keys.proto delete mode 100644 hapi/hedera-protobufs/services/state/tss/tss_status.proto delete mode 100644 hedera-node/configuration/dev/genesis-network.json create mode 100644 hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/AddressBookTransplantSchema.java delete mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/StartupAssets.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/RosterTransplantSchema.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java delete mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V057RosterSchema.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchema.java delete mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0570TssBaseSchema.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0580TssBaseSchema.java delete mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/StartupAssetsTest.java delete mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V057RosterSchemaTest.java create mode 100644 hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchemaTest.java rename hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/{BootstrapOverride.java => ConfigOverride.java} (89%) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/TssKeyMaterial.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/NoopSavedStateSpec.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartHapiTest.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartType.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/SavedStateSpec.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/StartupAssets.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableRosterLifecycleRestartTests.java create mode 100644 hedera-node/test-clients/src/main/resources/hapi-test-gossip-certs.json delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java rename platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/{V0540RosterSchema.java => V0540RosterBaseSchema.java} (95%) delete mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchema.java create mode 100644 platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchema.java delete mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchemaTest.java create mode 100644 platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchemaTest.java diff --git a/hapi/hedera-protobufs/block/stream/output/state_changes.proto b/hapi/hedera-protobufs/block/stream/output/state_changes.proto index 924f8dfdbe9d..28040854aeab 100644 --- a/hapi/hedera-protobufs/block/stream/output/state_changes.proto +++ b/hapi/hedera-protobufs/block/stream/output/state_changes.proto @@ -64,12 +64,11 @@ import "state/token/token.proto"; import "state/token/token_relation.proto"; import "state/platform_state.proto"; import "timestamp.proto"; -import "auxiliary/tss/tss_encryption_key.proto"; import "auxiliary/tss/tss_message.proto"; import "auxiliary/tss/tss_vote.proto"; +import "state/tss/tss_encryption_keys.proto"; import "state/tss/tss_message_map_key.proto"; import "state/tss/tss_vote_map_key.proto"; -import "state/tss/tss_status.proto"; /** * A set of state changes. @@ -609,11 +608,6 @@ message SingletonUpdateChange { * A change to the roster state singleton. */ com.hedera.hapi.node.state.roster.RosterState roster_state_value = 13; - - /** - * A change to the tss status state singleton. - */ - com.hedera.hapi.node.state.tss.TssStatus tss_status_state_value = 14; } } @@ -892,7 +886,7 @@ message MapChangeValue { /** * The value of a map that stores tss encryption keys for each node. */ - com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody tss_encryption_key_value = 20; + com.hedera.hapi.node.state.tss.TssEncryptionKeys tss_encryption_keys_value = 20; /** * The value of a map that stores tss messages submitted for each share of nodes. diff --git a/hapi/hedera-protobufs/services/state/tss/tss_encryption_keys.proto b/hapi/hedera-protobufs/services/state/tss/tss_encryption_keys.proto new file mode 100644 index 000000000000..0b65d892e3e9 --- /dev/null +++ b/hapi/hedera-protobufs/services/state/tss/tss_encryption_keys.proto @@ -0,0 +1,51 @@ +/** + * # Current and next TSS encryption keys + * + * ### Keywords + * The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + * "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + * document are to be interpreted as described in + * [RFC2119](https://www.ietf.org/rfc/rfc2119) and clarified in + * [RFC8174](https://www.ietf.org/rfc/rfc8174). + */ +syntax = "proto3"; + +package com.hedera.hapi.node.state.tss; + +/* + * 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. + */ + +option java_package = "com.hedera.hapi.node.state.tss.legacy"; +// <<>> This comment is special code for setting PBJ Compiler java package +option java_multiple_files = true; + +/** + * A message containing a node's current and next TSS encryption keys, where + * the next key (if present) will be switched to the node's current key during + * the first transaction at the beginning of a staking period.
+ */ +message TssEncryptionKeys { + + /** + * If non-empty, a node's current TSS encryption key. + */ + bytes current_encryption_key = 1; + + /** + * If non-empty, the same node's next TSS encryption key. + */ + bytes next_encryption_key = 2; +} diff --git a/hapi/hedera-protobufs/services/state/tss/tss_status.proto b/hapi/hedera-protobufs/services/state/tss/tss_status.proto deleted file mode 100644 index 362d23a5a998..000000000000 --- a/hapi/hedera-protobufs/services/state/tss/tss_status.proto +++ /dev/null @@ -1,117 +0,0 @@ -/** - * # Tss Message Map Key - * - * ### Keywords - * The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", - * "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this - * document are to be interpreted as described in - * [RFC2119](https://www.ietf.org/rfc/rfc2119) and clarified in - * [RFC8174](https://www.ietf.org/rfc/rfc8174). - */ -syntax = "proto3"; - -package com.hedera.hapi.node.state.tss; - -/* - * 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. - */ - -option java_package = "com.hedera.hapi.node.state.tss.legacy"; -// <<>> This comment is special code for setting PBJ Compiler java package -option java_multiple_files = true; - -/** - * A Singleton state object that represents the status of the TSS keying process. - * - * This key SHALL be used to determine the stage of the TSS keying process. - */ -message TssStatus { - - /** - * An enum representing the status of the TSS keying process.
- *

- * This status SHALL be used to determine the state of the TSS keying process.
- * This value MUST be set when tss is enabled. - */ - TssKeyingStatus tss_keying_status = 1; - - /** - * An enum representing the key either active roster or candidate roster.
- * This value will be to key active roster if it is genesis stage - *

- * This value MUST be set. - */ - RosterToKey roster_to_key = 2; - - /** - * A hash of the ledger_id resulting from the TSS keying process.
- * If this value is empty, the TSS keying process has not yet completed. - *

- * This value COULD be empty.
- * This value MUST contain a valid hash after the TSS keying process is complete.
- */ - bytes ledger_id = 3; -} - -/** - * An enum representing the status of the TSS keying process. - * - * This status SHALL be used to determine the state of the TSS keying process. - */ -enum TssKeyingStatus { - - /** - * The TSS keying process has not yet reached the threshold for encryption - * keys. - */ - WAITING_FOR_ENCRYPTION_KEYS = 0; - - /** - * The TSS keying process has not yet reached the threshold for TSS messages. - */ - WAITING_FOR_THRESHOLD_TSS_MESSAGES = 1; - - /** - * The TSS keying process has not yet reached the threshold for TSS votes. - */ - WAITING_FOR_THRESHOLD_TSS_VOTES = 2; - - /** - * The TSS keying process has completed and the ledger id is set. - */ - KEYING_COMPLETE = 3; -} - -/** - * An enum representing the key either active roster or candidate roster. - * This value will be to key active roster if it is genesis stage. - */ -enum RosterToKey { - - /** - * Key the active roster. This is true when we are keying roster on genesis stage. - */ - ACTIVE_ROSTER = 0; - - /** - * Key the candidate roster. This is true when we are keying roster on non-genesis stage. - */ - CANDIDATE_ROSTER = 1; - - /** - * Key none of the roster. This is true when we are not keying any roster. - */ - NONE = 2; -} diff --git a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java index efc3714b9809..d239ca16cd65 100644 --- a/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java +++ b/hapi/src/main/java/com/hedera/hapi/util/HapiUtils.java @@ -35,14 +35,12 @@ import java.util.Comparator; import java.util.EnumSet; import java.util.Set; -import java.util.regex.Pattern; /** * Utility class for working with the HAPI. We might move this to the HAPI project. */ public class HapiUtils { private static final int EVM_ADDRESS_ALIAS_LENGTH = 20; - public static final Pattern ALPHA_PRE_PATTERN = Pattern.compile("alpha[.](\\d+)"); public static final Key EMPTY_KEY_LIST = Key.newBuilder().keyList(KeyList.DEFAULT).build(); public static final long FUNDING_ACCOUNT_EXPIRY = 33197904000L; diff --git a/hedera-node/configuration/dev/genesis-network.json b/hedera-node/configuration/dev/genesis-network.json deleted file mode 100644 index 9e1c4ab07ec0..000000000000 --- a/hedera-node/configuration/dev/genesis-network.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "nodeMetadata": [{ - "rosterEntry": { - "weight": "1", - "gossipCaCertificate": "MIIDpzCCAg+gAwIBAgIJAK05TS8KZeb1MA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTEwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTEwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDBoP9dI3K1PRLRK7h90D9eNCfgzuHTyJi70yDEs90XJXlE6jmgf1NE2av83VAhQHLxu8Ehc/55M9Ayx9IQc0zJLSS+IrRM9QwqoG8ZvNdRgNw+je3V/8rAK/mHId+cPnnyDplCyskyi5kWCv6kTULIewFH8/KVZwhe0/hB2+N6ujWixURrxjjGLHA6b2gPoGAb/nxiVOn+L0cWcOzcyiYShxagj0FBWV7AxKx65Ynzfe7eF0gOzBUA+IM10OM5KXJejk53Xz5KpEyGe8htO/bXFlpLdm3UzrYiIhY0oKPYKECAC1s+VAZA6i+MV0nDpqDgxHRRXD8O2arauPhEI6iVT9f05AtzElrs7U95HbpQUuP1sxkaQw+bLdMOQHHMVCgMgw2g0eDdVDAMJD7wjZ+Bs6kDc/EJELb0l1uy2GEnOZMiHkK4K1r4IyZ/ed6QpyIRKfBCNyT5IIpMoVpzRYxVXgjgFdudd8iErKyvSXHThU6nu92c+vSd+FLBFHPpb6ECAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAdga5NYtV48uDCd4vIsmpGWpKuUHtDVDlCvzHc2ij8DxAR6OFp+hIRNEBXkzg1KS5qP8Wba5ptmGoV4f89HemP+AL3Azde+HjpYRtffdfTdQwmMbw7xJg2lKkEo11gDo5+zPZnVbfb3FsZ+IXKji0QshQBfg+ddTkFG3TJG1ttq3ZDw94RxFQivVnkj1p+Ogel/DuBNRWQobFVe5VrmJqbuwwN8AdrPae1dMrkZatF91On5+cpVLGfk96fYUhDohDt6KKQ6DdhvFk5rhd0vsHGMQq2gAW2+Or6ZVsKkHKx8CPINpJVKAdpE0tItI+loMO02jf9oRI/8cThWP1vNAeWnr0D6m275EZf/4qem/DdJ0FJIVou3P7tsq7eSdueDnj5RmcbW/vOBtvlXpD3SqsVRn6sltZ0sk24p+6ZMzopevCZEMf/nL3OzGvSadisXb39H9DgwkNLlefju1QLgHWf0TGfeNHluDgVDhU8+/1/KUGtr2SnZ5EVO1l59FWHALj", - "gossipEndpoint": [{ - "ipAddressV4": "fwAAAQ==", - "port": 35372 - }, { - "ipAddressV4": "fwAAAQ==", - "port": 35371 - }] - } - }, { - "rosterEntry": { - "nodeId": "1", - "weight": "1", - "gossipCaCertificate": "MIIDpjCCAg6gAwIBAgIIHWg7e2Q/smQwDQYJKoZIhvcNAQEMBQAwEjEQMA4GA1UEAxMHcy1ub2RlMjAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowEjEQMA4GA1UEAxMHcy1ub2RlMjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKr5WsBepS3+y/0/yfBjzMWje7zianEz7sszrNWV3cGu2KUlR7v2+9wp/EtX1+BdcGlTTojgFs5nEBN4lM76Cp6JjFH461yN8GSkIkpe8GZnb1w4KEjZj5UYMbq+qOUI6QmwmgLeO8RHAsS6lCP1AyGFalb2ZVJ09DcYDxCRXeFj4BqvNbtD5r5DTCtpVT4ax3eb3pzNSGsjQUG9zhyp/WcsAmwmzKdMl72tk6qF8tlAWXyzwiCujWHS0Kln0C5pyEjeFNsG299toC4pgT8juxijgseTeIFRnNHmGSeSmXpAkEELlwLKR8HOnqeiS5UXNqdbxNemx/EpJSc5rTB6kzLX24dIuRsgyIIFWx73goOzmaHUolN4xmenifoMYlSNNM07WrsvmjRC5OLc/uGhdWqhZGBCH6AJB8Cmw84QLXVdHE6LiueP1oMd7g++N4X880wJkuh0ebfV3i7etUIn0jLlM50AkRucG9kwZDJ/M4LY7FT2F85R1/o2FaB/537ARQIDAQABMA0GCSqGSIb3DQEBDAUAA4IBgQB5lTkqYw0hEW+BJTFsQ8jEHfIDNRJ0kNbVuibfP+u7kzlJy15lCEi+Qw6E3d8hA1QBX3xJMxNBlrtYPrdG26hh/tOwo5Np/OfxQC5jo0Q7n7hu7aLxZRUB/q7AfdDbOun4Za6rJhT3+EsFocyARWp8bYSk3YILBMkP+2VYDRkgQidzKgKtO5yv21Y9sEgziSprc+dQb/tqn5aQZLWavFwCLwnB3t4r4qwLHkkH00Jw51uOvLeM49/t333V5Caa7wmWzMcE+KSWW0QWFRxeJrodSyjPdmDi4D8lKN5WJHSAU5L2yWIODUyWD/cvsAapTv7xXk9ja/Ssb9DpMQnM1xh0hYaESajNeL1QbGuZgPxAwrw981h7kprR2P2iMGRVGA6u4ezxmhW3s7D+yJ3+Yxs/x2J/sw65Z16mRYXRWYWHQmhgaVQjIviiAkVB6CWZo1kHl/eYaVedQzKlrTpbr3JtmwGwhYEOnrkzsC63h8/AG9gRtIAIGWGqTPWbn2pEm8M=", - "gossipEndpoint": [{ - "ipAddressV4": "fwAAAQ==", - "port": 35374 - }, { - "ipAddressV4": "fwAAAQ==", - "port": 35373 - }] - } - }, { - "rosterEntry": { - "nodeId": "2", - "weight": "1", - "gossipCaCertificate": "MIIDpzCCAg+gAwIBAgIJAJg3GRFp5bT9MA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTMwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCl5ut2dCleDmgEneRYpAKa9Pe2qnXzgF+BEIuTfizG2OcPQi/ltv+6HxSrJXtuWNaiX/G4iP7iBzWj2ysaAYwfYj0ezTSMLRqM9hXzVgLtW0LJEF6a8vUXPsJt4GEJkUKiYCCO1MP1NLd3y/3SVJrFhwJSPqKYm2pQNg84WfPDWSkzSneOIO4Z0uWDXgs+vzSNyChWOxVieFQhLjcELtyj6narmLox+Jdo/SxUzPuktuFB3ebNgUqWPkjljgZpl00BTmbRIVHgHfDVulo2PBpXd0VplIDgdPr5zMKdTrKCuDKey8Mft72RkPKMe9LZVZ/21+rXVEh+olvvUCySsP2RkWPUJJD90c8wKo01rZsjAOXscJKQcBYlam5XXO4ZBRYzEdxuivbkPwsOoQ83swCR3alPvwfbg11Va+zXE6sRbUM9LqkYo/M3Hwg8tSIXu8oah6csputanz867dzWwyVJEPzmiXZ6ncVDQO31QlB7RndWCqKTjOQpnpblUMsrE9MCAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAnUA8+kz7L+eSOm/iVvUNYF10PKO2nZtxWWL7R1vwK/2Up765PwqxKb0eSEM4bjgvZq1GuGXs9X/Y7dos42yntXvgeUY+/2JzCnw4J5tzxytZ+IKX6DR67NjDzDzVZQfptjLQrb8E7yzml0uxsqrhNPWl57Bmfe66Kg2lD11jImeeEhExlRggFukoiUWVwRNU21Q1jMUWrg2ZwfP+6fFTgRt0WR+X5zkyYPbvI6/yv7reYGjPDuZTOFhbwG8LUTQxdttDswPjnQ606kMyninL+aNelSdV/UIII7lpr/dTvgQAnrlBaGXvdy6brh3wWEwia0FZFZcKEs6M+jZ3MrFxvlTfUIdI3jRq12L10cCDi2VhORg4JmvlM+Tk6kJeSku30ZLAVo3S7GbTdvkuesOxz3UwnF7yfOA1KYOPvhv1oLxGV5z05glsn1OBKnXMdzsKFbAYYHj81bgBni2WLuIpv3oXlai2uc4y9m8LvWAQ+h/ivyog34Ai3Pvr5ZZOFgjy", - "gossipEndpoint": [{ - "ipAddressV4": "fwAAAQ==", - "port": 35376 - }, { - "ipAddressV4": "fwAAAQ==", - "port": 35375 - }] - } - }, { - "rosterEntry": { - "nodeId": "3", - "weight": "1", - "gossipCaCertificate": "MIIDpzCCAg+gAwIBAgIJAN7hww13zBZEMA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTQwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDK/bVyv0ZUeJZ4cIOImM+wmqtYjCw4jPAC549WQPPV1vG0lzSpgV+nRKqmWBexhLlKN3bsvrfNCUpKSq8meFyCtdppT1dhUOmEZcoNhLZzqxXb2HYYqRPv82tR+tbh+27WFsBOOqYrYTvr72ECD7qDOuw/Xob6KImaw/b/SIAPecMoYy25fkgYkJSETwd8HUpwssYH/JTLBF8eGjjTTMuu14ARQKeH8BXSs+jjV1+3IItXERS8ryUGDjqc5vC8ZW1kDVQbb91IDxRjqZbFyhuasocCqTAcZuiEgE8Wilwp2g1vbAUnHnvKNfiaEAHoEV6vF4lelaWhOnN2U5tnox/ns6PiDqIbOfs0pmXxjAK0vxc6oZM3TwdRtzo6cSb/AYfQdnmQzkra980kHN12r3f7PK2PzGBuVUPT7fLGA4S3vQDYO4rqcgTc/OLobtqLtdBusOFjZscfIfUW4GVWJUI1j+fwvHacxWLmyZwlQ5Q47UtrtjWpFru7CTn5S477lqMCAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAdW6AWDhT0eOJw+0O6MYngmCgkXfFsgBC/B1plaE596hHo58FHxzCNiLFdvfRj37rxujvqsDAkADWUmOzLzLHYMXu302HzDqAMNY6FZJc32y4ZDsIQpaUOAuiNHAHwFXuPRInVpCqztfJMgw4RhOhcCTEsoIJsqoIN1t4M0pEVAv6x3nJwFKZqSNOZrQ7sOW32FjwWS3kHwRsCTtqdk5n2KxU6wr/fggV3QsSPRMYro8sUfwu93mqggtswwWqfeKlsz5WiaR9aqLnb8z1R6HLvA0bcoPWzjgn8RdP+9we4z06iZ5vdBuNpwBjrCKUELWISyAoekLGGxyS8pPqYiSBRNUoaPITSuUjcCBbJ9EFvm72QgCBesbwF71KPabTPbMPhLmf+uAi+zmeu8ZeVvT6DrX9OHSkIvIEQFry9BrqOT3ce6KBHSO1HpXIetj5Wcd3WHXtz9ulBL9ikWC8eh7/+we51ucmLvFzNKznElhT2Dp+czXUVNEUjp3u/66pyRA4", - "gossipEndpoint": [{ - "ipAddressV4": "fwAAAQ==", - "port": 35378 - }, { - "ipAddressV4": "fwAAAQ==", - "port": 35377 - }] - } - }] -} diff --git a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/ReadableNodeStoreImpl.java b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/ReadableNodeStoreImpl.java index 1ab44266fc0e..c0cb9b11939e 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/ReadableNodeStoreImpl.java +++ b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/ReadableNodeStoreImpl.java @@ -29,9 +29,9 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; +import java.util.List; /** * Provides read-only methods for interacting with the underlying data storage mechanisms for @@ -93,11 +93,11 @@ private Roster constructFromNodesState(@NonNull final ReadableKVState 1) { - Collections.swap(nodeEndpoints, 0, 1); + nodeEndpoints = List.of(nodeEndpoints.getLast(), nodeEndpoints.getFirst()); } if (!node.deleted()) { final var entry = RosterEntry.newBuilder() diff --git a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/helpers/AddressBookHelper.java b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/helpers/AddressBookHelper.java index 868e63d98e03..b9bd52f36e53 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/helpers/AddressBookHelper.java +++ b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/helpers/AddressBookHelper.java @@ -72,7 +72,7 @@ public void adjustPostUpgradeNodeMetadata( if (node == null) { final var newNode = Node.newBuilder() .nodeId(nodeInfo.nodeId()) - .weight(nodeInfo.stake()) + .weight(nodeInfo.weight()) .accountId(nodeInfo.accountId()) .gossipCaCertificate(nodeInfo.sigCertBytes()) .gossipEndpoint(nodeInfo.gossipEndpoints()) diff --git a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/AddressBookTransplantSchema.java b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/AddressBookTransplantSchema.java new file mode 100644 index 000000000000..71530c223644 --- /dev/null +++ b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/AddressBookTransplantSchema.java @@ -0,0 +1,63 @@ +/* + * 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.addressbook.impl.schemas; + +import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.state.addressbook.Node; +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.node.app.service.addressbook.AddressBookService; +import com.hedera.node.internal.network.Network; +import com.hedera.node.internal.network.NodeMetadata; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * The {@link Schema#restart(MigrationContext)} implementation whereby the {@link AddressBookService} ensures that any + * node metadata overrides in the startup assets are copied into the state. + *

+ * Important: The latest {@link AddressBookService} schema should always implement this interface. + */ +public interface AddressBookTransplantSchema { + default void restart(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + if (!ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + return; + } + ctx.startupNetworks() + .overrideNetworkFor(ctx.roundNumber()) + .ifPresent(network -> setNodeMetadata(network, ctx.newStates())); + } + + /** + * Set the node metadata in the state from the provided network, for whatever nodes they are available. + * @param network the network from which to extract the node metadata + * @param writableStates the state in which to store the node metadata + */ + default void setNodeMetadata(@NonNull final Network network, @NonNull final WritableStates writableStates) { + final WritableKVState nodes = writableStates.get(NODES_KEY); + network.nodeMetadata().stream() + .filter(NodeMetadata::hasNode) + .map(NodeMetadata::nodeOrThrow) + .forEach(node -> nodes.put(new EntityNumber(node.nodeId()), node)); + } +} diff --git a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V053AddressBookSchema.java b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V053AddressBookSchema.java index 406b2a29d45a..75c5e43dc931 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V053AddressBookSchema.java +++ b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V053AddressBookSchema.java @@ -90,7 +90,7 @@ public void migrate(@NonNull final MigrationContext ctx) { // explicit that the override admin keys apply only at genesis final Map nodeAdminKeys = ctx.isGenesis() ? parseEd25519NodeAdminKeysFrom( - ctx.configuration().getConfigData(BootstrapConfig.class).nodeAdminKeysPath()) + ctx.appConfig().getConfigData(BootstrapConfig.class).nodeAdminKeysPath()) : emptyMap(); final var networkInfo = ctx.genesisNetworkInfo(); if (networkInfo == null) { @@ -98,7 +98,7 @@ public void migrate(@NonNull final MigrationContext ctx) { } final WritableKVState writableNodes = ctx.newStates().get(NODES_KEY); - final var bootstrapConfig = ctx.configuration().getConfigData(BootstrapConfig.class); + final var bootstrapConfig = ctx.appConfig().getConfigData(BootstrapConfig.class); log.info("Started migrating nodes from address book"); final var adminKey = getAccountAdminKey(ctx); @@ -121,7 +121,7 @@ public void migrate(@NonNull final MigrationContext ctx) { .description(formatNodeName(nodeInfo.nodeId())) .gossipEndpoint(nodeInfo.gossipEndpoints()) .gossipCaCertificate(nodeInfo.sigCertBytes()) - .weight(nodeInfo.stake()) + .weight(nodeInfo.weight()) .adminKey(nodeAdminKey); if (nodeDetailMap != null) { nodeDetail = nodeDetailMap.get(nodeInfo.nodeId()); @@ -141,7 +141,7 @@ public void migrate(@NonNull final MigrationContext ctx) { private Key getAccountAdminKey(@NonNull final MigrationContext ctx) { var adminKey = Key.DEFAULT; - final var accountConfig = ctx.configuration().getConfigData(AccountsConfig.class); + final var accountConfig = ctx.appConfig().getConfigData(AccountsConfig.class); ReadableKVState readableAccounts = null; try { @@ -163,7 +163,7 @@ private Key getAccountAdminKey(@NonNull final MigrationContext ctx) { private Map getNodeAddressMap(@NonNull final MigrationContext ctx) { Map nodeDetailMap = null; - final var fileConfig = ctx.configuration().getConfigData(FilesConfig.class); + final var fileConfig = ctx.appConfig().getConfigData(FilesConfig.class); ReadableKVState readableFiles = null; try { readableFiles = ctx.newStates().get(FILES_KEY); diff --git a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchema.java b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchema.java index af62be4eb20d..b8a949d8cd9b 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchema.java +++ b/hedera-node/hedera-addressbook-service-impl/src/main/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchema.java @@ -16,29 +16,20 @@ package com.hedera.node.app.service.addressbook.impl.schemas; -import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.addressbook.Node; -import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.node.internal.network.Network; -import com.hedera.node.internal.network.NodeMetadata; +import com.swirlds.platform.config.AddressBookConfig; import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; -import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.concurrent.atomic.AtomicReference; /** - * A restart-only schema that ensures address book state reflects any overrides in - * {@link Network}s returned from {@link MigrationContext#startupNetworks()} on - * disk at startup. (Note that once the network has written at least one state to - * disk after restart, all such override {@link Network}s will be archived, and - * hence are applied to at most one state after restart.) + * A genesis-only schema that ensures address book state reflects the genesis {@link Network}s returned from + * {@link MigrationContext#startupNetworks()} on disk at startup when using the roster lifecycle. */ -public class V057AddressBookSchema extends Schema { +public class V057AddressBookSchema extends Schema implements AddressBookTransplantSchema { private static final SemanticVersion VERSION = SemanticVersion.newBuilder().major(0).minor(57).build(); @@ -47,29 +38,18 @@ public V057AddressBookSchema() { } @Override - public void restart(@NonNull final MigrationContext ctx) { + public void migrate(@NonNull final MigrationContext ctx) { requireNonNull(ctx); - final AtomicReference network = new AtomicReference<>(); - if (ctx.isGenesis()) { - try { - network.set(ctx.startupNetworks().genesisNetworkOrThrow()); - } catch (Exception ignore) { - // FUTURE - fail hard here once the roster lifecycle is always enabled - return; - } - } else { - ctx.startupNetworks().overrideNetworkFor(ctx.roundNumber()).ifPresent(network::set); + if (!ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + return; } - if (network.get() != null) { - setNodeMetadata(network.get(), ctx.newStates()); + if (ctx.isGenesis()) { + setNodeMetadata(ctx.startupNetworks().genesisNetworkOrThrow(), ctx.newStates()); } } - private void setNodeMetadata(@NonNull final Network network, @NonNull final WritableStates writableStates) { - final WritableKVState nodes = writableStates.get(NODES_KEY); - network.nodeMetadata().stream() - .filter(NodeMetadata::hasNode) - .map(NodeMetadata::nodeOrThrow) - .forEach(node -> nodes.put(new EntityNumber(node.nodeId()), node)); + @Override + public void restart(@NonNull final MigrationContext ctx) { + AddressBookTransplantSchema.super.restart(ctx); } } diff --git a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchemaTest.java b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchemaTest.java index a3cd9beb8fc8..0003db3fb8ee 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchemaTest.java +++ b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/schemas/V057AddressBookSchemaTest.java @@ -17,9 +17,11 @@ package com.hedera.node.app.service.addressbook.impl.schemas; import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; +import static com.hedera.node.app.service.addressbook.impl.test.handlers.AddressBookTestBase.DEFAULT_CONFIG; +import static com.hedera.node.app.service.addressbook.impl.test.handlers.AddressBookTestBase.WITH_ROSTER_LIFECYCLE; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; import com.hedera.hapi.node.state.addressbook.Node; import com.hedera.hapi.node.state.common.EntityNumber; @@ -63,25 +65,24 @@ class V057AddressBookSchemaTest { private final V057AddressBookSchema subject = new V057AddressBookSchema(); @Test - void returnsIfGenesisNodeMetadataUnavailable() { - given(ctx.isGenesis()).willReturn(true); - given(ctx.startupNetworks()).willReturn(startupNetworks); - given(startupNetworks.genesisNetworkOrThrow()).willThrow(IllegalStateException.class); + void migrationIsNoOpIfRosterLifecycleNotEnabled() { + given(ctx.appConfig()).willReturn(DEFAULT_CONFIG); subject.restart(ctx); - verifyNoInteractions(writableStates); + verifyNoMoreInteractions(ctx); } @Test void usesGenesisNodeMetadataIfPresent() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); given(ctx.startupNetworks()).willReturn(startupNetworks); given(startupNetworks.genesisNetworkOrThrow()).willReturn(NETWORK); given(ctx.newStates()).willReturn(writableStates); given(ctx.isGenesis()).willReturn(true); given(writableStates.get(NODES_KEY)).willReturn(nodes); - subject.restart(ctx); + subject.migrate(ctx); verify(nodes) .put(new EntityNumber(1L), NETWORK.nodeMetadata().getFirst().nodeOrThrow()); @@ -90,6 +91,7 @@ void usesGenesisNodeMetadataIfPresent() { @Test void usesOverrideMetadataIfPresent() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); given(ctx.startupNetworks()).willReturn(startupNetworks); given(startupNetworks.overrideNetworkFor(0L)).willReturn(Optional.of(NETWORK)); given(ctx.newStates()).willReturn(writableStates); diff --git a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/AddressBookServiceImplTest.java b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/AddressBookServiceImplTest.java index 55ae3ab4f68b..2c15ed934288 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/AddressBookServiceImplTest.java +++ b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/AddressBookServiceImplTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import com.hedera.node.app.service.addressbook.impl.AddressBookServiceImpl; +import com.hedera.node.app.service.addressbook.impl.schemas.AddressBookTransplantSchema; import com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema; import com.hedera.node.app.service.addressbook.impl.schemas.V057AddressBookSchema; import com.swirlds.state.lifecycle.Schema; @@ -54,5 +55,6 @@ void registersExpectedSchema() { assertThat(schemas).hasSize(2); assertThat(schemas.getFirst()).isInstanceOf(V053AddressBookSchema.class); assertThat(schemas.getLast()).isInstanceOf(V057AddressBookSchema.class); + assertThat(schemas.getLast()).isInstanceOf(AddressBookTransplantSchema.class); } } diff --git a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/handlers/AddressBookTestBase.java b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/handlers/AddressBookTestBase.java index 8955ca71b4f3..484edd4983db 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/handlers/AddressBookTestBase.java +++ b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/handlers/AddressBookTestBase.java @@ -96,6 +96,9 @@ public class AddressBookTestBase { A_COMPLEX_KEY))) .build(); public static final Configuration DEFAULT_CONFIG = HederaTestConfigBuilder.createConfig(); + public static final Configuration WITH_ROSTER_LIFECYCLE = HederaTestConfigBuilder.create() + .withValue("addressBook.useRosterLifecycle", true) + .getOrCreateConfig(); protected final Key key = A_COMPLEX_KEY; protected final Key anotherKey = B_COMPLEX_KEY; diff --git a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/schemas/V053AddressBookSchemaTest.java b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/schemas/V053AddressBookSchemaTest.java index f56d46b5a78f..7cb776e649ad 100644 --- a/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/schemas/V053AddressBookSchemaTest.java +++ b/hedera-node/hedera-addressbook-service-impl/src/test/java/com/hedera/node/app/service/addressbook/impl/test/schemas/V053AddressBookSchemaTest.java @@ -305,7 +305,7 @@ private void setupMigrationContext() { final var config = HederaTestConfigBuilder.create() .withValue("bootstrap.genesisPublicKey", defaultAdminKeyBytes) .getOrCreateConfig(); - given(migrationContext.configuration()).willReturn(config); + given(migrationContext.appConfig()).willReturn(config); } private void setupMigrationContext2() { @@ -332,7 +332,7 @@ private void setupMigrationContext2() { adminKeysLoc.toAbsolutePath().toString()) .withValue("accounts.addressBookAdmin", "55") .getOrCreateConfig(); - given(migrationContext.configuration()).willReturn(config); + given(migrationContext.appConfig()).willReturn(config); } private void setupMigrationContext3() { @@ -367,7 +367,7 @@ private void setupMigrationContext3() { .withValue("accounts.addressBookAdmin", "55") .withValue("files.nodeDetails", "102") .getOrCreateConfig(); - given(migrationContext.configuration()).willReturn(config); + given(migrationContext.appConfig()).willReturn(config); } private void setupMigrationContext4() { @@ -388,7 +388,7 @@ private void setupMigrationContext4() { .withValue("accounts.addressBookAdmin", "55") .withValue("files.nodeDetails", "102") .getOrCreateConfig(); - given(migrationContext.configuration()).willReturn(config); + given(migrationContext.appConfig()).willReturn(config); } private String nodeAdminKeysJson() { diff --git a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/info/FakeNetworkInfo.java b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/info/FakeNetworkInfo.java index 9838e2cc658c..0eda16f58924 100644 --- a/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/info/FakeNetworkInfo.java +++ b/hedera-node/hedera-app-spi/src/testFixtures/java/com/hedera/node/app/spi/fixtures/info/FakeNetworkInfo.java @@ -20,7 +20,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.ServiceEndpoint; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.platform.NodeId; import com.swirlds.state.State; @@ -90,15 +89,10 @@ public void updateFrom(final State state) { throw new UnsupportedOperationException("Not implemented"); } - @Override - public Roster roster() { - return Roster.DEFAULT; - } - private static NodeInfo fakeInfoWith( final long nodeId, @NonNull final AccountID nodeAccountId, - long stake, + long weight, List gossipEndpoints, @Nullable Bytes sigCertBytes) { return new NodeInfo() { @@ -113,8 +107,8 @@ public AccountID accountId() { } @Override - public long stake() { - return stake; + public long weight() { + return weight; } @Override 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 741bfbd2f1ce..fc47299d054d 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 @@ -36,7 +36,6 @@ import static com.hedera.node.app.util.HederaAsciiArt.HEDERA; import static com.hedera.node.config.types.StreamMode.BLOCKS; import static com.hedera.node.config.types.StreamMode.RECORDS; -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; import static com.swirlds.platform.state.service.PlatformStateService.PLATFORM_STATE_SERVICE; import static com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema.PLATFORM_STATE_KEY; import static com.swirlds.platform.system.InitTrigger.EVENT_STREAM_RECOVERY; @@ -115,6 +114,7 @@ import com.hedera.node.config.data.NetworkAdminConfig; import com.hedera.node.config.data.VersionConfig; import com.hedera.node.config.types.StreamMode; +import com.hedera.node.internal.network.Network; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; @@ -133,12 +133,10 @@ import com.swirlds.platform.listeners.ReconnectCompleteListener; import com.swirlds.platform.listeners.ReconnectCompleteNotification; import com.swirlds.platform.listeners.StateWriteToDiskCompleteListener; -import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformMerkleStateRoot; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.ReadablePlatformStateStore; -import com.swirlds.platform.state.service.ReadableRosterStoreImpl; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; @@ -285,11 +283,6 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener, A */ private final StartupNetworksFactory startupNetworksFactory; - /** - * The id of this node. - */ - private final NodeId selfNodeId; - /** * The Hashgraph Platform. This is set during state initialization. */ @@ -310,6 +303,15 @@ public final class Hedera implements SwirldMain, PlatformStatusChangeListener, A */ private HederaInjectionComponent daggerApp; + /** + * When applying and migrating schemas to a target state, it is set here to support + * giving the {@link RosterService} schemas access to a {@link ReadablePlatformStateStore} + * before the roster lifecycle is adopted. + */ + @Nullable + @Deprecated + private State initState; + /** * The metrics object being used for reporting. */ @@ -360,8 +362,7 @@ public interface TssBaseServiceFactory { @FunctionalInterface public interface StartupNetworksFactory { @NonNull - StartupNetworks apply( - long selfNodeId, @NonNull ConfigProvider configProvider, @NonNull TssBaseService tssBaseService); + StartupNetworks apply(@NonNull ConfigProvider configProvider, @NonNull TssBaseService tssBaseService); } /*================================================================================================================== @@ -382,7 +383,6 @@ StartupNetworks apply( * @param migrator the migrator to use with the services * @param tssBaseServiceFactory the factory for the TSS base service * @param startupNetworksFactory the factory for the startup networks - * @param selfNodeId the node ID of this node */ public Hedera( @NonNull final ConstructableRegistry constructableRegistry, @@ -390,11 +390,9 @@ public Hedera( @NonNull final ServiceMigrator migrator, @NonNull final InstantSource instantSource, @NonNull final TssBaseServiceFactory tssBaseServiceFactory, - @NonNull final StartupNetworksFactory startupNetworksFactory, - @NonNull final NodeId selfNodeId) { + @NonNull final StartupNetworksFactory startupNetworksFactory) { requireNonNull(registryFactory); requireNonNull(constructableRegistry); - this.selfNodeId = requireNonNull(selfNodeId); this.serviceMigrator = requireNonNull(migrator); this.startupNetworksFactory = requireNonNull(startupNetworksFactory); logger.info( @@ -460,7 +458,10 @@ public Hedera( // FUTURE: a lambda that tests if a ReadableTssStore // constructed from the migration state returns a // RosterKeys with the ledger id for the given roster - new RosterService(roster -> true), + new RosterService( + roster -> true, + () -> new ReadablePlatformStateStore( + requireNonNull(initState).getReadableStates(PlatformStateService.NAME))), PLATFORM_STATE_SERVICE) .forEach(servicesRegistry::register); try { @@ -539,10 +540,11 @@ public void initializeStatesApi( @NonNull final State state, @NonNull final Metrics metrics, @NonNull final InitTrigger trigger, - @Nullable final AddressBook genesisAddressBook, - @NonNull final Configuration platformConfiguration) { + @Nullable final Network genesisNetwork, + @NonNull final Configuration platformConfig, + @Deprecated @Nullable final AddressBook diskAddressBook) { requireNonNull(state); - requireNonNull(platformConfiguration); + requireNonNull(platformConfig); this.metrics = requireNonNull(metrics); this.configProvider = new ConfigProviderImpl(trigger == GENESIS, metrics); final var deserializedVersion = serviceMigrator.creationVersionOf(state); @@ -568,7 +570,7 @@ public void initializeStatesApi( throw new IllegalStateException("Cannot downgrade from " + savedStateVersion + " to " + version); } try { - migrateSchemas(state, savedStateVersion, trigger, metrics, genesisAddressBook, platformConfiguration); + migrateSchemas(state, savedStateVersion, trigger, metrics, genesisNetwork, platformConfig, diskAddressBook); logConfiguration(); } catch (final Throwable t) { logger.fatal("Critical failure during schema migration", t); @@ -598,11 +600,7 @@ public void onStateInitialized( this.platform = requireNonNull(platform); if (state.getReadableStates(PlatformStateService.NAME).isEmpty()) { initializeStatesApi( - state, - metrics, - trigger, - RosterUtils.buildAddressBook(platform.getRoster()), - platform.getContext().getConfiguration()); + state, metrics, trigger, null, platform.getContext().getConfiguration(), null); } // With the States API grounded in the working state, we can create the object graph from it initializeDagger(state, trigger); @@ -618,19 +616,21 @@ public void onStateInitialized( *

If the {@code deserializedVersion} is {@code null}, then this is the first time the node has been started, * and thus all schemas will be executed. * - * @param state current state - * @param deserializedVersion version deserialized - * @param trigger trigger that is calling migration - * @param genesisAddressBook the genesis address book, if applicable - * @param platformConfiguration platform configuration + * @param state current state + * @param deserializedVersion version deserialized + * @param trigger trigger that is calling migration + * @param genesisNetwork the genesis address book, if applicable + * @param platformConfig platform configuration + * @param diskAddressBook before enabling the roster lifecycle, the address book from disk */ private void migrateSchemas( @NonNull final State state, @Nullable final ServicesSoftwareVersion deserializedVersion, @NonNull final InitTrigger trigger, @NonNull final Metrics metrics, - @Nullable final AddressBook genesisAddressBook, - @NonNull final Configuration platformConfiguration) { + @Nullable final Network genesisNetwork, + @NonNull final Configuration platformConfig, + @Deprecated @Nullable final AddressBook diskAddressBook) { final var previousVersion = deserializedVersion == null ? null : deserializedVersion.getPbjSemanticVersion(); final var isUpgrade = version.compareTo(deserializedVersion) > 0; logger.info( @@ -647,14 +647,17 @@ private void migrateSchemas( if (trigger == GENESIS) { final var config = configProvider.getConfiguration(); final var ledgerConfig = config.getConfigData(LedgerConfig.class); - final var genesisRoster = buildRoster(requireNonNull(genesisAddressBook)); - genesisNetworkInfo = new GenesisNetworkInfo(genesisRoster, ledgerConfig.id()); + genesisNetworkInfo = new GenesisNetworkInfo(requireNonNull(genesisNetwork), ledgerConfig.id()); } blockStreamService.resetMigratedLastBlockHash(); - startupNetworks = startupNetworksFactory.apply(selfNodeId.id(), configProvider, tssBaseService); - PLATFORM_STATE_SERVICE.setAppVersionFn(() -> version); - PLATFORM_STATE_SERVICE.setActiveRosterFn( - () -> new ReadableRosterStoreImpl(state.getReadableStates(RosterService.NAME)).getActiveRoster()); + startupNetworks = startupNetworksFactory.apply(configProvider, tssBaseService); + PLATFORM_STATE_SERVICE.setAppVersionFn(ServicesSoftwareVersion::from); + // If the client code did not provide a disk address book, we are reconnecting; and + // PlatformState schemas must not try to update the current address book anyway + if (diskAddressBook != null) { + PLATFORM_STATE_SERVICE.setDiskAddressBook(diskAddressBook); + } + this.initState = state; final var migrationChanges = serviceMigrator.doMigrations( state, servicesRegistry, @@ -663,11 +666,12 @@ private void migrateSchemas( // (FUTURE) In principle, the FileService could change the active configuration during a // migration, implying we should pass a config provider; but we don't need this yet configProvider.getConfiguration(), - platformConfiguration, + platformConfig, genesisNetworkInfo, metrics, startupNetworks); - PLATFORM_STATE_SERVICE.clearActiveRosterFn(); + this.initState = null; + PLATFORM_STATE_SERVICE.clearDiskAddressBook(); migrationStateChanges = new ArrayList<>(migrationChanges); kvStateChangeListener.reset(); boundaryStateChangeListener.reset(); @@ -1005,8 +1009,7 @@ private void initializeDagger(@NonNull final State state, @NonNull final InitTri final var activeRoster = tssBaseService.chooseRosterForNetwork( state, trigger, serviceMigrator, version, configProvider.getConfiguration(), platform.getRoster()); - final var networkInfo = - new StateNetworkInfo(state, activeRoster, platform.getSelfId().id(), configProvider); + final var networkInfo = new StateNetworkInfo(platform.getSelfId().id(), state, activeRoster, configProvider); // Fully qualified so as to not confuse javadoc daggerApp = com.hedera.node.app.DaggerHederaInjectionComponent.builder() .configProviderImpl(configProvider) 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 3bfa8a205153..870dde6c4779 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 @@ -20,6 +20,7 @@ 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.PlatformBuildConstants.LOG4J_FILE_NAME; @@ -27,17 +28,20 @@ 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.roster.RosterUtils.buildRosterHistory; +import static com.swirlds.platform.state.signed.StartupStateUtils.copyInitialSignedState; 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.initializeAddressBook; import static com.swirlds.platform.util.BootstrapUtils.checkNodesToRun; +import static com.swirlds.platform.util.BootstrapUtils.detectSoftwareUpgrade; import static com.swirlds.platform.util.BootstrapUtils.getNodesToRun; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; import com.hedera.node.app.info.DiskStartupNetworks; +import com.hedera.node.app.service.addressbook.AddressBookService; +import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl; import com.hedera.node.app.services.OrderedServiceMigrator; import com.hedera.node.app.services.ServicesRegistryImpl; import com.hedera.node.app.store.ReadableStoreFactory; @@ -59,6 +63,7 @@ import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.config.extensions.sources.SystemEnvironmentConfigSource; import com.swirlds.config.extensions.sources.SystemPropertiesConfigSource; +import com.swirlds.logging.legacy.payload.SavedStateLoadedPayload; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.Browser; import com.swirlds.platform.CommandLineArgs; @@ -68,18 +73,22 @@ 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.crypto.CryptoStatic; import com.swirlds.platform.roster.RosterHistory; import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; +import com.swirlds.platform.state.PlatformMerkleStateRoot; +import com.swirlds.platform.state.address.AddressBookInitializer; import com.swirlds.platform.state.service.ReadableRosterStore; +import com.swirlds.platform.state.signed.HashedReservedSignedState; import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.platform.state.signed.StartupStateUtils; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.SwirldMain; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.system.address.AddressBookUtils; import com.swirlds.platform.util.BootstrapUtils; import com.swirlds.state.State; import com.swirlds.state.merkle.MerkleStateRoot; @@ -89,6 +98,7 @@ import java.util.Set; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -98,7 +108,6 @@ *

This class simply delegates to {@link Hedera}. */ public class ServicesMain implements SwirldMain { - private static final Logger logger = LogManager.getLogger(ServicesMain.class); /** @@ -162,8 +171,8 @@ public void run() { * {@link Hedera#newMerkleStateRoot()} method. *

  • Determine this node's self id by searching the config.txt * in the working directory for any address book entries with IP addresses - * local to this machine; if there is there is more than one such entry, - * fail unless the command line args include a {@literal -local N} arg.
  • + * local to this machine; if there is more than one such entry, fail unless + * the command line args include a {@literal -local N} arg. *
  • Build a {@link Platform} instance from Services application metadata * and the working directory settings.txt, providing the same * {@link Hedera#newMerkleStateRoot()} method reference as the genesis state @@ -179,173 +188,166 @@ public void run() { *

    Please see the startup-phase-lifecycle.png in this directory to visualize * the sequence of events in the startup phase and the centrality of the {@link Hedera} * singleton. + *

    + * IMPORTANT: A surface-level reading of this method will undersell the centrality + * of the Hedera instance. It is actually omnipresent throughout both the startup and + * runtime phases of the application. Let's see why. When we build the platform, the + * builder will either: + *

      + *
    1. Create a genesis state; or,
    2. + *
    3. Deserialize a saved state.
    4. + *
    + * In both cases the state object will be created by the {@link Hedera#newMerkleStateRoot()} + * method reference bound to our Hedera instance. Because, + *
      + *
    1. We provided this method as the genesis state factory right above; and,
    2. + *
    3. Our Hedera instance's constructor registered its {@link Hedera#newMerkleStateRoot()} + * method with the {@link ConstructableRegistry} as the factory for the Services state root + * class id.
    4. + *
    + * Now, note that {@link Hedera#newMerkleStateRoot()} returns {@link PlatformMerkleStateRoot} + * instances that delegate their lifecycle methods to an injected instance of + * {@link com.swirlds.platform.state.MerkleStateLifecycles}---and the implementation of that + * injected by {@link Hedera#newMerkleStateRoot()} delegates these calls back to the Hedera + * instance itself. + *

    + * Thus, the Hedera instance centralizes nearly all the setup and runtime logic for the + * application. It implements this logic by instantiating a {@link javax.inject.Singleton} + * component whose object graph roots include the Ingest, PreHandle, Handle, and Query + * workflows; as well as other infrastructure components that need to be initialized or + * accessed at specific points in the Swirlds application lifecycle. * * @param args optionally, what node id to run; required if the address book is ambiguous */ public static void main(final String... args) throws Exception { + // --- Configure platform infrastructure and context from the command line and environment --- BootstrapUtils.setupConstructableRegistry(); - // Determine which node to run locally - // Load config.txt address book file and parse address book - final AddressBook diskAddressBook = loadAddressBook(DEFAULT_CONFIG_FILE_NAME); - // parse command line arguments - final CommandLineArgs commandLineArgs = CommandLineArgs.parse(args); - - // Only allow 1 node to be specified by the command line arguments. + final var diskAddressBook = loadAddressBook(DEFAULT_CONFIG_FILE_NAME); + final var commandLineArgs = CommandLineArgs.parse(args); if (commandLineArgs.localNodesToStart().size() > 1) { logger.error( EXCEPTION.getMarker(), "Multiple nodes were supplied via the command line. Only one node can be started per java process."); exitSystem(NODE_ADDRESS_MISMATCH); } - - // 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(diskAddressBook, commandLineArgs.localNodesToStart()); - // hard exit if no nodes are configured to run checkNodesToRun(nodesToRun); - - final NodeId selfId = ensureSingleNode(nodesToRun, commandLineArgs.localNodesToStart()); - - final var configuration = buildConfiguration(); - - // Register with the ConstructableRegistry classes which need configuration. - BootstrapUtils.setupConstructableRegistryWithConfiguration(configuration); - - final var keysAndCerts = - initNodeSecurity(diskAddressBook, configuration).get(selfId); - - setupGlobalMetrics(configuration); + final var selfId = ensureSingleNode(nodesToRun, commandLineArgs.localNodesToStart()); + final var platformConfig = buildPlatformConfig(); + BootstrapUtils.setupConstructableRegistryWithConfiguration(platformConfig); + final var networkKeysAndCerts = initNodeSecurity(diskAddressBook, platformConfig); + final var keysAndCerts = networkKeysAndCerts.get(selfId); + setupGlobalMetrics(platformConfig); metrics = getMetricsProvider().createPlatformMetrics(selfId); - - hedera = newHedera(selfId); - final SoftwareVersion version = hedera.getSoftwareVersion(); - logger.info("Starting node {} with version {}", selfId, version); - final var time = Time.getCurrent(); - final var fileSystemManager = FileSystemManager.create(configuration); + final var fileSystemManager = FileSystemManager.create(platformConfig); final var recycleBin = - RecycleBin.create(metrics, configuration, getStaticThreadManager(), time, fileSystemManager, selfId); - + RecycleBin.create(metrics, platformConfig, 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(diskAddressBook); - - // Initialize the Merkle cryptography - final var merkleCryptography = MerkleCryptographyFactory.create(configuration, cryptography); + final var merkleCryptography = MerkleCryptographyFactory.create(platformConfig, cryptography); MerkleCryptoFactory.set(merkleCryptography); + final var platformContext = PlatformContext.create( + platformConfig, + Time.getCurrent(), + metrics, + cryptography, + FileSystemManager.create(platformConfig), + recycleBin, + merkleCryptography); - // Create initial state for the platform + // --- Construct the Hedera instance and use it to initialize the starting state --- + hedera = newHedera(selfId, metrics); + final var version = hedera.getSoftwareVersion(); + logger.info("Starting node {} with version {}", selfId, version); final var isGenesis = new AtomicBoolean(false); // We want to be able to see the schema migration logs, so init logging here initLogging(); - final var reservedState = getInitialState( - configuration, + final var reservedState = loadInitialState( + platformConfig, recycleBin, version, () -> { isGenesis.set(true); - final var genesisState = hedera.newMerkleStateRoot(); + final var genesisState = (PlatformMerkleStateRoot) hedera.newMerkleStateRoot(); + final var genesisNetwork = DiskStartupNetworks.fromLegacyAddressBook(diskAddressBook); hedera.initializeStatesApi( - (MerkleStateRoot) genesisState, + genesisState, metrics, InitTrigger.GENESIS, - diskAddressBook, - configuration); + genesisNetwork, + platformConfig, + diskAddressBook); return genesisState; }, Hedera.APP_NAME, Hedera.SWIRLD_NAME, - selfId, - diskAddressBook); + selfId); final var initialState = reservedState.state(); if (!isGenesis.get()) { hedera.initializeStatesApi( - (MerkleStateRoot) initialState.get().getState().getSwirldState(), + (PlatformMerkleStateRoot) initialState.get().getState().getSwirldState(), metrics, InitTrigger.RESTART, null, - configuration); + platformConfig, + diskAddressBook); } + hedera.setInitialStateHash(reservedState.hash()); - // Create the platform context - final var platformContext = PlatformContext.create( - configuration, - Time.getCurrent(), - metrics, - cryptography, - FileSystemManager.create(configuration), - recycleBin, - merkleCryptography); - - final var stateHash = reservedState.hash(); - - // Initialize the address book and set on platform builder - final var addressBook = initializeAddressBook(selfId, version, initialState, diskAddressBook, platformContext); - + // --- Now build the platform and start it --- + final var stateRoot = (PlatformMerkleStateRoot) initialState.get().getState(); + // Roster history naturally derives from RosterService state if the roster + // lifecycle is enabled; but until then, is translated from the legacy + // previous and current AddressBook fields in the PlatformState final RosterHistory rosterHistory; - final boolean shouldUseRosterLifecycle = - configuration.getConfigData(AddressBookConfig.class).useRosterLifecycle(); - if (shouldUseRosterLifecycle) { - final SignedState loadedSignedState = initialState.get(); - final var state = ((MerkleStateRoot) loadedSignedState.getState()); - final var rosterStore = new ReadableStoreFactory(state).getStore(ReadableRosterStore.class); + if (platformConfig.getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + final var rosterStore = new ReadableStoreFactory(stateRoot).getStore(ReadableRosterStore.class); rosterHistory = RosterUtils.createRosterHistory(rosterStore); } else { - rosterHistory = - RosterUtils.buildRosterHistory((State) initialState.get().getState()); + // This constructor both does extensive validation and has the side effect of + // moving unused config.txt files to an archive directory; so keep calling it + // here until we enable the roster lifecycle + new AddressBookInitializer( + selfId, + version, + detectSoftwareUpgrade(version, initialState.get()), + initialState.get(), + diskAddressBook.copy(), + platformContext); + rosterHistory = buildRosterHistory((State) initialState.get().getState()); } - - // 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, - AddressBookUtils.formatConsensusEventStreamName(addressBook, selfId), - // C.f. https://github.com/hashgraph/hedera-services/issues/14751, - // we need to choose the correct roster in the following cases: - // - At genesis, a roster loaded from disk - // - At restart, the active roster in the saved state - // - At upgrade boundary, the candidate roster in the saved state IF - // that state satisfies conditions (e.g. the roster has been keyed) + canonicalEventStreamLoc(selfId.id(), stateRoot), rosterHistory) .withPlatformContext(platformContext) - .withConfiguration(configuration) + .withConfiguration(platformConfig) .withKeysAndCerts(keysAndCerts); - - hedera.setInitialStateHash(stateHash); - // IMPORTANT: A surface-level reading of this method will undersell the centrality - // of the Hedera instance. It is actually omnipresent throughout both the startup - // and runtime phases of the application. - // - // Let's see why. When we build the platform, the builder will either: - // (1) Create a genesis state; or, - // (2) Deserialize a saved state. - // In both cases the state object will be created by the hedera::newState method - // reference bound to our Hedera instance. Because, - // (1) We provided this method as the genesis state factory right above; and, - // (2) Our Hedera instance's constructor registered its newState() method with the - // ConstructableRegistry as the factory for the Services Merkle tree class id. - // - // Now, note that hedera::newState returns MerkleStateRoot instances that delegate - // their lifecycle methods to an injected instance of MerkleStateLifecycles---and - // hedera::newState injects an instance of MerkleStateLifecyclesImpl which primarily - // delegates these calls back to the Hedera instance itself. - // - // Thus, the Hedera instance centralizes nearly all the setup and runtime logic for the - // application. It implements this logic by instantiating a Dagger2 @Singleton component - // whose object graph roots include the Ingest, PreHandle, Handle, and Query workflows; - // as well as other infrastructure components that need to be initialized or accessed - // at specific points in the Swirlds application lifecycle. - final Platform platform = platformBuilder.build(); + final var platform = platformBuilder.build(); hedera.init(platform, selfId); platform.start(); hedera.run(); } + /** + * Returns the event stream name for the given node id. + * + * @param nodeId the node id + * @param root the platform merkle state root + * @return the event stream name + */ + private static String canonicalEventStreamLoc(final long nodeId, @NonNull final PlatformMerkleStateRoot root) { + final var nodeStore = new ReadableNodeStoreImpl(root.getReadableStates(AddressBookService.NAME)); + final var accountId = requireNonNull(nodeStore.get(nodeId)).accountIdOrThrow(); + return accountId.shardNum() + "." + accountId.realmNum() + "." + accountId.accountNumOrThrow(); + } + private static void initLogging() { final var log4jPath = getAbsolutePath(LOG4J_FILE_NAME); try { @@ -359,12 +361,37 @@ private static void initLogging() { } /** - * Build the configuration for this node. + * Creates a canonical {@link Hedera} instance for the given node id and metrics. + * + * @param selfNodeId the node id + * @param metrics the metrics + * @return the {@link Hedera} instance + */ + public static Hedera newHedera(@NonNull final NodeId selfNodeId, @NonNull final Metrics metrics) { + requireNonNull(selfNodeId); + requireNonNull(metrics); + return new Hedera( + ConstructableRegistry.getInstance(), + ServicesRegistryImpl::new, + new OrderedServiceMigrator(), + InstantSource.system(), + appContext -> new TssBaseServiceImpl( + appContext, + ForkJoinPool.commonPool(), + ForkJoinPool.commonPool(), + new TssLibraryImpl(appContext), + ForkJoinPool.commonPool(), + metrics), + DiskStartupNetworks::new); + } + + /** + * Builds the platform configuration for this node. * * @return the configuration */ @NonNull - private static Configuration buildConfiguration() { + public static Configuration buildPlatformConfig() { final ConfigurationBuilder configurationBuilder = ConfigurationBuilder.create() .withSource(SystemEnvironmentConfigSource.getInstance()) .withSource(SystemPropertiesConfigSource.getInstance()); @@ -436,25 +463,49 @@ private static AddressBook loadAddressBook(@NonNull final String addressBookPath } } - private static @NonNull Hedera hederaOrThrow() { - return requireNonNull(hedera); + /** + * Get the initial state to be used by this node. May return a state loaded from disk, or may return a genesis state + * if no valid state is found on disk. + * + * @param configuration the configuration for this node + * @param softwareVersion the software version of the app + * @param stateRootSupplier a supplier that can build a genesis state + * @param mainClassName the name of the app's SwirldMain class + * @param swirldName the name of this swirld + * @param selfId the node id of this node + * @return the initial state to be used by this node + */ + @NonNull + private static HashedReservedSignedState loadInitialState( + @NonNull final Configuration configuration, + @NonNull final RecycleBin recycleBin, + @NonNull final SoftwareVersion softwareVersion, + @NonNull final Supplier stateRootSupplier, + @NonNull final String mainClassName, + @NonNull final String swirldName, + @NonNull final NodeId selfId) { + final var loadedState = StartupStateUtils.loadStateFile( + configuration, recycleBin, selfId, mainClassName, swirldName, softwareVersion); + try (loadedState) { + if (loadedState.isNotNull()) { + logger.info( + STARTUP.getMarker(), + new SavedStateLoadedPayload( + loadedState.get().getRound(), loadedState.get().getConsensusTimestamp())); + return copyInitialSignedState(configuration, loadedState.get()); + } + } + final var stateRoot = stateRootSupplier.get(); + final var signedState = new SignedState( + configuration, CryptoStatic::verifySignature, stateRoot, "genesis state", false, false, false); + final var reservedSignedState = signedState.reserve("initial reservation on genesis state"); + try (reservedSignedState) { + return copyInitialSignedState(configuration, reservedSignedState.get()); + } } - private static Hedera newHedera(@NonNull final NodeId selfNodeId) { - return new Hedera( - ConstructableRegistry.getInstance(), - ServicesRegistryImpl::new, - new OrderedServiceMigrator(), - InstantSource.system(), - appContext -> new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - metrics), - DiskStartupNetworks::new, - selfNodeId); + private static @NonNull Hedera hederaOrThrow() { + return requireNonNull(hedera); } @VisibleForTesting diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/StartupAssets.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/StartupAssets.java deleted file mode 100644 index 9c2c0033eeef..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/StartupAssets.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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; - -import com.hedera.node.internal.network.Network; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.nio.file.Path; -import java.util.Optional; - -public interface StartupAssets { - interface Factory { - StartupAssets fromInitialConditions(@NonNull Path workingDir); - } - - /** - * Called by a node that finds itself with an empty RosterService - * state, and is thus at the migration boundary for adoption of the - * proposed roster; implementations must either throw unsupported or - * return an aggregation of the information in legacy config.txt and - * public.pfx files. - */ - Network migrationNetworkOrThrow(); - - /** - * Called by a node that finds itself with a completely empty state - * and no genesis-config.txt file. - */ - Network genesisNetworkOrThrow(); - - /** - * Returns a Network description if there is an override-config.txt - * on disk that has not already been used in an earlier round than the - * given number. - */ - Optional overrideNetwork(long roundNumber); - - void archiveInitialConditions(); -} 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 051f6f7e8d3b..f19801262018 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 @@ -358,7 +358,12 @@ public void endRound(@NonNull final State state, final long roundNum) { case ONLY_FREEZE_BLOCK -> roundNum == freezeRoundNumber; }; if (exportNetworkToDisk) { - DiskStartupNetworks.writeNetworkInfo(state, Paths.get(diskNetworkExportFile)); + final var exportPath = Paths.get(diskNetworkExportFile); + log.info( + "Writing network info to disk @ {} (REASON = {})", + exportPath.toAbsolutePath(), + diskNetworkExport); + DiskStartupNetworks.writeNetworkInfo(state, exportPath); } } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BoundaryStateChangeListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BoundaryStateChangeListener.java index f0691852106e..4da8ca14d14a 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BoundaryStateChangeListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/BoundaryStateChangeListener.java @@ -39,7 +39,6 @@ import com.hedera.hapi.node.state.roster.RosterState; import com.hedera.hapi.node.state.throttles.ThrottleUsageSnapshots; import com.hedera.hapi.node.state.token.NetworkStakingRewards; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.transaction.ExchangeRateSet; import com.hedera.hapi.platform.state.PlatformState; import com.hedera.pbj.runtime.OneOf; @@ -232,9 +231,6 @@ private static OneOf queuePushChangeValueFor case PlatformState platformState -> { return new OneOf<>(SingletonUpdateChange.NewValueOneOfType.PLATFORM_STATE_VALUE, platformState); } - case TssStatus tssStatus -> { - return new OneOf<>(SingletonUpdateChange.NewValueOneOfType.TSS_STATUS_STATE_VALUE, tssStatus); - } default -> throw new IllegalArgumentException( "Unknown value type " + value.getClass().getName()); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/KVStateChangeListener.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/KVStateChangeListener.java index fb16df8673bc..74fbf95e1899 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/KVStateChangeListener.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/impl/KVStateChangeListener.java @@ -56,9 +56,9 @@ import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.hapi.node.state.token.Token; import com.hedera.hapi.node.state.token.TokenRelation; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.swirlds.state.StateChangeListener; @@ -241,8 +241,8 @@ private static MapChangeValue mapChangeValueFor(@NonNull final V value) { case TssVoteTransactionBody tssVoteTransactionBody -> MapChangeValue.newBuilder() .tssVoteValue(tssVoteTransactionBody) .build(); - case TssEncryptionKeyTransactionBody tssEncryptionKeyTransactionBody -> MapChangeValue.newBuilder() - .tssEncryptionKeyValue(tssEncryptionKeyTransactionBody) + case TssEncryptionKeys tssEncryptionKeys -> MapChangeValue.newBuilder() + .tssEncryptionKeysValue(tssEncryptionKeys) .build(); default -> throw new IllegalStateException( "Unexpected value: " + value.getClass().getSimpleName()); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchema.java index ab8d8fb642f9..edc27b1d036b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchema.java @@ -85,7 +85,7 @@ public V0560BlockStreamSchema(@NonNull final Consumer migratedBlockHashCo public void restart(@NonNull final MigrationContext ctx) { requireNonNull(ctx); final var state = ctx.newStates().getSingleton(BLOCK_STREAM_INFO_KEY); - if (ctx.previousVersion() == null) { + if (ctx.isGenesis()) { state.put(BlockStreamInfo.DEFAULT); } else { final var blockStreamInfo = state.get(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/schemas/V0490FeeSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/schemas/V0490FeeSchema.java index efc01409d355..83829e8e8277 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/schemas/V0490FeeSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/fees/schemas/V0490FeeSchema.java @@ -52,7 +52,7 @@ public void migrate(@NonNull final MigrationContext ctx) { if (isGenesis) { // Set the initial exchange rates (from the bootstrap config) as the midnight rates final var midnightRatesState = ctx.newStates().getSingleton(MIDNIGHT_RATES_STATE_KEY); - final var bootstrapConfig = ctx.configuration().getConfigData(BootstrapConfig.class); + final var bootstrapConfig = ctx.appConfig().getConfigData(BootstrapConfig.class); final var exchangeRateSet = ExchangeRateSet.newBuilder() .currentRate(ExchangeRate.newBuilder() .centEquiv(bootstrapConfig.ratesCurrentCentEquiv()) diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/schemas/V0490EntityIdSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/schemas/V0490EntityIdSchema.java index e63a8fd4196c..c38cc1d87031 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/schemas/V0490EntityIdSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/ids/schemas/V0490EntityIdSchema.java @@ -68,7 +68,7 @@ public Set statesToCreate() { public void migrate(@NonNull MigrationContext ctx) { final var entityIdState = ctx.newStates().getSingleton(ENTITY_ID_STATE_KEY); if (entityIdState.get() == null) { - final var config = ctx.configuration().getConfigData(HederaConfig.class); + final var config = ctx.appConfig().getConfigData(HederaConfig.class); final var entityNum = config.firstUserEntity() - 1; log.info("Setting initial entity id to {}", entityNum); entityIdState.put(new EntityNumber(entityNum)); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java index bd2cc7b10955..2df85c36d661 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java @@ -16,10 +16,15 @@ package com.hedera.node.app.info; +import static com.hedera.hapi.util.HapiUtils.parseAccount; +import static com.swirlds.platform.roster.RosterRetriever.buildRoster; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.state.addressbook.Node; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.service.addressbook.AddressBookService; @@ -37,9 +42,13 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.pbj.runtime.io.stream.ReadableStreamingData; import com.hedera.pbj.runtime.io.stream.WritableStreamingData; +import com.swirlds.common.platform.NodeId; 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.ReadableRosterStore; import com.swirlds.platform.state.service.ReadableRosterStoreImpl; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.State; import com.swirlds.state.lifecycle.StartupNetworks; import edu.umd.cs.findbugs.annotations.NonNull; @@ -62,41 +71,36 @@ public class DiskStartupNetworks implements StartupNetworks { private static final Logger log = LogManager.getLogger(DiskStartupNetworks.class); - private static final Pattern ROUND_DIR_PATTERN = Pattern.compile("\\d+"); - public static final String ARCHIVE = ".archive"; public static final String GENESIS_NETWORK_JSON = "genesis-network.json"; public static final String OVERRIDE_NETWORK_JSON = "override-network.json"; + public static final Pattern ROUND_DIR_PATTERN = Pattern.compile("\\d+"); - private final long selfNodeId; private final ConfigProvider configProvider; private final TssBaseService tssBaseService; private boolean isArchived = false; public DiskStartupNetworks( - final long selfNodeId, - @NonNull final ConfigProvider configProvider, - @NonNull final TssBaseService tssBaseService) { - this.selfNodeId = selfNodeId; + @NonNull final ConfigProvider configProvider, @NonNull final TssBaseService tssBaseService) { this.configProvider = requireNonNull(configProvider); this.tssBaseService = tssBaseService; } @Override public Network genesisNetworkOrThrow() { - return loadNetwork(configProvider.getConfiguration(), GENESIS_NETWORK_JSON) + return loadNetwork("genesis", configProvider.getConfiguration(), GENESIS_NETWORK_JSON) .orElseThrow(() -> new IllegalStateException("Genesis network not found")); } @Override public Optional overrideNetworkFor(final long roundNumber) { final var config = configProvider.getConfiguration(); - final var unscopedNetwork = loadNetwork(config, OVERRIDE_NETWORK_JSON); + final var unscopedNetwork = loadNetwork("override", config, OVERRIDE_NETWORK_JSON); if (unscopedNetwork.isPresent()) { return unscopedNetwork; } - return loadNetwork(config, "" + roundNumber, OVERRIDE_NETWORK_JSON); + return loadNetwork("override", config, "" + roundNumber, OVERRIDE_NETWORK_JSON); } @Override @@ -147,12 +151,13 @@ public void archiveStartupNetworks() { @Override public Network migrationNetworkOrThrow() { // FUTURE - look into sourcing this from a config.txt and public.pfx to ease migration - return loadNetwork(configProvider.getConfiguration(), OVERRIDE_NETWORK_JSON) + return loadNetwork("migration", configProvider.getConfiguration(), OVERRIDE_NETWORK_JSON) .orElseThrow(() -> new IllegalStateException("Transplant network not found")); } /** * Writes a JSON representation of the {@link Network} information in the given state to a given path. + * * @param state the state to write network information from. * @param path the path to write the JSON network information to. */ @@ -162,67 +167,133 @@ public static void writeNetworkInfo(@NonNull final State state, @NonNull final P new ReadableTssStoreImpl(state.getReadableStates(TssBaseService.NAME)), new ReadableNodeStoreImpl(state.getReadableStates(AddressBookService.NAME)), new ReadableRosterStoreImpl(state.getReadableStates(RosterService.NAME)), + new ReadablePlatformStateStore(state.getReadableStates(PlatformStateService.NAME)), path); } /** * Writes a JSON representation of the {@link Network} information in the given state to a given path. + * + * @param platformStateStore the platform state store to read the network information from * @param path the path to write the JSON network information to. */ public static void writeNetworkInfo( @NonNull final ReadableTssStore tssStore, @NonNull final ReadableNodeStore nodeStore, @NonNull final ReadableRosterStore rosterStore, + @NonNull final ReadablePlatformStateStore platformStateStore, @NonNull final Path path) { requireNonNull(tssStore); requireNonNull(nodeStore); requireNonNull(rosterStore); requireNonNull(path); - Optional.ofNullable(rosterStore.getActiveRoster()).ifPresent(activeRoster -> { - final var network = Network.newBuilder(); - final List nodeMetadata = new ArrayList<>(); - rosterStore.getActiveRoster().rosterEntries().forEach(entry -> { - final var node = requireNonNull(nodeStore.get(entry.nodeId())); - nodeMetadata.add(new NodeMetadata(entry, node, Bytes.EMPTY)); - }); - network.nodeMetadata(nodeMetadata); - final var sourceRosterHash = - Optional.ofNullable(rosterStore.getPreviousRosterHash()).orElse(Bytes.EMPTY); - tssStore.consensusRosterKeys( - sourceRosterHash, requireNonNull(rosterStore.getCurrentRosterHash()), rosterStore) - .ifPresent(rosterKeys -> - network.ledgerId(rosterKeys.ledgerId()).tssMessages(rosterKeys.tssMessages())); - try (final var fout = Files.newOutputStream(path)) { - Network.JSON.write(network.build(), new WritableStreamingData(fout)); - } catch (IOException e) { - log.warn("Failed to write network info", e); - } - }); + requireNonNull(platformStateStore); + Optional.ofNullable(rosterStore.getActiveRoster()) + .or(() -> Optional.ofNullable(buildRoster(platformStateStore.getAddressBook()))) + .ifPresent(activeRoster -> { + final var network = Network.newBuilder(); + final List nodeMetadata = new ArrayList<>(); + activeRoster.rosterEntries().forEach(entry -> { + final var node = requireNonNull(nodeStore.get(entry.nodeId())); + final var encryptionKey = Optional.ofNullable(tssStore.getTssEncryptionKeys(node.nodeId())) + .map(TssEncryptionKeys::currentEncryptionKey) + .orElse(Bytes.EMPTY); + nodeMetadata.add(new NodeMetadata(entry, node, encryptionKey)); + }); + network.nodeMetadata(nodeMetadata); + final var currentRosterHash = rosterStore.getCurrentRosterHash(); + if (currentRosterHash != null) { + final var sourceRosterHash = Optional.ofNullable(rosterStore.getPreviousRosterHash()) + .orElse(Bytes.EMPTY); + tssStore.consensusRosterKeys(sourceRosterHash, currentRosterHash, rosterStore) + .ifPresent(rosterKeys -> + network.ledgerId(rosterKeys.ledgerId()).tssMessages(rosterKeys.tssMessages())); + } + try (final var fout = Files.newOutputStream(path)) { + Network.JSON.write(network.build(), new WritableStreamingData(fout)); + } catch (IOException e) { + log.warn("Failed to write network info", e); + } + }); + } + + /** + * Converts a {@link AddressBook} to a {@link Network}. The resulting network will have no TSS + * keys of any kind. + * + * @param addressBook the address book to convert + * @return the converted network + */ + public static @NonNull Network fromLegacyAddressBook(@NonNull final AddressBook addressBook) { + final var roster = buildRoster(addressBook); + return Network.newBuilder() + .nodeMetadata(roster.rosterEntries().stream() + .map(rosterEntry -> { + final var nodeId = rosterEntry.nodeId(); + final var nodeAccountId = parseAccount( + addressBook.getAddress(NodeId.of(nodeId)).getMemo()); + // Currently the ReadableFreezeUpgradeActions.writeConfigLineAndPem() + // assumes that the gossip endpoints in the Node objects are in the order + // (Internal, External)...even though Roster format is the reverse :/ + final var legacyGossipEndpoints = List.of( + rosterEntry.gossipEndpoint().getLast(), + rosterEntry.gossipEndpoint().getFirst()); + return NodeMetadata.newBuilder() + .rosterEntry(rosterEntry) + .node(Node.newBuilder() + .nodeId(nodeId) + .accountId(nodeAccountId) + .description("node" + (nodeId + 1)) + .gossipEndpoint(legacyGossipEndpoints) + .serviceEndpoint(List.of()) + .gossipCaCertificate(rosterEntry.gossipCaCertificate()) + .grpcCertificateHash(Bytes.EMPTY) + .weight(rosterEntry.weight()) + .deleted(false) + .adminKey(Key.DEFAULT) + .build()) + .tssEncryptionKey(Bytes.EMPTY) + .build(); + }) + .toList()) + .build(); } /** * Attempts to load a {@link Network} from a given file in the directory whose relative path is given * by the provided {@link Configuration}. + * + * @param type the type of network to load * @param config the configuration to use to determine the location of the network file * @param segments the path segments of the file to load the network from * @return the loaded network, if it was found and successfully loaded */ - private Optional loadNetwork(@NonNull final Configuration config, @NonNull final String... segments) { + private Optional loadNetwork( + @NonNull final String type, @NonNull final Configuration config, @NonNull final String... segments) { final var path = networksPath(config, segments); + log.info("Loading {} network info from {}", type, path.toAbsolutePath()); if (Files.exists(path)) { try (final var fin = Files.newInputStream(path)) { final var network = Network.JSON.parse(new ReadableStreamingData(fin)); + log.info( + "Parsed {} network info for N={} nodes from {}", + type, + network.nodeMetadata().size(), + path.toAbsolutePath()); assertValidTssKeys(network); return Optional.of(network); } catch (Exception e) { - log.warn("Failed to load network info from {}", path.toAbsolutePath(), e); + log.warn("Failed to load {} network info from {}", path.toAbsolutePath(), e); } } return Optional.empty(); } /** - * If the given network has a ledger id, then it asserts that the TSS keys in the network are valid. + * If the given network has a ledger id, then it asserts that the TSS keys in the network are valid. This includes + * the encryption keys within the {@link NodeMetadata} messages, since without these specified the TSS messages + * would be unusable. + * * @param network the network to assert the TSS keys of * @throws IllegalArgumentException if the TSS keys are invalid */ @@ -240,7 +311,8 @@ private void assertValidTssKeys(@NonNull final Network network) { .getConfiguration() .getConfigData(TssConfig.class) .maxSharesPerNode(); - final var directory = TssUtils.computeParticipantDirectory(roster, maxSharesPerNode); + final var encryptionKeysFn = TssUtils.encryptionKeysFnFor(network); + final var directory = TssUtils.computeParticipantDirectory(roster, maxSharesPerNode, encryptionKeysFn); final var tssMessages = network.tssMessages().stream() .map(TssMessageTransactionBody::tssMessage) .map(Bytes::toByteArray) @@ -256,6 +328,7 @@ private void assertValidTssKeys(@NonNull final Network network) { /** * Attempts to archive the given segments in the given configuration. + * * @param segments the segments to archive */ private static void archiveIfPresent(@NonNull final Configuration config, @NonNull final String... segments) { @@ -275,6 +348,7 @@ private static void archiveIfPresent(@NonNull final Configuration config, @NonNu /** * Ensures that the archive directory exists in the given configuration. + * * @param config the configuration to ensure the archive directory exists in */ private static void ensureArchiveDir(@NonNull final Configuration config) throws IOException { @@ -283,6 +357,7 @@ private static void ensureArchiveDir(@NonNull final Configuration config) throws /** * Creates the given path as a directory if it does not already exist. + * * @param path the path to the directory create if it does not already exist */ private static void createIfAbsent(@NonNull final Path path) throws IOException { @@ -293,6 +368,7 @@ private static void createIfAbsent(@NonNull final Path path) throws IOException /** * Gets the path to the directory containing network files. + * * @param config the configuration to use to determine the location of the network files * @return the path to the directory containing network files */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/GenesisNetworkInfo.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/GenesisNetworkInfo.java index b6cbd3291846..e34ce5800a0f 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/GenesisNetworkInfo.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/GenesisNetworkInfo.java @@ -16,19 +16,15 @@ package com.hedera.node.app.info; -import static com.hedera.node.app.service.token.impl.handlers.BaseCryptoHandler.asAccount; import static java.util.Objects.requireNonNull; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.internal.network.Network; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.State; import com.swirlds.state.lifecycle.info.NetworkInfo; import com.swirlds.state.lifecycle.info.NodeInfo; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; -import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -38,19 +34,17 @@ */ public class GenesisNetworkInfo implements NetworkInfo { private final Bytes ledgerId; - private final Roster genesisRoster; private final Map nodeInfos; /** * Constructs a new {@link GenesisNetworkInfo} instance. * - * @param genesisRoster The genesis roster + * @param genesisNetwork The genesis network * @param ledgerId The ledger ID */ - public GenesisNetworkInfo(@NonNull final Roster genesisRoster, @NonNull final Bytes ledgerId) { + public GenesisNetworkInfo(@NonNull final Network genesisNetwork, @NonNull final Bytes ledgerId) { this.ledgerId = requireNonNull(ledgerId); - this.genesisRoster = requireNonNull(genesisRoster); - this.nodeInfos = buildNodeInfoMap(genesisRoster); + this.nodeInfos = nodeInfosFrom(genesisNetwork); } /** @@ -102,50 +96,18 @@ public void updateFrom(final State state) { throw new UnsupportedOperationException("Not implemented"); } - @Override - public Roster roster() { - return genesisRoster; - } - - /** - * Builds a map of node information from the given roster. The map is keyed by node ID. - * The node information is retrieved from the roster entry. - * If the node information is not found in the roster entry, it is not included in the map. - * - * @param roster The roster to retrieve the node information from - * @return A map of node information - */ - private Map buildNodeInfoMap(@NonNull final Roster roster) { + private static Map nodeInfosFrom(@NonNull final Network network) { final var nodeInfos = new LinkedHashMap(); - final var rosterEntries = roster.rosterEntries(); - for (final var rosterEntry : rosterEntries) { - nodeInfos.put(rosterEntry.nodeId(), fromRosterEntry(rosterEntry)); + for (final var metadata : network.nodeMetadata()) { + final var node = metadata.nodeOrThrow(); + final var nodeInfo = new NodeInfoImpl( + node.nodeId(), + node.accountIdOrThrow(), + node.weight(), + node.gossipEndpoint(), + node.gossipCaCertificate()); + nodeInfos.put(node.nodeId(), nodeInfo); } return nodeInfos; } - - /** - * Builds a node info from a roster entry from the given roster. - * Since this is only used in the genesis case, the account ID is generated from the node ID - * by adding 3 to it, as a default case. - * - * @param entry The roster entry - * @return The node info - */ - private NodeInfo fromRosterEntry(@NonNull final RosterEntry entry) { - // The RosterEntry has external ip address at index 0. - // The NodeInfo needs to have internal ip address at index 0. - // swap the internal and external endpoints when converting to NodeInfo. - final var gossipEndpointsCopy = new ArrayList<>(entry.gossipEndpoint()); - if (gossipEndpointsCopy.size() > 1) { - Collections.swap(gossipEndpointsCopy, 0, 1); - } - - return new NodeInfoImpl( - entry.nodeId(), - asAccount(entry.nodeId() + 3), - entry.weight(), - gossipEndpointsCopy, - entry.gossipCaCertificate()); - } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NodeInfoImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NodeInfoImpl.java index eddf0fdd3561..c61ab3010ded 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NodeInfoImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/NodeInfoImpl.java @@ -29,7 +29,7 @@ public record NodeInfoImpl( long nodeId, @NonNull AccountID accountId, - long stake, + long weight, List gossipEndpoints, @Nullable Bytes sigCertBytes) implements NodeInfo { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/StateNetworkInfo.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/StateNetworkInfo.java index 82532d9768b1..283e6a90576b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/StateNetworkInfo.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/StateNetworkInfo.java @@ -18,8 +18,6 @@ import static com.hedera.node.app.info.NodeInfoImpl.fromRosterEntry; import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; -import static com.swirlds.platform.roster.RosterRetriever.retrieveActiveOrGenesisRoster; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.AccountID; @@ -29,10 +27,7 @@ import com.hedera.node.app.service.addressbook.AddressBookService; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.LedgerConfig; -import com.hedera.node.config.data.TssConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.platform.state.service.PlatformStateService; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.state.State; import com.swirlds.state.lifecycle.info.NetworkInfo; import com.swirlds.state.lifecycle.info.NodeInfo; @@ -56,32 +51,35 @@ public class StateNetworkInfo implements NetworkInfo { private static final Logger log = LogManager.getLogger(StateNetworkInfo.class); private final long selfId; private final Bytes ledgerId; - private Roster activeRoster; - private final ConfigProvider configProvider; + /** + * The active roster, used to limit exposed node info to the active set of nodes. + */ + private final Roster activeRoster; + private final Map nodeInfos; /** * Constructs a new network information provider from the given state, roster, selfID, and configuration provider. * - * @param state the state to retrieve the network information from - * @param roster the roster to retrieve the network information from - * @param selfId the ID of the node + * @param selfId the ID of the node + * @param state the state to retrieve the network information from + * @param roster the roster to retrieve the network information from * @param configProvider the configuration provider to retrieve the ledger ID from */ public StateNetworkInfo( + final long selfId, @NonNull final State state, @NonNull final Roster roster, - final long selfId, @NonNull final ConfigProvider configProvider) { - this.selfId = selfId; + requireNonNull(state); + requireNonNull(configProvider); this.activeRoster = requireNonNull(roster); - // We keep this for now to check the keyCandidateRoster feature flag in updateFrom() - this.configProvider = requireNonNull(configProvider); - this.nodeInfos = buildNodeInfoMap(state); - // Load the ledger ID from configuration - final var config = configProvider.getConfiguration(); - final var ledgerConfig = config.getConfigData(LedgerConfig.class); - ledgerId = ledgerConfig.id(); + this.ledgerId = configProvider + .getConfiguration() + .getConfigData(LedgerConfig.class) + .id(); + this.nodeInfos = nodeInfosFrom(state); + this.selfId = selfId; } @NonNull @@ -115,19 +113,8 @@ public boolean containsNode(final long nodeId) { @Override public void updateFrom(@NonNull final State state) { - final var config = configProvider.getConfiguration(); - if (config.getConfigData(TssConfig.class).keyCandidateRoster()) { - activeRoster = retrieveActiveOrGenesisRoster(state); - } else { - // When the feature flag is disabled, the rosters in RosterService state are not up-to-date - // FUTURE: Once TSS Roster is implemented in the future, this will be removed and use roster state - // instead of the address book - final var readablePlatformStateStore = - new ReadablePlatformStateStore(state.getReadableStates(PlatformStateService.NAME)); - activeRoster = buildRoster(requireNonNull(readablePlatformStateStore.getAddressBook())); - } nodeInfos.clear(); - nodeInfos.putAll(buildNodeInfoMap(state)); + nodeInfos.putAll(nodeInfosFrom(state)); } /** @@ -138,16 +125,15 @@ public void updateFrom(@NonNull final State state) { * @param state the state to retrieve the node information from * @return a map of node information */ - private Map buildNodeInfoMap(final State state) { - final var nodeInfos = new LinkedHashMap(); - final var rosterEntries = activeRoster.rosterEntries(); - final ReadableKVState nodeState = + private Map nodeInfosFrom(@NonNull final State state) { + final ReadableKVState nodes = state.getReadableStates(AddressBookService.NAME).get(NODES_KEY); - for (final var rosterEntry : rosterEntries) { + final Map nodeInfos = new LinkedHashMap<>(); + for (final var rosterEntry : activeRoster.rosterEntries()) { // At genesis the node store is derived from the roster, hence must have info for every // node id; and from then on, the roster is derived from the node store, and hence the // node store must have every node id in the roster. - final var node = nodeState.get(new EntityNumber(rosterEntry.nodeId())); + final var node = nodes.get(new EntityNumber(rosterEntry.nodeId())); if (node != null) { // Notice it's possible the node could be deleted here, because a DAB transaction removed // it from the future address book; that doesn't mean we should stop using it in the current @@ -166,9 +152,4 @@ private Map buildNodeInfoMap(final State state) { } return nodeInfos; } - - @Override - public Roster roster() { - return activeRoster; - } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/UnavailableNetworkInfo.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/UnavailableNetworkInfo.java index 87fb87732f66..692700ce6e98 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/UnavailableNetworkInfo.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/UnavailableNetworkInfo.java @@ -16,7 +16,6 @@ package com.hedera.node.app.info; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.State; import com.swirlds.state.lifecycle.info.NetworkInfo; @@ -66,9 +65,4 @@ public boolean containsNode(final long nodeId) { public void updateFrom(final State state) { throw new UnsupportedOperationException("Not implemented"); } - - @Override - public Roster roster() { - throw new UnsupportedOperationException("Not implemented"); - } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java index 37e67a8e1f4a..470cc01d0bcd 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java @@ -20,14 +20,14 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.node.app.roster.schemas.V057RosterSchema; +import com.hedera.node.app.roster.schemas.V0540RosterSchema; import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.WritableRosterStore; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.lifecycle.Service; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.function.Predicate; +import java.util.function.Supplier; /** * A {@link com.hedera.hapi.node.state.roster.Roster} implementation of the {@link Service} interface. @@ -49,9 +49,18 @@ public class RosterService implements Service { * adopted at an upgrade boundary. */ private final Predicate canAdopt; + /** + * Required until the upgrade that adopts the roster lifecycle; at that upgrade boundary, + * we must initialize the active roster from the platform state's legacy address books. + */ + @Deprecated + private final Supplier platformStateStoreFactory; - public RosterService(@NonNull final Predicate canAdopt) { + public RosterService( + @NonNull final Predicate canAdopt, + @NonNull final Supplier platformStateStoreFactory) { this.canAdopt = requireNonNull(canAdopt); + this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); } @NonNull @@ -68,7 +77,6 @@ public int migrationOrder() { @Override public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); - registry.register(new V0540RosterSchema()); - registry.register(new V057RosterSchema(canAdopt, WritableRosterStore::new, ReadablePlatformStateStore::new)); + registry.register(new V0540RosterSchema(canAdopt, WritableRosterStore::new, platformStateStoreFactory)); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/RosterTransplantSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/RosterTransplantSchema.java new file mode 100644 index 000000000000..0259532b9872 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/RosterTransplantSchema.java @@ -0,0 +1,66 @@ +/* + * 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.roster.schemas; + +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.roster.RosterService; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.Function; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * The {@link Schema#restart(MigrationContext)} implementation whereby the {@link RosterService} ensures that any + * roster overrides in the startup assets are copied into the state. + *

    + * Important: The latest {@link RosterService} schema should always implement this interface. + */ +public interface RosterTransplantSchema { + Logger log = LogManager.getLogger(RosterTransplantSchema.class); + + /** + * Restart the {@link RosterService} by copying any roster overrides from the startup assets into the state. + * @param ctx the migration context + * @param rosterStoreFactory the factory to use to create the writable roster store + */ + default boolean restart( + @NonNull final MigrationContext ctx, + @NonNull final Function rosterStoreFactory) { + requireNonNull(ctx); + if (ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + final long roundNumber = ctx.roundNumber(); + final var startupNetworks = ctx.startupNetworks(); + final var overrideNetwork = startupNetworks.overrideNetworkFor(roundNumber); + overrideNetwork.ifPresent(network -> { + final long activeRoundNumber = roundNumber + 1; + log.info("Adopting roster from override network in round {}", activeRoundNumber); + final var rosterStore = rosterStoreFactory.apply(ctx.newStates()); + rosterStore.putActiveRoster(RosterUtils.rosterFrom(network), activeRoundNumber); + startupNetworks.setOverrideRound(roundNumber); + }); + return overrideNetwork.isPresent(); + } + return false; + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java new file mode 100644 index 000000000000..4c2220218178 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java @@ -0,0 +1,134 @@ +/* + * 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.roster.schemas; + +import static com.swirlds.platform.roster.RosterRetriever.buildRoster; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterState; +import com.hedera.node.app.version.ServicesSoftwareVersion; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.platform.state.service.ReadablePlatformStateStore; +import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.lifecycle.StateDefinition; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Initial {@link com.hedera.node.app.roster.RosterService} schema that registers two states, + *

      + *
    1. A mapping from roster hashes to rosters (which may be either candidate or active).
    2. + *
    3. A singleton that contains the history of active rosters along with the round numbers where + * they were adopted; along with the hash of a candidate roster if there is one.
    4. + *
    + */ +public class V0540RosterSchema extends Schema implements RosterTransplantSchema { + private static final Logger log = LogManager.getLogger(V0540RosterSchema.class); + + public static final String ROSTER_KEY = "ROSTERS"; + public static final String ROSTER_STATES_KEY = "ROSTER_STATE"; + + private static final SemanticVersion VERSION = + SemanticVersion.newBuilder().major(0).minor(54).patch(0).build(); + + /** + * The delegate schema that defines the base roster schema. + */ + private final V0540RosterBaseSchema baseSchema = new V0540RosterBaseSchema(); + /** + * The test to use to determine if a candidate roster may be adopted at an upgrade boundary. + */ + private final Predicate canAdopt; + /** + * The factory to use to create the writable roster store. + */ + private final Function rosterStoreFactory; + /** + * Required until the upgrade that adopts the roster lifecycle; at that upgrade boundary, + * we must initialize the active roster from the platform state's legacy address books. + */ + @Deprecated + private final Supplier platformStateStoreFactory; + + public V0540RosterSchema( + @NonNull final Predicate canAdopt, + @NonNull final Function rosterStoreFactory, + @NonNull final Supplier platformStateStoreFactory) { + super(VERSION); + this.canAdopt = requireNonNull(canAdopt); + this.rosterStoreFactory = requireNonNull(rosterStoreFactory); + this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); + } + + @Override + public @NonNull Set statesToCreate() { + return baseSchema.statesToCreate(); + } + + @Override + public void migrate(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + final var rosterState = ctx.newStates().getSingleton(ROSTER_STATES_KEY); + if (!ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + rosterState.put(RosterState.DEFAULT); + } + } + + @Override + public void restart(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + if (!RosterTransplantSchema.super.restart(ctx, rosterStoreFactory) + && ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + final var startupNetworks = ctx.startupNetworks(); + final var rosterStore = rosterStoreFactory.apply(ctx.newStates()); + final var activeRoundNumber = ctx.roundNumber() + 1; + if (ctx.isGenesis()) { + rosterStore.putActiveRoster(RosterUtils.rosterFrom(startupNetworks.genesisNetworkOrThrow()), 0L); + } else if (rosterStore.getActiveRoster() == null) { + // (FUTURE) Once the roster lifecycle is active by default, remove this code building an initial + // roster history from the last address book and the first roster at the upgrade boundary + final var addressBook = platformStateStoreFactory.get().getAddressBook(); + final var previousRoster = buildRoster(requireNonNull(addressBook)); + rosterStore.putActiveRoster(previousRoster, 0); + final var currentRoster = RosterUtils.rosterFrom(startupNetworks.migrationNetworkOrThrow()); + rosterStore.putActiveRoster(currentRoster, activeRoundNumber); + } else if (ctx.isUpgrade(ServicesSoftwareVersion::from, ServicesSoftwareVersion::new)) { + final var candidateRoster = rosterStore.getCandidateRoster(); + if (candidateRoster == null) { + log.info("No candidate roster to adopt in round {}", activeRoundNumber); + } else if (canAdopt.test(candidateRoster)) { + log.info("Adopting candidate roster in round {}", activeRoundNumber); + rosterStore.adoptCandidateRoster(activeRoundNumber); + } else { + log.info("Rejecting candidate roster in round {}", activeRoundNumber); + } + } + } + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V057RosterSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V057RosterSchema.java deleted file mode 100644 index 5af1663e3962..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V057RosterSchema.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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.roster.schemas; - -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.node.app.version.ServicesSoftwareVersion; -import com.hedera.node.internal.network.Network; -import com.hedera.node.internal.network.NodeMetadata; -import com.swirlds.platform.config.AddressBookConfig; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; -import com.swirlds.platform.state.service.WritableRosterStore; -import com.swirlds.state.lifecycle.MigrationContext; -import com.swirlds.state.lifecycle.Schema; -import com.swirlds.state.spi.WritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Function; -import java.util.function.Predicate; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A restart-only schema that ensures state has a current roster if {@link AddressBookConfig#useRosterLifecycle()} is set. - */ -public class V057RosterSchema extends Schema { - private static final Logger log = LogManager.getLogger(V057RosterSchema.class); - - private static final long GENESIS_ROUND_NO = 0L; - private static final SemanticVersion VERSION = - SemanticVersion.newBuilder().major(0).minor(57).build(); - - /** - * The test to use to determine if a candidate roster may be - * adopted at an upgrade boundary. - */ - private final Predicate canAdopt; - /** - * The factory to use to create the writable roster store. - */ - private final Function rosterStoreFactory; - - private final Function platformStateStoreFactory; - - public V057RosterSchema( - @NonNull final Predicate canAdopt, - @NonNull final Function rosterStoreFactory, - @NonNull final Function platformStateStoreFactory) { - super(VERSION); - this.canAdopt = requireNonNull(canAdopt); - this.rosterStoreFactory = requireNonNull(rosterStoreFactory); - this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); - } - - @Override - public void restart(@NonNull final MigrationContext ctx) { - requireNonNull(ctx); - if (!ctx.configuration().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { - return; - } - final var rosterStore = rosterStoreFactory.apply(ctx.newStates()); - final var startupNetworks = ctx.startupNetworks(); - if (ctx.isGenesis()) { - setActiveRoster(GENESIS_ROUND_NO, rosterStore, startupNetworks.genesisNetworkOrThrow()); - } else { - final long roundNumber = ctx.roundNumber(); - final var overrideNetwork = startupNetworks.overrideNetworkFor(roundNumber); - if (overrideNetwork.isPresent()) { - // currentRound := state round +1 - final long currentRound = roundNumber + 1; - log.info("Found override network for round {}", currentRound); - - // If there is no active roster in the roster state. - if (rosterStore.getActiveRoster() == null) { - // Read the current AddressBooks from the platform state. - // previousRoster := translateToRoster(currentAddressBook) - final var platformState = platformStateStoreFactory.apply(ctx.newStates()); - final var previousRoster = buildRoster(platformState.getAddressBook()); - // (previousRoster, previousRound) := (previousRoster, 0) - // set (previousRoster, 0) as the active roster in the roster state. - rosterStore.putActiveRoster(previousRoster, 0L); - } - - // set (overrideRoster, currentRound) as the active roster in the roster state. - setActiveRoster(currentRound, rosterStore, overrideNetwork.get()); - startupNetworks.setOverrideRound(roundNumber); - } else if (isUpgrade(ctx)) { - if (rosterStore.getActiveRoster() == null) { - // currentRound := state round +1 - final long currentRound = roundNumber + 1; - log.info("Migrating active roster at round {}", currentRound); - - // Read the current AddressBooks from the platform state. - // previousRoster := translateToRoster(currentAddressBook) - // set (previousRoster, 0) as the active roster in the roster state. - final var platformState = platformStateStoreFactory.apply(ctx.newStates()); - final var previousRoster = buildRoster(platformState.getAddressBook()); - rosterStore.putActiveRoster(previousRoster, 0L); - - // If there is no active roster at a migration boundary, we - // must have a migration network in the startup assets - // configAddressBook := Read the address book in config.txt - final var network = startupNetworks.migrationNetworkOrThrow(); - - // currentRoster := translateToRoster(configAddressBook) - // set (currentRoster, currentRound) as the active roster in the roster state. - rosterStore.putActiveRoster(rosterFrom(network), currentRound); - } else { - // candidateRoster := read the candidate roster from the roster state. - final var candidateRoster = rosterStore.getCandidateRoster(); - if (canAdopt.test(candidateRoster)) { - // currentRound := state round +1 - final long currentRound = roundNumber + 1; - log.info("Adopting candidate roster at round {}", currentRound); - - // set (candidateRoster, currentRound) as the new active roster in the roster state. - rosterStore.adoptCandidateRoster(currentRound); - } - } - } - } - } - - private void setActiveRoster( - final long roundNumber, @NonNull final WritableRosterStore rosterStore, @NonNull final Network network) { - rosterStore.putActiveRoster(rosterFrom(network), roundNumber); - } - - private Roster rosterFrom(@NonNull final Network network) { - return new Roster(network.nodeMetadata().stream() - .map(NodeMetadata::rosterEntryOrThrow) - .toList()); - } - - private boolean isUpgrade(@NonNull final MigrationContext ctx) { - return ServicesSoftwareVersion.from(ctx.configuration()) - .compareTo(new ServicesSoftwareVersion(requireNonNull(ctx.previousVersion()))) - > 0; - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/MigrationContextImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/MigrationContextImpl.java index ebb5c549e287..92fa458ff1e7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/MigrationContextImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/MigrationContextImpl.java @@ -37,7 +37,7 @@ * * @param previousStates The previous states. * @param newStates The new states, preloaded with any new state definitions. - * @param configuration The configuration to use + * @param appConfig The configuration to use * @param genesisNetworkInfo The genesis network info * @param writableEntityIdStore The instance responsible for generating new entity IDs (ONLY during * migrations). Note that this is nullable only because it cannot exist @@ -47,7 +47,8 @@ public record MigrationContextImpl( @NonNull ReadableStates previousStates, @NonNull WritableStates newStates, - @NonNull Configuration configuration, + @NonNull Configuration appConfig, + @NonNull Configuration platformConfig, @Nullable NetworkInfo genesisNetworkInfo, @Nullable WritableEntityIdStore writableEntityIdStore, @Nullable SemanticVersion previousVersion, @@ -58,7 +59,8 @@ public record MigrationContextImpl( public MigrationContextImpl { requireNonNull(previousStates); requireNonNull(newStates); - requireNonNull(configuration); + requireNonNull(appConfig); + requireNonNull(platformConfig); } @Override diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/OrderedServiceMigrator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/OrderedServiceMigrator.java index 302c60c8fb36..89a5de531bd7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/OrderedServiceMigrator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/OrderedServiceMigrator.java @@ -64,8 +64,8 @@ public class OrderedServiceMigrator implements ServiceMigrator { * @param servicesRegistry The services registry to use for the migrations * @param previousVersion The previous version of the state * @param currentVersion The current version of the state - * @param nodeConfiguration The system configuration to use at the time of migration - * @param platformConfiguration The platform configuration to use for subsequent object initializations + * @param appConfig The system configuration to use at the time of migration + * @param platformConfig The platform configuration to use for subsequent object initializations * @param genesisNetworkInfo The network information to use for the migrations. * This is only used in genesis case * @param metrics The metrics to use for the migrations @@ -78,19 +78,19 @@ public List doMigrations( @NonNull final ServicesRegistry servicesRegistry, @Nullable final SoftwareVersion previousVersion, @NonNull final SoftwareVersion currentVersion, - @NonNull final Configuration nodeConfiguration, - @NonNull final Configuration platformConfiguration, + @NonNull final Configuration appConfig, + @NonNull final Configuration platformConfig, @Nullable final NetworkInfo genesisNetworkInfo, @NonNull final Metrics metrics, @NonNull final StartupNetworks startupNetworks) { requireNonNull(state); requireNonNull(currentVersion); - requireNonNull(nodeConfiguration); - requireNonNull(platformConfiguration); + requireNonNull(appConfig); + requireNonNull(platformConfig); requireNonNull(metrics); final Map sharedValues = new HashMap<>(); - final var migrationStateChanges = new MigrationStateChanges(state, nodeConfiguration); + final var migrationStateChanges = new MigrationStateChanges(state, appConfig); logger.info("Migrating Entity ID Service as pre-requisite for other services"); final var entityIdRegistration = servicesRegistry.registrations().stream() .filter(service -> EntityIdService.NAME.equals(service.service().getServiceName())) @@ -104,8 +104,8 @@ public List doMigrations( state, deserializedPbjVersion, currentVersion.getPbjSemanticVersion(), - nodeConfiguration, - platformConfiguration, + appConfig, + platformConfig, genesisNetworkInfo, metrics, // We call with null here because we're migrating the entity ID service itself @@ -143,8 +143,8 @@ public List doMigrations( state, deserializedPbjVersion, currentVersion.getPbjSemanticVersion(), - nodeConfiguration, - platformConfiguration, + appConfig, + platformConfig, genesisNetworkInfo, metrics, entityIdStore, diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceMigrator.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceMigrator.java index 949995a84766..eb3ede447ec3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceMigrator.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/services/ServiceMigrator.java @@ -41,8 +41,8 @@ public interface ServiceMigrator { * @param servicesRegistry The services registry to use for the migrations * @param previousVersion The previous version of the state * @param currentVersion The current version of the state - * @param nodeConfiguration The configuration to use for the migrations - * @param platformConfiguration The platform configuration to use for subsequent object initializations + * @param appConfig The app configuration to use for the migrations + * @param platformConfig The platform configuration to use for subsequent object initializations * @param genesisNetworkInfo The network information to use for the migrations * @param metrics The metrics to use for the migrations * @param startupNetworks The startup networks to use for the migrations @@ -53,8 +53,8 @@ List doMigrations( @NonNull ServicesRegistry servicesRegistry, @Nullable SoftwareVersion previousVersion, @NonNull SoftwareVersion currentVersion, - @NonNull Configuration nodeConfiguration, - @NonNull Configuration platformConfiguration, + @NonNull Configuration appConfig, + @NonNull Configuration platformConfig, @Nullable NetworkInfo genesisNetworkInfo, @NonNull Metrics metrics, @NonNull StartupNetworks startupNetworks); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java index ae4872e198b9..c111793b24c7 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/state/merkle/MerkleSchemaRegistry.java @@ -174,8 +174,8 @@ private record RedefinedWritableStates(WritableStates beforeStates, WritableStat * @param previousVersion The version of state loaded from disk. Possibly null. * @param currentVersion The current version. Never null. Must be newer than {@code * previousVersion}. - * @param nodeConfiguration The system configuration to use at the time of migration - * @param platformConfiguration The platform configuration to use for subsequent object initializations + * @param appConfig The system configuration to use at the time of migration + * @param platformConfig The platform configuration to use for subsequent object initializations * @param genesisNetworkInfo The network information to use at the time of migration * @param sharedValues A map of shared values for cross-service migration patterns * @param migrationStateChanges Tracker for state changes during migration @@ -189,8 +189,8 @@ public void migrate( @NonNull final State state, @Nullable final SemanticVersion previousVersion, @NonNull final SemanticVersion currentVersion, - @NonNull final Configuration nodeConfiguration, - @NonNull final Configuration platformConfiguration, + @NonNull final Configuration appConfig, + @NonNull final Configuration platformConfig, @Nullable final NetworkInfo genesisNetworkInfo, @NonNull final Metrics metrics, @Nullable final WritableEntityIdStore entityIdStore, @@ -199,8 +199,8 @@ public void migrate( @NonNull final StartupNetworks startupNetworks) { requireNonNull(state); requireNonNull(currentVersion); - requireNonNull(nodeConfiguration); - requireNonNull(platformConfiguration); + requireNonNull(appConfig); + requireNonNull(platformConfig); requireNonNull(metrics); requireNonNull(sharedValues); requireNonNull(migrationStateChanges); @@ -226,7 +226,7 @@ public void migrate( () -> HapiUtils.toString(latestVersion)); for (final var schema : schemas) { final var applications = - schemaApplications.computeApplications(previousVersion, latestVersion, schema, nodeConfiguration); + schemaApplications.computeApplications(previousVersion, latestVersion, schema, appConfig); logger.info("Applying {} schema {} ({})", serviceName, schema.getVersion(), applications); // Now we can migrate the schema and then commit all the changes // We just have one merkle tree -- the just-loaded working tree -- to work from. @@ -251,7 +251,7 @@ public void migrate( && alreadyIncludesStateDefs(previousVersion, s.getVersion())) .toList(); final var redefinedWritableStates = applyStateDefinitions( - schema, schemasAlreadyInState, nodeConfiguration, platformConfiguration, metrics, stateRoot); + schema, schemasAlreadyInState, appConfig, platformConfig, metrics, stateRoot); writableStates = redefinedWritableStates.beforeStates(); newStates = redefinedWritableStates.afterStates(); } else { @@ -261,7 +261,8 @@ && alreadyIncludesStateDefs(previousVersion, s.getVersion())) final var migrationContext = new MigrationContextImpl( previousStates, newStates, - nodeConfiguration, + appConfig, + platformConfig, genesisNetworkInfo, entityIdStore, previousVersion, 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 index 73652706d96f..69f39eaf4dd8 100644 --- 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 @@ -71,6 +71,11 @@ default String getServiceName() { return NAME; } + @Override + default int migrationOrder() { + return MIGRATION_ORDER; + } + /** * Returns the status of the TSS service relative to the given roster, ledger id, and given TSS base state. * @@ -161,7 +166,7 @@ Roster chooseRosterForNetwork( * Generates the participant directory for the active roster. * @param state the network state */ - void generateParticipantDirectory(@NonNull State state); + void ensureParticipantDirectoryKnown(@NonNull State state); /** * Returns the ledger id from the given TSS participant directory and TSS messages. diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java index 05ad252c1b34..5f43d1c2ca4c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java @@ -18,6 +18,7 @@ import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; import static com.hedera.node.app.tss.TssBaseService.Status.PENDING_LEDGER_ID; +import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; import static com.hedera.node.app.tss.handlers.TssUtils.computeParticipantDirectory; import static com.hedera.node.app.tss.handlers.TssUtils.hasMetThreshold; import static com.swirlds.platform.roster.RosterRetriever.getCandidateRosterHash; @@ -26,6 +27,7 @@ import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; +import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.tss.api.TssMessage; import com.hedera.cryptography.tss.api.TssParticipantDirectory; import com.hedera.hapi.node.state.primitives.ProtoBytes; @@ -34,15 +36,17 @@ import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssShareSignatureTransactionBody; import com.hedera.node.app.roster.RosterService; +import com.hedera.node.app.roster.schemas.V0540RosterSchema; import com.hedera.node.app.services.ServiceMigrator; import com.hedera.node.app.spi.AppContext; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.store.ReadableStoreFactory; +import com.hedera.node.app.tss.api.FakeGroupElement; import com.hedera.node.app.tss.api.TssLibrary; import com.hedera.node.app.tss.handlers.TssHandlers; import com.hedera.node.app.tss.handlers.TssSubmissions; import com.hedera.node.app.tss.schemas.V0560TssBaseSchema; -import com.hedera.node.app.tss.schemas.V0570TssBaseSchema; +import com.hedera.node.app.tss.schemas.V0580TssBaseSchema; import com.hedera.node.app.tss.stores.ReadableTssStore; import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; import com.hedera.node.app.version.ServicesSoftwareVersion; @@ -53,12 +57,12 @@ import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.service.ReadableRosterStore; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; import com.swirlds.platform.system.InitTrigger; import com.swirlds.state.State; import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; import java.time.Instant; import java.time.InstantSource; import java.util.LinkedHashMap; @@ -69,6 +73,8 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.LongFunction; +import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -92,7 +98,7 @@ public class TssBaseServiceImpl implements TssBaseService { private final Executor signingExecutor; private final TssKeysAccessor tssKeysAccessor; private final TssDirectoryAccessor tssDirectoryAccessor; - private final AppContext appContext; + private final Supplier configSupplier; public TssBaseServiceImpl( @NonNull final AppContext appContext, @@ -105,7 +111,7 @@ public TssBaseServiceImpl( this.tssLibrary = requireNonNull(tssLibrary); this.signingExecutor = requireNonNull(signingExecutor); this.tssLibraryExecutor = requireNonNull(tssLibraryExecutor); - this.appContext = requireNonNull(appContext); + this.configSupplier = appContext.configSupplier(); final var component = DaggerTssBaseServiceComponent.factory() .create( tssLibrary, @@ -127,7 +133,7 @@ public TssBaseServiceImpl( public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); registry.register(new V0560TssBaseSchema()); - registry.register(new V0570TssBaseSchema()); + registry.register(new V0580TssBaseSchema()); } @Override @@ -166,9 +172,11 @@ public void setCandidateRoster(@NonNull final Roster candidateRoster, @NonNull f final var maxSharesPerNode = context.configuration().getConfigData(TssConfig.class).maxSharesPerNode(); - final var selfId = (int) context.networkInfo().selfNodeInfo().nodeId(); - final var candidateDirectory = computeParticipantDirectory(candidateRoster, maxSharesPerNode); + // TODO - use the real encryption keys from state + final LongFunction encryptionKeyFn = + nodeId -> new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(nodeId)), SIGNATURE_SCHEMA); + final var candidateDirectory = computeParticipantDirectory(candidateRoster, maxSharesPerNode, encryptionKeyFn); final var activeRoster = requireNonNull( context.storeFactory().readableStore(ReadableRosterStore.class).getActiveRoster()); final var activeRosterHash = RosterUtils.hash(activeRoster).getBytes(); @@ -206,11 +214,7 @@ public void requestLedgerSignature( final var mockSignature = noThrowSha384HashOf(messageHash); CompletableFuture.runAsync( () -> { - if (appContext - .configSupplier() - .get() - .getConfigData(TssConfig.class) - .signWithLedgerId()) { + if (configSupplier.get().getConfigData(TssConfig.class).signWithLedgerId()) { submitShareSignatures(messageHash, lastUsedConsensusTime); } else { // This is only for testing purposes when the candidate roster is @@ -269,10 +273,10 @@ public TssHandlers tssHandlers() { @Override @NonNull public Roster chooseRosterForNetwork( - @NonNull State state, - @NonNull InitTrigger trigger, - @NonNull ServiceMigrator serviceMigrator, - @NonNull ServicesSoftwareVersion version, + @NonNull final State state, + @NonNull final InitTrigger trigger, + @NonNull final ServiceMigrator serviceMigrator, + @NonNull final ServicesSoftwareVersion version, @NonNull final Configuration configuration, @NonNull final Roster overrideRoster) { if (!configuration.getConfigData(TssConfig.class).keyCandidateRoster()) { @@ -304,7 +308,7 @@ public void regenerateKeyMaterial(@NonNull final State state) { } @Override - public void generateParticipantDirectory(@NonNull final State state) { + public void ensureParticipantDirectoryKnown(@NonNull final State state) { tssDirectoryAccessor.generateTssParticipantDirectory(state); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java index 0e644cc05460..7d6fa982fbfe 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java @@ -104,6 +104,7 @@ public CompletableFuture getVoteFuture( final var tssMessageBodies = tssStore.getMessagesForTarget(targetRosterHash); final var voteKey = new TssVoteMapKey( targetRosterHash, context.networkInfo().selfNodeInfo().nodeId()); + // We only vote once for a given target roster hash if (tssStore.getVote(voteKey) == null) { return computeVote(tssMessageBodies, directory).exceptionally(e -> { log.error("Error computing public keys and signing", e); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java index b6062092544a..088f071a38c1 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java @@ -16,18 +16,22 @@ package com.hedera.node.app.tss; +import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; import static com.hedera.node.app.tss.handlers.TssUtils.computeParticipantDirectory; import static java.util.Objects.requireNonNull; +import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.tss.api.TssParticipantDirectory; import com.hedera.node.app.spi.AppContext; import com.hedera.node.app.store.ReadableStoreFactory; +import com.hedera.node.app.tss.api.FakeGroupElement; import com.hedera.node.config.data.TssConfig; import com.swirlds.config.api.Configuration; import com.swirlds.platform.state.service.ReadableRosterStore; import com.swirlds.state.State; -import com.swirlds.state.lifecycle.info.NodeInfo; import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; +import java.util.function.LongFunction; import java.util.function.Supplier; import javax.inject.Inject; import javax.inject.Singleton; @@ -37,34 +41,58 @@ */ @Singleton public class TssDirectoryAccessor { - private TssParticipantDirectory tssParticipantDirectory; private final Supplier configurationSupplier; - private final Supplier nodeInfoSupplier; + + /** + * Non-final because it is lazy-initialized once state is available. + */ + private TssParticipantDirectory tssParticipantDirectory; @Inject public TssDirectoryAccessor(@NonNull final AppContext appContext) { - this.configurationSupplier = appContext.configSupplier(); - this.nodeInfoSupplier = appContext.selfNodeInfoSupplier(); + this.configurationSupplier = requireNonNull(appContext).configSupplier(); } /** * Generates the participant directory for the active roster. - * - * @param state state + * @param state state */ public void generateTssParticipantDirectory(@NonNull final State state) { + final var readableStoreFactory = new ReadableStoreFactory(state); + final var rosterStore = readableStoreFactory.getStore(ReadableRosterStore.class); + // TODO - use the real encryption keys from state + final LongFunction encryptionKeyFn = + nodeId -> new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(nodeId)), SIGNATURE_SCHEMA); + activeParticipantDirectoryFrom(rosterStore, encryptionKeyFn); + } + + /** + * Returns the {@link TssParticipantDirectory} for the active roster in the given store. + * + * @param rosterStore the store from which to retrieve the active roster + * @param encryptionKeyFn the function to get the TSS encryption keys + * @return the {@link TssParticipantDirectory} for the active roster + */ + public TssParticipantDirectory activeParticipantDirectoryFrom( + @NonNull final ReadableRosterStore rosterStore, @NonNull final LongFunction encryptionKeyFn) { + // Since the active roster can only change when restarting the JVM, we only compute it once + // per instantiation of this singleton accessor if (tssParticipantDirectory != null) { - return; + return tssParticipantDirectory; } + final var activeRoster = requireNonNull(rosterStore.getActiveRoster()); final var maxSharesPerNode = configurationSupplier.get().getConfigData(TssConfig.class).maxSharesPerNode(); - final var readableStoreFactory = new ReadableStoreFactory(state); - final var rosterStore = readableStoreFactory.getStore(ReadableRosterStore.class); - final var activeRoster = requireNonNull(rosterStore.getActiveRoster()); - this.tssParticipantDirectory = computeParticipantDirectory(activeRoster, maxSharesPerNode); + tssParticipantDirectory = computeParticipantDirectory(activeRoster, maxSharesPerNode, encryptionKeyFn); + return tssParticipantDirectory; } - public TssParticipantDirectory activeParticipantDirectory() { - return tssParticipantDirectory; + /** + * Returns the {@link TssParticipantDirectory} for the active roster. + * @return the {@link TssParticipantDirectory} for the active roster + * @throws NullPointerException if the participant directory has not been generated + */ + public @NonNull TssParticipantDirectory activeParticipantDirectoryOrThrow() { + return requireNonNull(tssParticipantDirectory); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java index 762ea385a59f..2880a681d355 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java @@ -64,7 +64,7 @@ public void generateKeyMaterialForActiveRoster(@NonNull final State state) { final var tssStore = storeFactory.getStore(ReadableTssStore.class); final var rosterStore = storeFactory.getStore(ReadableRosterStore.class); final var activeRosterHash = requireNonNull(rosterStore.getCurrentRosterHash()); - final var activeParticipantDirectory = tssDirectoryAccessor.activeParticipantDirectory(); + final var activeParticipantDirectory = tssDirectoryAccessor.activeParticipantDirectoryOrThrow(); final var tssMessageBodies = tssStore.getMessagesForTarget(activeRosterHash); final var validTssMessages = getTssMessages(tssMessageBodies, activeParticipantDirectory, tssLibrary); final var activeRosterShares = getTssPrivateShares(activeParticipantDirectory, tssStore, activeRosterHash); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java index dc434daef636..1e5489cd7bb3 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java @@ -83,7 +83,7 @@ public void handle(@NonNull final HandleContext context) throws HandleException tssStore.put(key, op); // Obtain the directory of participants for the target roster - final var directory = tssDirectoryAccessor.activeParticipantDirectory(); + final var directory = tssDirectoryAccessor.activeParticipantDirectoryOrThrow(); // Schedule work to potentially compute a signed vote for the new key material of the target // roster, if this message was valid and passed the threshold number of messages required tssCryptographyManager diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java index 745be12b0be6..befa5d0ed375 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java @@ -17,6 +17,7 @@ package com.hedera.node.app.tss.handlers; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toMap; import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.bls.GroupAssignment; @@ -29,40 +30,62 @@ import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.node.app.tss.api.FakeGroupElement; import com.hedera.node.app.tss.api.TssLibrary; +import com.hedera.node.internal.network.Network; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.function.LongFunction; public class TssUtils { public static final SignatureSchema SIGNATURE_SCHEMA = SignatureSchema.create(Curve.ALT_BN128, GroupAssignment.SHORT_SIGNATURES); + + /** + * Given a network, return a function that maps node IDs to their TSS encryption keys in the network (if + * this information is available). + * @param network the network + * @return a function that maps node IDs to their TSS encryption keys + */ + public static LongFunction encryptionKeysFnFor(@NonNull final Network network) { + return network.nodeMetadata().stream() + .filter(metadata -> metadata.tssEncryptionKey().length() > 0) + .collect(toMap( + metadata -> metadata.nodeOrThrow().nodeId(), + // TODO - compute the real public key + metadata -> new BlsPublicKey( + new FakeGroupElement(new BigInteger( + metadata.tssEncryptionKey().toByteArray())), + SIGNATURE_SCHEMA)))::get; + } /** * Compute the TSS participant directory from the roster. * - * @param roster the roster + * @param roster the roster * @param maxSharesPerNode the maximum number of shares per node + * @param tssEncryptionKeyFn the function to get the TSS encryption keys * @return the TSS participant directory */ public static TssParticipantDirectory computeParticipantDirectory( - @NonNull final Roster roster, final long maxSharesPerNode) { + @NonNull final Roster roster, + final int maxSharesPerNode, + @NonNull final LongFunction tssEncryptionKeyFn) { final var computedShares = computeNodeShares(roster.rosterEntries(), maxSharesPerNode); final var totalShares = computedShares.values().stream().mapToLong(Long::longValue).sum(); final var threshold = getThresholdForTssMessages(totalShares); final var builder = TssParticipantDirectory.createBuilder().withThreshold(threshold); - for (var rosterEntry : roster.rosterEntries()) { + for (final var rosterEntry : roster.rosterEntries()) { final int numSharesPerThisNode = computedShares.get(rosterEntry.nodeId()).intValue(); - // FUTURE: Use the actual public key from the node - final var pairingPublicKey = - new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(10L)), SIGNATURE_SCHEMA); - builder.withParticipant(rosterEntry.nodeId(), numSharesPerThisNode, pairingPublicKey); + final long nodeId = rosterEntry.nodeId(); + final var encryptionKey = + requireNonNull(tssEncryptionKeyFn.apply(nodeId), "No encryption key for node" + nodeId); + builder.withParticipant(nodeId, numSharesPerThisNode, encryptionKey); } - // FUTURE: Use the actual signature schema return builder.build(); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchema.java new file mode 100644 index 000000000000..5e6e358df67a --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchema.java @@ -0,0 +1,122 @@ +/* + * 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.schemas; + +import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; +import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; +import com.hedera.hapi.node.state.tss.TssMessageMapKey; +import com.hedera.hapi.node.state.tss.TssVoteMapKey; +import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; +import com.hedera.node.app.tss.TssBaseService; +import com.hedera.node.config.data.TssConfig; +import com.hedera.node.internal.network.Network; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.spi.WritableKVState; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.BitSet; +import java.util.concurrent.atomic.AtomicLong; + +/** + * The {@link Schema#restart(MigrationContext)} implementation whereby the {@link TssBaseService} ensures that any + * TSS keys in the startup assets are copied into the state. These assets may range from no keys at all, to just + * encryption keys; to a full set of TSS messages with shares for a transplant roster encrypted using the encryption + * keys in the override address book. + *

    + * Important: The latest {@link TssBaseService} schema should always implement this interface. + */ +public interface TssBaseTransplantSchema { + default void restart(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + if (!ctx.appConfig().getConfigData(TssConfig.class).keyCandidateRoster()) { + return; + } + ctx.startupNetworks().overrideNetworkFor(ctx.roundNumber()).ifPresent(network -> { + setEncryptionKeys(network, ctx.newStates().get(TSS_ENCRYPTION_KEYS_KEY)); + setTssMessageOpsAndVotes( + network, + ctx.newStates().get(TSS_MESSAGE_MAP_KEY), + ctx.newStates().get(TSS_VOTE_MAP_KEY)); + }); + } + + /** + * Set the encryption keys in the state from the provided network, for whatever nodes they are available. + * @param network the network from which to extract the encryption keys + * @param encryptionKeys the state in which to store the encryption keys + */ + default void setEncryptionKeys( + @NonNull final Network network, + @NonNull final WritableKVState encryptionKeys) { + network.nodeMetadata().forEach(metadata -> { + if (metadata.tssEncryptionKey().length() > 0) { + final var key = new EntityNumber(metadata.rosterEntryOrThrow().nodeId()); + final var value = new TssEncryptionKeys(metadata.tssEncryptionKey(), Bytes.EMPTY); + encryptionKeys.put(key, value); + } + }); + } + + /** + * Set TSS state from the provided network. If {@link Network#tssMessages()} is empty, this is a no-op. If + * {@link Network#tssMessages()} is non-empty, then assumes there are exactly a threshold number of TSS messages + * that were received in ea + * + * @param network the network from which to extract the TSS messages + * @param tssMessageOps the state in which to store the TSS messages + */ + default void setTssMessageOpsAndVotes( + @NonNull final Network network, + @NonNull final WritableKVState tssMessageOps, + @NonNull final WritableKVState tssVotes) { + final var ops = network.tssMessages(); + if (ops.isEmpty()) { + return; + } + // We treat these messages as having come in this exact order + final AtomicLong seqNo = new AtomicLong(0); + final var roster = RosterUtils.rosterFrom(network); + final var rosterHash = RosterUtils.hash(roster).getBytes(); + network.tssMessages() + .forEach(op -> tssMessageOps.put(new TssMessageMapKey(rosterHash, seqNo.getAndIncrement()), op)); + final var tssVote = new BitSet(); + for (int i = 0, n = ops.size(); i < n; i++) { + tssVote.set(i); + } + network.nodeMetadata() + .forEach(metadata -> tssVotes.put( + new TssVoteMapKey( + rosterHash, metadata.rosterEntryOrThrow().nodeId()), + TssVoteTransactionBody.newBuilder() + .ledgerId(network.ledgerId()) + // (FUTURE) Are there any environments that would want a real signature here? + .nodeSignature(Bytes.EMPTY) + // (FUTURE) Are there any environments that would want a real source roster hash here? + .sourceRosterHash(Bytes.EMPTY) + .targetRosterHash(rosterHash) + .tssVote(Bytes.wrap(tssVote.toByteArray())) + .build())); + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0570TssBaseSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0570TssBaseSchema.java deleted file mode 100644 index 0b9bf4ce5423..000000000000 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0570TssBaseSchema.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.schemas; - -import static com.hedera.hapi.node.state.tss.RosterToKey.ACTIVE_ROSTER; -import static com.hedera.hapi.node.state.tss.TssKeyingStatus.WAITING_FOR_ENCRYPTION_KEYS; - -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.common.EntityNumber; -import com.hedera.hapi.node.state.tss.TssStatus; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.state.lifecycle.MigrationContext; -import com.swirlds.state.lifecycle.Schema; -import com.swirlds.state.lifecycle.StateDefinition; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.Set; - -/** - * Schema for the TSS service. - */ -public class V0570TssBaseSchema extends Schema { - public static final String TSS_STATUS_KEY = "TSS_STATUS"; - public static final String TSS_ENCRYPTION_KEY_MAP_KEY = "TSS_ENCRYPTION_KEY"; - /** - * This will at most be equal to the number of nodes in the network. - */ - private static final long MAX_TSS_ENCRYPTION_KEYS = 65_536L; - - /** - * The version of the schema. - */ - private static final SemanticVersion VERSION = - SemanticVersion.newBuilder().major(0).minor(57).patch(0).build(); - - /** - * Create a new instance - */ - public V0570TssBaseSchema() { - super(VERSION); - } - - @Override - public void migrate(@NonNull final MigrationContext ctx) { - final var tssStatusState = ctx.newStates().getSingleton(TSS_STATUS_KEY); - if (tssStatusState.get() == null) { - tssStatusState.put(new TssStatus(WAITING_FOR_ENCRYPTION_KEYS, ACTIVE_ROSTER, Bytes.EMPTY)); - } - } - - @NonNull - @Override - public Set statesToCreate() { - return Set.of( - StateDefinition.singleton(TSS_STATUS_KEY, TssStatus.PROTOBUF), - StateDefinition.onDisk( - TSS_ENCRYPTION_KEY_MAP_KEY, - EntityNumber.PROTOBUF, - TssEncryptionKeyTransactionBody.PROTOBUF, - MAX_TSS_ENCRYPTION_KEYS)); - } -} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0580TssBaseSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0580TssBaseSchema.java new file mode 100644 index 000000000000..322d5883ada4 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/schemas/V0580TssBaseSchema.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.tss.schemas; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.lifecycle.StateDefinition; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Set; + +/** + * Schema for the TSS service. + */ +public class V0580TssBaseSchema extends Schema implements TssBaseTransplantSchema { + public static final String TSS_ENCRYPTION_KEYS_KEY = "TSS_ENCRYPTION_KEYS"; + /** + * This will at most be equal to the number of nodes in the network. + */ + private static final long MAX_TSS_ENCRYPTION_KEYS = 65_536L; + + /** + * The version of the schema. + */ + private static final SemanticVersion VERSION = + SemanticVersion.newBuilder().major(0).minor(58).patch(0).build(); + + public V0580TssBaseSchema() { + super(VERSION); + } + + @Override + public @NonNull Set statesToCreate() { + return Set.of(StateDefinition.onDisk( + TSS_ENCRYPTION_KEYS_KEY, EntityNumber.PROTOBUF, TssEncryptionKeys.PROTOBUF, MAX_TSS_ENCRYPTION_KEYS)); + } + + @Override + public void restart(@NonNull final MigrationContext ctx) { + TssBaseTransplantSchema.super.restart(ctx); + } +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java index e65b3b5ecde2..e00ace87359c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java @@ -20,10 +20,9 @@ import static java.util.stream.Collectors.toMap; import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.pbj.runtime.io.buffer.Bytes; @@ -166,16 +165,10 @@ Optional anyWinningVoteFrom( /** * Get the Tss encryption key transaction body for the given node ID. + * * @param nodeID The node ID to look up. * @return The Tss encryption key transaction body, or null if not found. */ @Nullable - TssEncryptionKeyTransactionBody getTssEncryptionKey(final long nodeID); - - /** - * Get the Tss status. - * @return The Tss status. - */ - @NonNull - TssStatus getTssStatus(); + TssEncryptionKeys getTssEncryptionKeys(long nodeID); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java index 8dec56ceb64d..4f59c15a323b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java @@ -19,8 +19,7 @@ import static com.hedera.node.app.tss.handlers.TssUtils.hasMetThreshold; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_ENCRYPTION_KEY_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_STATUS_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static java.util.Objects.requireNonNull; import static java.util.Spliterator.NONNULL; import static java.util.Spliterators.spliterator; @@ -29,15 +28,13 @@ import static java.util.stream.StreamSupport.stream; import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableSingletonState; import com.swirlds.state.spi.ReadableStates; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.ArrayList; @@ -56,8 +53,7 @@ public class ReadableTssStoreImpl implements ReadableTssStore { private final ReadableKVState readableTssVoteState; - private final ReadableKVState readableTssEncryptionKeyState; - private final ReadableSingletonState readableTssStatusState; + private final ReadableKVState readableTssEncryptionKeyState; /** * Create a new {@link ReadableTssStoreImpl} instance. @@ -68,8 +64,7 @@ public ReadableTssStoreImpl(@NonNull final ReadableStates states) { requireNonNull(states); this.readableTssMessageState = states.get(TSS_MESSAGE_MAP_KEY); this.readableTssVoteState = states.get(TSS_VOTE_MAP_KEY); - this.readableTssEncryptionKeyState = states.get(TSS_ENCRYPTION_KEY_MAP_KEY); - this.readableTssStatusState = states.getSingleton(TSS_STATUS_KEY); + this.readableTssEncryptionKeyState = states.get(TSS_ENCRYPTION_KEYS_KEY); } @Override @@ -153,14 +148,8 @@ public List getMessagesForTarget(@NonNull final Bytes * {@inheritDoc} */ @Override - public TssEncryptionKeyTransactionBody getTssEncryptionKey(long nodeID) { + public TssEncryptionKeys getTssEncryptionKeys(final long nodeID) { return readableTssEncryptionKeyState.get( EntityNumber.newBuilder().number(nodeID).build()); } - - @Override - @NonNull - public TssStatus getTssStatus() { - return readableTssStatusState.get(); - } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/WritableTssStore.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/WritableTssStore.java index 4a74374b262c..5008bc30516d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/WritableTssStore.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/WritableTssStore.java @@ -18,19 +18,16 @@ import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_ENCRYPTION_KEY_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_STATUS_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; @@ -47,14 +44,11 @@ public class WritableTssStore extends ReadableTssStoreImpl { private final WritableKVState tssEncryptionKeyState; - private final WritableSingletonState tssStatusState; - public WritableTssStore(@NonNull final WritableStates states) { super(states); this.tssMessageState = states.get(TSS_MESSAGE_MAP_KEY); this.tssVoteState = states.get(TSS_VOTE_MAP_KEY); - this.tssEncryptionKeyState = states.get(TSS_ENCRYPTION_KEY_MAP_KEY); - this.tssStatusState = states.getSingleton(TSS_STATUS_KEY); + this.tssEncryptionKeyState = states.get(TSS_ENCRYPTION_KEYS_KEY); } public void put(@NonNull final TssMessageMapKey tssMessageMapKey, @NonNull final TssMessageTransactionBody txBody) { @@ -75,11 +69,6 @@ public void put(@NonNull final EntityNumber entityNumber, @NonNull final TssEncr tssEncryptionKeyState.put(entityNumber, txBody); } - public void put(@NonNull final TssStatus tssStatus) { - requireNonNull(tssStatus); - tssStatusState.put(tssStatus); - } - public void remove(@NonNull final TssMessageMapKey tssMessageMapKey) { requireNonNull(tssMessageMapKey); tssMessageState.remove(tssMessageMapKey); @@ -99,6 +88,5 @@ public void clear() { tssVoteState.keys().forEachRemaining(tssVoteState::remove); tssMessageState.keys().forEachRemaining(tssMessageState::remove); tssEncryptionKeyState.keys().forEachRemaining(tssEncryptionKeyState::remove); - tssStatusState.put(TssStatus.DEFAULT); } } 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 38a71a29ca3c..69f608052b57 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 @@ -225,7 +225,7 @@ public void handleRound(@NonNull final State state, @NonNull final Round round) logStartRound(round); cacheWarmer.warm(state, round); if (configProvider.getConfiguration().getConfigData(TssConfig.class).keyCandidateRoster()) { - tssBaseService.generateParticipantDirectory(state); + tssBaseService.ensureParticipantDirectoryKnown(state); } if (streamMode != RECORDS) { blockStreamManager.startRound(round, state); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneNetworkInfo.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneNetworkInfo.java index 8885d13e591f..02b547bba8b6 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneNetworkInfo.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/standalone/impl/StandaloneNetworkInfo.java @@ -23,7 +23,6 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.base.NodeAddressBook; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.info.NodeInfoImpl; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.FilesConfig; @@ -133,11 +132,6 @@ public void updateFrom(final State state) { throw new UnsupportedOperationException("Not implemented"); } - @Override - public Roster roster() { - throw new UnsupportedOperationException("Not implemented"); - } - private @NonNull List nodeInfosOrThrow() { return requireNonNull(nodeInfos, "Not initialized"); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/StartupAssetsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/StartupAssetsTest.java deleted file mode 100644 index 95835bc1de51..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/StartupAssetsTest.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * 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; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import com.hedera.node.internal.network.Network; -import java.nio.file.Path; -import java.util.Optional; -import org.junit.jupiter.api.Test; - -public class StartupAssetsTest { - - private static class MockFactory implements StartupAssets.Factory { - @Override - public StartupAssets fromInitialConditions(Path workingDir) { - return mock(StartupAssets.class); - } - } - - @Test - void testFactoryFromInitialConditions() { - Path workingDir = mock(Path.class); - MockFactory factory = new MockFactory(); - StartupAssets result = factory.fromInitialConditions(workingDir); - assertThat(result).isNotNull(); - } - - @Test - void testMigrationNetworkOrThrowReturnsNetwork() { - StartupAssets startupAssets = mock(StartupAssets.class); - Network mockNetwork = mock(Network.class); - when(startupAssets.migrationNetworkOrThrow()).thenReturn(mockNetwork); - Network result = startupAssets.migrationNetworkOrThrow(); - - assertThat(result).isNotNull(); - assertThat(mockNetwork).isEqualTo(result); - } - - @Test - void testMigrationNetworkOrThrowThrowsException() { - StartupAssets startupAssets = mock(StartupAssets.class); - when(startupAssets.migrationNetworkOrThrow()) - .thenThrow(new UnsupportedOperationException("Migration not supported")); - assertThatThrownBy(startupAssets::migrationNetworkOrThrow) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("Migration not supported"); - } - - @Test - void testGenesisNetworkOrThrowReturnsNetwork() { - StartupAssets startupAssets = mock(StartupAssets.class); - Network mockNetwork = mock(Network.class); - - when(startupAssets.genesisNetworkOrThrow()).thenReturn(mockNetwork); - Network result = startupAssets.genesisNetworkOrThrow(); - assertThat(result).isNotNull().isEqualTo(mockNetwork); - } - - @Test - void testGenesisNetworkOrThrowThrowsException() { - StartupAssets startupAssets = mock(StartupAssets.class); - when(startupAssets.genesisNetworkOrThrow()) - .thenThrow(new UnsupportedOperationException("Genesis network not supported")); - assertThatThrownBy(startupAssets::genesisNetworkOrThrow) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("Genesis network not supported"); - } - - @Test - void testOverrideNetworkReturnsNetwork() { - StartupAssets startupAssets = mock(StartupAssets.class); - Network mockNetwork = mock(Network.class); - long roundNumber = 123L; - when(startupAssets.overrideNetwork(roundNumber)).thenReturn(Optional.of(mockNetwork)); - Optional result = startupAssets.overrideNetwork(roundNumber); - assertThat(result).isPresent().contains(mockNetwork); - } - - @Test - void testOverrideNetworkReturnsEmptyOptional() { - StartupAssets startupAssets = mock(StartupAssets.class); - long roundNumber = 123L; - when(startupAssets.overrideNetwork(roundNumber)).thenReturn(Optional.empty()); - Optional result = startupAssets.overrideNetwork(roundNumber); - assertThat(result).isNotPresent(); - } - - @Test - void testArchiveInitialConditionsExecutesSuccessfully() { - StartupAssets startupAssets = mock(StartupAssets.class); - doNothing().when(startupAssets).archiveInitialConditions(); - assertThatCode(() -> startupAssets.archiveInitialConditions()).doesNotThrowAnyException(); - verify(startupAssets, times(1)).archiveInitialConditions(); - } -} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchemaTest.java index ebbbc5d65be4..d9cc1b910ce2 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchemaTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/blocks/schemas/V0560BlockStreamSchemaTest.java @@ -82,6 +82,7 @@ void createsDefaultInfoAtGenesis() { given(migrationContext.newStates()).willReturn(writableStates); given(writableStates.getSingleton(BLOCK_STREAM_INFO_KEY)) .willReturn(state); + given(migrationContext.isGenesis()).willReturn(true); subject.restart(migrationContext); @@ -135,7 +136,6 @@ void assumesMigrationIfNotGenesisAndStateIsNull() { @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); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java index af99b0b8bd14..1a5efe9c8157 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java @@ -20,15 +20,17 @@ import static com.hedera.node.app.info.DiskStartupNetworks.ARCHIVE; import static com.hedera.node.app.info.DiskStartupNetworks.GENESIS_NETWORK_JSON; import static com.hedera.node.app.info.DiskStartupNetworks.OVERRIDE_NETWORK_JSON; +import static com.hedera.node.app.info.DiskStartupNetworks.fromLegacyAddressBook; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_KEY; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; import static com.hedera.node.app.spi.AppContext.Gossip.UNAVAILABLE_GOSSIP; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_ENCRYPTION_KEY_MAP_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.FAKE_NETWORK_INFO; import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.NO_OP_METRICS; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_STATES_KEY; +import static com.swirlds.platform.state.service.PlatformStateService.PLATFORM_STATE_SERVICE; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -43,10 +45,9 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterState; import com.hedera.hapi.node.state.roster.RoundRosterPair; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.config.BootstrapConfigProviderImpl; @@ -62,7 +63,6 @@ import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.tss.TssBaseServiceImpl; import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.node.app.tss.schemas.V0570TssBaseSchema; import com.hedera.node.app.version.ServicesSoftwareVersion; import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.VersionedConfigImpl; @@ -74,8 +74,13 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.pbj.runtime.io.stream.ReadableStreamingData; import com.hedera.pbj.runtime.io.stream.WritableStreamingData; +import com.swirlds.common.platform.NodeId; import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.platform.state.service.PlatformStateService; +import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.ReadableRosterStoreImpl; +import com.swirlds.platform.system.address.Address; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.State; import com.swirlds.state.lifecycle.StartupNetworks; import com.swirlds.state.spi.CommittableWritableStates; @@ -91,6 +96,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.ForkJoinPool; +import java.util.stream.IntStream; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -103,11 +109,11 @@ @ExtendWith(MockitoExtension.class) class DiskStartupNetworksTest { private static final int FAKE_NETWORK_SIZE = 4; - private static final long NODE_ID = 0L; private static final long ROUND_NO = 666L; private static final Bytes EXPECTED_LEDGER_ID = Bytes.fromBase64("Lw=="); private static final Comparator TSS_MESSAGE_COMPARATOR = Comparator.comparingLong(TssMessageTransactionBody::shareIndex); + private static final Bytes FAKE_ENCRYPTION_KEY = Bytes.fromBase64("ASM="); private static Network networkWithTssKeys; private static Network networkWithoutTssKeys; @@ -152,7 +158,7 @@ static void setupAll() throws IOException, ParseException { @BeforeEach void setUp() { - subject = new DiskStartupNetworks(NODE_ID, configProvider, tssBaseService); + subject = new DiskStartupNetworks(configProvider, tssBaseService); } @Test @@ -184,6 +190,33 @@ void findsAvailableMigrationNetwork() throws IOException { assertThat(network).isEqualTo(networkWithTssKeys); } + @Test + void computesFromLegacyAddressBook() { + final int n = 3; + final var legacyBook = new AddressBook(IntStream.range(0, n) + .mapToObj(i -> new Address( + NodeId.of(i), + "" + i, + "node" + (i + 1), + 1L, + "localhost", + i + 1, + "127.0.0.1", + i + 2, + null, + null, + "0.0." + (i + 3))) + .toList()); + final var network = fromLegacyAddressBook(legacyBook); + for (int i = 0; i < n; i++) { + final var rosterEntry = network.nodeMetadata().get(i).rosterEntryOrThrow(); + assertThat(rosterEntry.nodeId()).isEqualTo(i); + assertThat(rosterEntry.gossipEndpoint().getFirst().ipAddressV4()) + .isEqualTo(Bytes.wrap(new byte[] {127, 0, 0, 1})); + assertThat(rosterEntry.gossipEndpoint().getLast().domainName()).isEqualTo("localhost"); + } + } + @Test void archivesGenesisNetworks() throws IOException { givenConfig(); @@ -304,7 +337,17 @@ private State stateContainingInfoFrom(@NonNull final Network network) { tssLibrary, ForkJoinPool.commonPool(), NO_OP_METRICS); - Set.of(tssBaseService, new EntityIdService(), new RosterService(roster -> true), new AddressBookServiceImpl()) + PLATFORM_STATE_SERVICE.setAppVersionFn(ServicesSoftwareVersion::from); + PLATFORM_STATE_SERVICE.setDiskAddressBook(new AddressBook()); + Set.of( + tssBaseService, + PLATFORM_STATE_SERVICE, + new EntityIdService(), + new RosterService( + roster -> true, + () -> new ReadablePlatformStateStore( + state.getReadableStates(PlatformStateService.NAME))), + new AddressBookServiceImpl()) .forEach(servicesRegistry::register); final var migrator = new FakeServiceMigrator(); final var bootstrapConfig = new BootstrapConfigProviderImpl().getConfiguration(); @@ -374,18 +417,13 @@ private void addTssInfo(@NonNull final FakeState state, @NonNull final Network n tssVotes.put(key, vote); } - final var tssEncryptionKey = - writableStates.get(TSS_ENCRYPTION_KEY_MAP_KEY); + final var tssEncryptionKey = writableStates.get(TSS_ENCRYPTION_KEYS_KEY); for (int i = 0; i < FAKE_NETWORK_SIZE; i++) { final var key = new EntityNumber(i); - final var value = TssEncryptionKeyTransactionBody.newBuilder() - .publicTssEncryptionKey(Bytes.EMPTY) - .build(); + final var value = new TssEncryptionKeys(FAKE_ENCRYPTION_KEY, Bytes.EMPTY); tssEncryptionKey.put(key, value); } - final var tssStatus = writableStates.getSingleton(V0570TssBaseSchema.TSS_STATUS_KEY); - tssStatus.put(TssStatus.DEFAULT); ((CommittableWritableStates) writableStates).commit(); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/StateNetworkInfoTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/StateNetworkInfoTest.java index 799a67679274..da13924b53b8 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/StateNetworkInfoTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/StateNetworkInfoTest.java @@ -16,7 +16,6 @@ package com.hedera.node.app.info; -import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.getCertBytes; import static com.hedera.node.app.workflows.standalone.TransactionExecutorsTest.randomX509Certificate; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -32,9 +31,6 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; -import com.hedera.hapi.platform.state.Address; -import com.hedera.hapi.platform.state.AddressBook; -import com.hedera.hapi.platform.state.NodeId; import com.hedera.hapi.platform.state.PlatformState; import com.hedera.node.app.service.addressbook.AddressBookService; import com.hedera.node.config.ConfigProvider; @@ -90,7 +86,7 @@ public void setUp() { when(state.getReadableStates(AddressBookService.NAME)).thenReturn(readableStates); when(readableStates.get("NODES")).thenReturn(nodeState); when(state.getReadableStates(PlatformStateService.NAME)).thenReturn(readableStates); - networkInfo = new StateNetworkInfo(state, activeRoster, SELF_ID, configProvider); + networkInfo = new StateNetworkInfo(SELF_ID, state, activeRoster, configProvider); } @Test @@ -129,34 +125,6 @@ public void testContainsNode() { @Test public void testUpdateFrom() { when(nodeState.get(any(EntityNumber.class))).thenReturn(mock(Node.class)); - when(readableStates.getSingleton("PLATFORM_STATE")).thenReturn(platformReadableState); - when(platformReadableState.get()).thenReturn(platformState); - when(platformState.addressBook()) - .thenReturn(AddressBook.newBuilder() - .addresses( - Address.newBuilder() - .id(new NodeId(2L)) - .weight(111L) - .signingCertificate(getCertBytes(CERTIFICATE_2)) - // The agreementCertificate is unused, but required to prevent deserialization - // failure in - // States API. - .agreementCertificate(getCertBytes(CERTIFICATE_2)) - .hostnameInternal("10.0.55.66") - .portInternal(222) - .build(), - Address.newBuilder() - .id(new NodeId(3L)) - .weight(3L) - .signingCertificate(getCertBytes(CERTIFICATE_3)) - // The agreementCertificate is unused, but required to prevent deserialization - // failure in - // States API. - .agreementCertificate(getCertBytes(CERTIFICATE_3)) - .hostnameExternal("external3.com") - .portExternal(111) - .build()) - .build()); networkInfo.updateFrom(state); assertEquals(2, networkInfo.addressBook().size()); @@ -166,7 +134,7 @@ public void testUpdateFrom() { public void testBuildNodeInfoMapNodeNotFound() { when(nodeState.get(any(EntityNumber.class))).thenReturn(null); - StateNetworkInfo networkInfo = new StateNetworkInfo(state, activeRoster, SELF_ID, configProvider); + StateNetworkInfo networkInfo = new StateNetworkInfo(SELF_ID, state, activeRoster, configProvider); final var nodeInfo = networkInfo.nodeInfo(SELF_ID); assertNotNull(nodeInfo); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java index 76d72f10c300..68df0a18558b 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java @@ -18,15 +18,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.node.app.roster.schemas.V057RosterSchema; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; +import com.hedera.node.app.roster.schemas.RosterTransplantSchema; +import com.hedera.node.app.roster.schemas.V0540RosterSchema; +import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; import java.util.function.Predicate; +import java.util.function.Supplier; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -40,11 +41,14 @@ class RosterServiceTest { @Mock private Predicate canAdopt; + @Mock + private Supplier platformStateStoreFactory; + private RosterService rosterService; @BeforeEach void setUp() { - rosterService = new RosterService(canAdopt); + rosterService = new RosterService(canAdopt, platformStateStoreFactory); } @Test @@ -59,11 +63,11 @@ void registerExpectedSchemas() { rosterService.registerSchemas(schemaRegistry); final var captor = ArgumentCaptor.forClass(Schema.class); - verify(schemaRegistry, times(2)).register(captor.capture()); + verify(schemaRegistry).register(captor.capture()); final var schemas = captor.getAllValues(); - assertThat(schemas).hasSize(2); + assertThat(schemas).hasSize(1); assertThat(schemas.getFirst()).isInstanceOf(V0540RosterSchema.class); - assertThat(schemas.getLast()).isInstanceOf(V057RosterSchema.class); + assertThat(schemas.getFirst()).isInstanceOf(RosterTransplantSchema.class); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java index e676fc2fb5a2..12cfdb250b9e 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java @@ -16,47 +16,95 @@ package com.hedera.node.app.roster.schemas; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_STATES_KEY; +import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; +import static com.hedera.node.app.fixtures.AppTestBase.WITH_ROSTER_LIFECYCLE; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_KEY; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_STATES_KEY; +import static com.swirlds.platform.roster.RosterRetriever.buildRoster; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.verifyNoMoreInteractions; -import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.node.state.roster.RosterState; -import com.hedera.node.app.spi.fixtures.util.LoggingSubject; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; +import com.hedera.node.internal.network.Network; +import com.hedera.node.internal.network.NodeMetadata; +import com.swirlds.platform.state.service.ReadablePlatformStateStore; +import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.StartupNetworks; import com.swirlds.state.lifecycle.StateDefinition; import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; /** * Unit tests for {@link V0540RosterSchema}. */ +@ExtendWith(MockitoExtension.class) class V0540RosterSchemaTest { + private static final long ROUND_NO = 666L; + private static final Network NETWORK = Network.newBuilder() + .nodeMetadata(NodeMetadata.newBuilder() + .rosterEntry(RosterEntry.newBuilder().nodeId(1L).build()) + .build()) + .build(); + private static final Roster ROSTER = new Roster(NETWORK.nodeMetadata().stream() + .map(NodeMetadata::rosterEntryOrThrow) + .toList()); + private static final AddressBook ADDRESS_BOOK = new AddressBook(List.of()); - @LoggingSubject - private V0540RosterSchema subject; + @Mock + private MigrationContext ctx; + + @Mock + private WritableStates writableStates; + + @Mock + private WritableRosterStore rosterStore; + + @Mock + private StartupNetworks startupNetworks; + + @Mock + private Function rosterStoreFactory; + + @Mock + private Predicate canAdopt; + + @Mock + private Supplier platformStateStoreFactory; - private MigrationContext migrationContext; - private WritableSingletonState rosterState; + @Mock + private ReadablePlatformStateStore platformStateStore; + + @Mock + private WritableSingletonState rosterState; + + private V0540RosterSchema subject; @BeforeEach void setUp() { - subject = new V0540RosterSchema(); - migrationContext = mock(MigrationContext.class); - rosterState = mock(WritableSingletonState.class); + subject = new V0540RosterSchema(canAdopt, rosterStoreFactory, platformStateStoreFactory); } @Test - void registersExpectedRosterSchema() { + void registersExpectedSchema() { final var statesToCreate = subject.statesToCreate(); assertThat(statesToCreate).hasSize(2); final var iter = @@ -66,24 +114,135 @@ void registersExpectedRosterSchema() { } @Test - @DisplayName("For this version, migrate from existing state version returns default.") - void testMigrateFromNullRosterStateReturnsDefault() { - when(migrationContext.newStates()).thenReturn(mock(WritableStates.class)); - when(migrationContext.newStates().getSingleton(ROSTER_STATES_KEY)).thenReturn(rosterState); + void usesDefaultRosterStateIfLifecycleNotEnabled() { + given(ctx.appConfig()).willReturn(DEFAULT_CONFIG); + given(ctx.newStates()).willReturn(writableStates); + given(writableStates.getSingleton(ROSTER_STATES_KEY)).willReturn(rosterState); + + subject.migrate(ctx); - subject.migrate(migrationContext); verify(rosterState, times(1)).put(RosterState.DEFAULT); } @Test - @DisplayName("Migrate from older state version returns default.") - void testMigrateFromPreviousStateVersion() { - when(migrationContext.newStates()).thenReturn(mock(WritableStates.class)); - when(migrationContext.newStates().getSingleton(ROSTER_STATES_KEY)).thenReturn(rosterState); - when(migrationContext.previousVersion()) - .thenReturn( - SemanticVersion.newBuilder().major(0).minor(53).patch(0).build()); - subject.migrate(migrationContext); - verify(rosterState, times(1)).put(RosterState.DEFAULT); + void usesGenesisRosterIfLifecycleEnabledAndApropros() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.isGenesis()).willReturn(true); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(startupNetworks.genesisNetworkOrThrow()).willReturn(NETWORK); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + + subject.restart(ctx); + + verify(rosterStore).putActiveRoster(ROSTER, 0L); + } + + @Test + void usesAdaptedAddressBookAndMigrationRosterIfLifecycleEnabledIfApropos() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(ctx.roundNumber()).willReturn(ROUND_NO); + given(startupNetworks.migrationNetworkOrThrow()).willReturn(NETWORK); + given(platformStateStoreFactory.get()).willReturn(platformStateStore); + given(platformStateStore.getAddressBook()).willReturn(ADDRESS_BOOK); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + + subject.restart(ctx); + + verify(rosterStore).putActiveRoster(buildRoster(ADDRESS_BOOK), 0L); + verify(rosterStore).putActiveRoster(ROSTER, ROUND_NO + 1L); + } + + @Test + void noOpIfNotUpgradeAndActiveRosterPresent() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + given(rosterStore.getActiveRoster()).willReturn(ROSTER); + + subject.restart(ctx); + + verify(rosterStore).getActiveRoster(); + verifyNoMoreInteractions(rosterStore); + } + + @Test + void doesNotAdoptNullCandidateRoster() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + given(rosterStore.getActiveRoster()).willReturn(ROSTER); + given(ctx.isUpgrade(any(), any())).willReturn(true); + + subject.restart(ctx); + + verify(rosterStore).getActiveRoster(); + verify(rosterStore).getCandidateRoster(); + verifyNoMoreInteractions(rosterStore); + } + + @Test + void doesNotAdoptCandidateRosterIfNotSpecified() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + given(rosterStore.getActiveRoster()).willReturn(ROSTER); + given(ctx.isUpgrade(any(), any())).willReturn(true); + given(rosterStore.getCandidateRoster()).willReturn(ROSTER); + given(canAdopt.test(ROSTER)).willReturn(false); + + subject.restart(ctx); + + verify(rosterStore).getActiveRoster(); + verify(rosterStore).getCandidateRoster(); + verifyNoMoreInteractions(rosterStore); + } + + @Test + void adoptsCandidateRosterIfTestPasses() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.newStates()).willReturn(writableStates); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + given(rosterStore.getActiveRoster()).willReturn(ROSTER); + given(ctx.isUpgrade(any(), any())).willReturn(true); + given(rosterStore.getCandidateRoster()).willReturn(ROSTER); + given(canAdopt.test(ROSTER)).willReturn(true); + given(ctx.roundNumber()).willReturn(ROUND_NO); + + subject.restart(ctx); + + verify(rosterStore).getActiveRoster(); + verify(rosterStore).getCandidateRoster(); + verify(rosterStore).adoptCandidateRoster(ROUND_NO + 1L); + } + + @Test + void restartIsNoOpIfNotUsingLifecycle() { + given(ctx.appConfig()).willReturn(DEFAULT_CONFIG); + + subject.restart(ctx); + + verifyNoMoreInteractions(ctx); + } + + @Test + void restartSetsActiveRosterFromOverrideIfPresent() { + given(ctx.appConfig()).willReturn(WITH_ROSTER_LIFECYCLE); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(ctx.roundNumber()).willReturn(ROUND_NO); + given(ctx.newStates()).willReturn(writableStates); + given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); + given(startupNetworks.overrideNetworkFor(ROUND_NO)).willReturn(Optional.of(NETWORK)); + + subject.restart(ctx); + + verify(rosterStore).putActiveRoster(ROSTER, ROUND_NO + 1L); + verify(startupNetworks).setOverrideRound(ROUND_NO); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V057RosterSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V057RosterSchemaTest.java deleted file mode 100644 index 2ccaa2649a6a..000000000000 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V057RosterSchemaTest.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * 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.roster.schemas; - -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; - -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.hapi.node.state.roster.RosterEntry; -import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; -import com.hedera.node.internal.network.Network; -import com.hedera.node.internal.network.NodeMetadata; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; -import com.swirlds.platform.state.service.WritableRosterStore; -import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.state.lifecycle.MigrationContext; -import com.swirlds.state.lifecycle.StartupNetworks; -import com.swirlds.state.spi.WritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Predicate; -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 V057RosterSchemaTest { - private static final long ROUND_NO = 666L; - private static final SemanticVersion THEN = - SemanticVersion.newBuilder().minor(7).build(); - private static final Network NETWORK = Network.newBuilder() - .nodeMetadata(NodeMetadata.newBuilder() - .rosterEntry(RosterEntry.newBuilder().nodeId(1L).build()) - .build()) - .build(); - private static final Roster ROSTER = new Roster(NETWORK.nodeMetadata().stream() - .map(NodeMetadata::rosterEntryOrThrow) - .toList()); - - private static final AddressBook ADDRESS_BOOK = new AddressBook(List.of()); - - @Mock - private Predicate canAdopt; - - @Mock - private WritableStates writableStates; - - @Mock - private WritableRosterStore rosterStore; - - @Mock - private MigrationContext context; - - @Mock - private StartupNetworks startupNetworks; - - @Mock - private Function rosterStoreFactory; - - @Mock - private Function platformStateStoreFactory; - - @Mock - private ReadablePlatformStateStore platformStateStore; - - private V057RosterSchema subject; - - @BeforeEach - void setUp() { - subject = new V057RosterSchema(canAdopt, rosterStoreFactory, platformStateStoreFactory); - } - - @Test - void noOpIfNotUsingRosterLifecycle() { - givenContextWith(CurrentVersion.NA, RosterLifecycle.OFF, AvailableNetwork.NONE); - - subject.restart(context); - - verify(context, never()).newStates(); - } - - @Test - void setsActiveFromStartupNetworksAtGenesis() { - givenContextWith(CurrentVersion.NA, RosterLifecycle.ON, AvailableNetwork.GENESIS); - given(context.isGenesis()).willReturn(true); - - subject.restart(context); - - verify(rosterStore).putActiveRoster(ROSTER, 0L); - } - - @Test - void doesNotAdoptCandidateIfNotUpgradeBoundary() { - givenContextWith(CurrentVersion.OLD, RosterLifecycle.ON, AvailableNetwork.NONE); - given(context.previousVersion()).willReturn(THEN); - - subject.restart(context); - - verifyNoInteractions(canAdopt); - verify(rosterStore, never()).putActiveRoster(ROSTER, ROUND_NO); - } - - @Test - void doesNotAdoptCandidateIfTestFails() { - givenContextWith(CurrentVersion.NEW, RosterLifecycle.ON, AvailableNetwork.NONE); - given(context.previousVersion()).willReturn(THEN); - given(rosterStore.getActiveRoster()).willReturn(ROSTER); - given(rosterStore.getCandidateRoster()).willReturn(ROSTER); - given(canAdopt.test(ROSTER)).willReturn(false); - - subject.restart(context); - - verify(rosterStore, never()).adoptCandidateRoster(ROUND_NO); - } - - @Test - void forcesActiveFromMigrationAtUpgradeBoundaryIfNonePresent() { - givenContextWith(CurrentVersion.NEW, RosterLifecycle.ON, AvailableNetwork.MIGRATION); - given(context.previousVersion()).willReturn(THEN); - given(context.roundNumber()).willReturn(ROUND_NO); - given(platformStateStoreFactory.apply(writableStates)).willReturn(platformStateStore); - given(platformStateStore.getAddressBook()).willReturn(ADDRESS_BOOK); - - subject.restart(context); - - verify(rosterStore).putActiveRoster(ROSTER, ROUND_NO + 1); - } - - @Test - void adoptsCandidateAtUpgradeBoundaryIfTestPasses() { - givenContextWith(CurrentVersion.NEW, RosterLifecycle.ON, AvailableNetwork.NONE); - given(context.previousVersion()).willReturn(THEN); - given(rosterStore.getActiveRoster()).willReturn(ROSTER); - given(rosterStore.getCandidateRoster()).willReturn(ROSTER); - given(canAdopt.test(ROSTER)).willReturn(true); - given(context.roundNumber()).willReturn(ROUND_NO); - - subject.restart(context); - - verify(rosterStore).adoptCandidateRoster(ROUND_NO + 1); - } - - @Test - void usesOverrideNetworkIfPresent() { - given(context.roundNumber()).willReturn(ROUND_NO); - givenContextWith(CurrentVersion.OLD, RosterLifecycle.ON, AvailableNetwork.OVERRIDE); - given(startupNetworks.overrideNetworkFor(ROUND_NO)).willReturn(Optional.of(NETWORK)); - given(platformStateStoreFactory.apply(writableStates)).willReturn(platformStateStore); - given(platformStateStore.getAddressBook()).willReturn(ADDRESS_BOOK); - - subject.restart(context); - - verify(rosterStore).putActiveRoster(ROSTER, ROUND_NO + 1); - verify(startupNetworks).setOverrideRound(ROUND_NO); - } - - private enum CurrentVersion { - NA, - OLD, - NEW, - } - - private enum RosterLifecycle { - ON, - OFF - } - - private enum AvailableNetwork { - GENESIS, - OVERRIDE, - MIGRATION, - NONE - } - - private void givenContextWith( - @NonNull final CurrentVersion currentVersion, - @NonNull final RosterLifecycle rosterLifecycle, - @NonNull final AvailableNetwork availableNetwork) { - final var configBuilder = HederaTestConfigBuilder.create() - .withValue( - "addressBook.useRosterLifecycle", - switch (rosterLifecycle) { - case ON -> "true"; - case OFF -> "false"; - }); - switch (currentVersion) { - case NA -> { - // No-op - } - case OLD -> configBuilder.withValue("hedera.services.version", "0.7.0"); - case NEW -> configBuilder.withValue("hedera.services.version", "0.42.0"); - } - given(context.configuration()).willReturn(configBuilder.getOrCreateConfig()); - if (rosterLifecycle == RosterLifecycle.ON) { - given(context.newStates()).willReturn(writableStates); - given(context.startupNetworks()).willReturn(startupNetworks); - given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); - } - switch (availableNetwork) { - case GENESIS -> given(startupNetworks.genesisNetworkOrThrow()).willReturn(NETWORK); - case OVERRIDE -> { - given(context.roundNumber()).willReturn(ROUND_NO); - given(startupNetworks.overrideNetworkFor(ROUND_NO)).willReturn(Optional.of(NETWORK)); - } - case MIGRATION -> given(startupNetworks.migrationNetworkOrThrow()).willReturn(NETWORK); - case NONE -> { - // No-op - } - } - } -} 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 594e06f6891b..e0576d1b7c2e 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 @@ -42,7 +42,6 @@ import com.swirlds.merkledb.MerkleDb; import com.swirlds.metrics.api.Metrics; import com.swirlds.platform.config.StateConfig_; -import com.swirlds.platform.state.MerkleStateLifecycles; import com.swirlds.platform.state.PlatformMerkleStateRoot; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.InitTrigger; @@ -92,9 +91,6 @@ class SerializationTest extends MerkleTestBase { private Configuration config; private NetworkInfo networkInfo; - @Mock - private MerkleStateLifecycles lifecycles; - @Mock private MigrationStateChanges migrationStateChanges; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java index 234d5217168a..9d427f642505 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java @@ -17,15 +17,19 @@ package com.hedera.node.app.tss; import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; +import static org.mockito.internal.verification.VerificationModeFactory.times; import com.hedera.node.app.spi.AppContext; +import com.hedera.node.app.tss.schemas.TssBaseTransplantSchema; import com.hedera.node.app.tss.schemas.V0560TssBaseSchema; +import com.hedera.node.app.tss.schemas.V0580TssBaseSchema; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; import com.swirlds.metrics.api.Metrics; +import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; import java.time.Instant; import java.time.InstantSource; @@ -39,6 +43,7 @@ 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; @@ -109,7 +114,14 @@ void onlyRegisteredConsumerReceiveCallbacks() throws InterruptedException { @Test void placeholderRegistersSchemas() { + final var captor = ArgumentCaptor.forClass(Schema.class); + subject.registerSchemas(registry); - verify(registry).register(argThat(s -> s instanceof V0560TssBaseSchema)); + + verify(registry, times(2)).register(captor.capture()); + final var schemas = captor.getAllValues(); + assertThat(schemas.getFirst()).isInstanceOf(V0560TssBaseSchema.class); + assertThat(schemas.getLast()).isInstanceOf(V0580TssBaseSchema.class); + assertThat(schemas.getLast()).isInstanceOf(TssBaseTransplantSchema.class); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java index b9dcf944055e..b655b8f8638d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java @@ -137,7 +137,7 @@ void submitsVoteOnHandlingMessageWhenThresholdMet() { eq(handleContext))) .willReturn(CompletableFuture.completedFuture(vote)); given(signature.getBytes()).willReturn(Bytes.wrap("test")); - given(directoryAccessor.activeParticipantDirectory()).willReturn(TSS_KEYS.activeParticipantDirectory()); + given(directoryAccessor.activeParticipantDirectoryOrThrow()).willReturn(TSS_KEYS.activeParticipantDirectory()); given(pairingPublicKey.toBytes()).willReturn("test".getBytes()); subject.handle(handleContext); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java index 0af0f618afde..9f6ebd7b89fb 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java @@ -17,6 +17,7 @@ package com.hedera.node.app.tss.handlers; import static com.hedera.node.app.tss.handlers.TssMessageHandlerTest.getTssBody; +import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; import static com.hedera.node.app.tss.handlers.TssUtils.validateTssMessages; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -26,25 +27,30 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.tss.api.TssMessage; import com.hedera.cryptography.tss.api.TssParticipantDirectory; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.app.tss.api.FakeGroupElement; import com.hedera.node.app.tss.api.TssLibrary; import com.hedera.pbj.runtime.io.buffer.Bytes; +import java.math.BigInteger; import java.util.List; import org.junit.jupiter.api.Test; public class TssUtilsTest { + private static final BlsPublicKey FAKE_ENCRYPTION_KEY = + new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(123L)), SIGNATURE_SCHEMA); + @Test public void testComputeParticipantDirectory() { RosterEntry rosterEntry1 = new RosterEntry(1L, 100L, null, null); RosterEntry rosterEntry2 = new RosterEntry(2L, 50L, null, null); - long maxSharesPerNode = 10L; - int selfNodeId = 1; + int maxSharesPerNode = 10; - TssParticipantDirectory directory = - TssUtils.computeParticipantDirectory(new Roster(List.of(rosterEntry1, rosterEntry2)), maxSharesPerNode); + TssParticipantDirectory directory = TssUtils.computeParticipantDirectory( + new Roster(List.of(rosterEntry1, rosterEntry2)), maxSharesPerNode, nodeId -> FAKE_ENCRYPTION_KEY); assertNotNull(directory); assertEquals((15 + 2) / 2, directory.getThreshold()); @@ -84,14 +90,13 @@ public void testGetTssMessages() { final var tssMessage = mock(TssMessage.class); RosterEntry rosterEntry1 = new RosterEntry(1L, 100L, null, null); RosterEntry rosterEntry2 = new RosterEntry(2L, 50L, null, null); - long maxSharesPerNode = 10L; - int selfNodeId = 1; + int maxSharesPerNode = 10; given(library.getTssMessageFromBytes(any(), any())).willReturn(tssMessage); given(tssMessage.toBytes()) .willReturn(Bytes.wrap("tssMessage".getBytes()).toByteArray()); - TssParticipantDirectory directory = - TssUtils.computeParticipantDirectory(new Roster(List.of(rosterEntry1, rosterEntry2)), maxSharesPerNode); + TssParticipantDirectory directory = TssUtils.computeParticipantDirectory( + new Roster(List.of(rosterEntry1, rosterEntry2)), maxSharesPerNode, nodeId -> FAKE_ENCRYPTION_KEY); final var body = getTssBody(); final var validTssOps = List.of(body.tssMessageOrThrow()); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchemaTest.java new file mode 100644 index 000000000000..e0ed2cff734f --- /dev/null +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/schemas/TssBaseTransplantSchemaTest.java @@ -0,0 +1,175 @@ +/* + * 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.schemas; + +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import com.hedera.hapi.node.state.common.EntityNumber; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; +import com.hedera.hapi.node.state.tss.TssMessageMapKey; +import com.hedera.hapi.node.state.tss.TssVoteMapKey; +import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; +import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.node.internal.network.Network; +import com.hedera.node.internal.network.NodeMetadata; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.StartupNetworks; +import com.swirlds.state.spi.WritableKVState; +import com.swirlds.state.spi.WritableStates; +import java.util.List; +import java.util.Optional; +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 TssBaseTransplantSchemaTest { + private static final long ROUND_NO = 666L; + private static final Bytes FAKE_LEDGER_ID = Bytes.fromHex("abcd"); + private static final Bytes NODE1_ENCRYPTION_KEY = Bytes.wrap("NODE1_ENCRYPTION_KEY"); + private static final Bytes NODE2_ENCRYPTION_KEY = Bytes.wrap("NODE2_ENCRYPTION_KEY"); + private static final Network NETWORK_WITH_KEYS = Network.newBuilder() + .nodeMetadata( + NodeMetadata.newBuilder() + .rosterEntry(RosterEntry.newBuilder().nodeId(1L).build()) + .tssEncryptionKey(NODE1_ENCRYPTION_KEY) + .build(), + NodeMetadata.newBuilder() + .rosterEntry(RosterEntry.newBuilder().nodeId(2L).build()) + .tssEncryptionKey(NODE2_ENCRYPTION_KEY) + .build()) + .build(); + private static final Network NETWORK_WITHOUT_KEYS = NETWORK_WITH_KEYS + .copyBuilder() + .nodeMetadata(NETWORK_WITH_KEYS.nodeMetadata().stream() + .map(nm -> nm.copyBuilder().tssEncryptionKey(Bytes.EMPTY).build()) + .toList()) + .build(); + private static Network NETWORK_WITH_TSS_KEY_MATERIAL = NETWORK_WITH_KEYS + .copyBuilder() + .ledgerId(FAKE_LEDGER_ID) + .tssMessages(List.of( + new TssMessageTransactionBody(Bytes.EMPTY, Bytes.EMPTY, 1L, Bytes.EMPTY), + new TssMessageTransactionBody(Bytes.EMPTY, Bytes.EMPTY, 2L, Bytes.EMPTY))) + .build(); + + @Mock + private MigrationContext ctx; + + @Mock + private StartupNetworks startupNetworks; + + @Mock + private WritableStates writableStates; + + @Mock + private WritableKVState writableEncryptionKeys; + + @Mock + private WritableKVState writableVotes; + + @Mock + private WritableKVState writableMessages; + + private final TssBaseTransplantSchema subject = new V0580TssBaseSchema(); + + @Test + void noOpWithoutTssEnabled() { + givenConfig(false); + subject.restart(ctx); + verifyNoMoreInteractions(ctx); + } + + @Test + void notGenesisAndNoOverridePresentIsNoop() { + givenConfig(true); + given(ctx.roundNumber()).willReturn(ROUND_NO); + given(ctx.startupNetworks()).willReturn(startupNetworks); + + subject.restart(ctx); + + verify(startupNetworks).overrideNetworkFor(ROUND_NO); + } + + @Test + void withOverrideSetsEncryptionKeysFromNetwork() { + givenConfig(true); + given(ctx.roundNumber()).willReturn(ROUND_NO); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(startupNetworks.overrideNetworkFor(ROUND_NO)).willReturn(Optional.of(NETWORK_WITH_KEYS)); + given(ctx.newStates()).willReturn(writableStates); + given(writableStates.get(TSS_ENCRYPTION_KEYS_KEY)) + .willReturn(writableEncryptionKeys); + + subject.restart(ctx); + + verify(writableEncryptionKeys) + .put(new EntityNumber(1L), new TssEncryptionKeys(NODE1_ENCRYPTION_KEY, Bytes.EMPTY)); + verify(writableEncryptionKeys) + .put(new EntityNumber(2L), new TssEncryptionKeys(NODE2_ENCRYPTION_KEY, Bytes.EMPTY)); + } + + @Test + void ignoresEmptyEncryptionKeys() { + givenConfig(true); + given(ctx.roundNumber()).willReturn(ROUND_NO); + given(ctx.startupNetworks()).willReturn(startupNetworks); + given(startupNetworks.overrideNetworkFor(ROUND_NO)).willReturn(Optional.of(NETWORK_WITHOUT_KEYS)); + given(ctx.newStates()).willReturn(writableStates); + given(writableStates.get(TSS_ENCRYPTION_KEYS_KEY)) + .willReturn(writableEncryptionKeys); + + subject.restart(ctx); + + verify(writableEncryptionKeys, never()).put(any(), any()); + } + + @Test + void setsTssMaterialFromNetworkMessagesIfPresent() { + final var roster = RosterUtils.rosterFrom(NETWORK_WITH_TSS_KEY_MATERIAL); + final var rosterHash = RosterUtils.hash(roster).getBytes(); + + subject.setTssMessageOpsAndVotes(NETWORK_WITH_TSS_KEY_MATERIAL, writableMessages, writableVotes); + + verify(writableMessages) + .put( + new TssMessageMapKey(rosterHash, 0L), + NETWORK_WITH_TSS_KEY_MATERIAL.tssMessages().getFirst()); + verify(writableMessages) + .put( + new TssMessageMapKey(rosterHash, 1L), + NETWORK_WITH_TSS_KEY_MATERIAL.tssMessages().getLast()); + verify(writableVotes).put(eq(new TssVoteMapKey(rosterHash, 1L)), any()); + verify(writableVotes).put(eq(new TssVoteMapKey(rosterHash, 2L)), any()); + } + + private void givenConfig(final boolean tssEnabled) { + final var configBuilder = HederaTestConfigBuilder.create().withValue("tss.keyCandidateRoster", tssEnabled); + given(ctx.appConfig()).willReturn(configBuilder.getOrCreateConfig()); + } +} diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java index f6040c6f864b..8e19b1611424 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java @@ -18,11 +18,11 @@ import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_ENCRYPTION_KEY_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_STATUS_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static java.util.Collections.singletonList; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -35,16 +35,14 @@ import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; -import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.state.service.ReadableRosterStore; import com.swirlds.state.spi.ReadableKVState; -import com.swirlds.state.spi.ReadableSingletonState; import com.swirlds.state.spi.ReadableStates; import java.util.BitSet; import java.util.List; @@ -68,10 +66,7 @@ class ReadableTssStoreTest { private ReadableKVState readableTssVoteState; @Mock - private ReadableKVState readableTssEncryptionKeyState; - - @Mock - private ReadableSingletonState readableTssStatusState; + private ReadableKVState readableTssEncryptionKeyState; @Mock private ReadableStates states; @@ -109,9 +104,8 @@ void setUp() { .thenReturn(readableTssMessageState); when(states.get(TSS_VOTE_MAP_KEY)) .thenReturn(readableTssVoteState); - when(states.get(TSS_ENCRYPTION_KEY_MAP_KEY)) + when(states.get(TSS_ENCRYPTION_KEYS_KEY)) .thenReturn(readableTssEncryptionKeyState); - when(states.getSingleton(TSS_STATUS_KEY)).thenReturn(readableTssStatusState); tssStore = new ReadableTssStoreImpl(states); } @@ -235,20 +229,9 @@ void testGetMessagesForTarget() { void testGetTssEncryptionKey() { long nodeID = 123L; EntityNumber entityNumber = new EntityNumber(nodeID); - TssEncryptionKeyTransactionBody encryptionKey = TssEncryptionKeyTransactionBody.DEFAULT; - when(readableTssEncryptionKeyState.get(entityNumber)).thenReturn(encryptionKey); - - TssEncryptionKeyTransactionBody result = tssStore.getTssEncryptionKey(nodeID); - assertEquals(encryptionKey, result); - } - - @Test - void testGetTssStatus() { - TssStatus status = TssStatus.DEFAULT; - when(readableTssStatusState.get()).thenReturn(status); - - TssStatus result = tssStore.getTssStatus(); - assertEquals(status, result); + when(readableTssEncryptionKeyState.get(entityNumber)).thenReturn(TssEncryptionKeys.DEFAULT); + final var keys = tssStore.getTssEncryptionKeys(nodeID); + assertSame(TssEncryptionKeys.DEFAULT, keys); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java index 399f3bee327a..a71d546212a0 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/WritableTssStoreTest.java @@ -18,19 +18,16 @@ import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_ENCRYPTION_KEY_MAP_KEY; -import static com.hedera.node.app.tss.schemas.V0570TssBaseSchema.TSS_STATUS_KEY; +import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static org.mockito.Mockito.*; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.tss.TssMessageMapKey; -import com.hedera.hapi.node.state.tss.TssStatus; import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.services.auxiliary.tss.TssEncryptionKeyTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.swirlds.state.spi.WritableKVState; -import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; import java.util.Iterator; import org.junit.jupiter.api.BeforeEach; @@ -50,9 +47,6 @@ class WritableTssStoreTest { @Mock private WritableKVState tssEncryptionKeyState; - @Mock - private WritableSingletonState tssStatusState; - @Mock private WritableStates states; @@ -64,9 +58,8 @@ void setUp() { .thenReturn(tssMessageState); when(states.get(TSS_VOTE_MAP_KEY)) .thenReturn(tssVoteState); - when(states.get(TSS_ENCRYPTION_KEY_MAP_KEY)) + when(states.get(TSS_ENCRYPTION_KEYS_KEY)) .thenReturn(tssEncryptionKeyState); - when(states.getSingleton(TSS_STATUS_KEY)).thenReturn(tssStatusState); tssStore = new WritableTssStore(states); } @@ -95,13 +88,6 @@ void testPutEncryptionKey() { verify(tssEncryptionKeyState).put(entityNumber, body); } - @Test - void testPutTssStatus() { - TssStatus status = TssStatus.DEFAULT; - tssStore.put(status); - verify(tssStatusState).put(status); - } - @Test void testRemoveTssMessage() { TssMessageMapKey key = TssMessageMapKey.DEFAULT; @@ -134,6 +120,5 @@ void testClear() { verify(tssVoteState).keys(); verify(tssMessageState).keys(); verify(tssEncryptionKeyState).keys(); - verify(tssStatusState).put(TssStatus.DEFAULT); } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java index d428f98429a1..ff6e88822e3c 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/record/BlockRecordManagerTest.java @@ -28,6 +28,7 @@ import static com.hedera.node.app.records.schemas.V0490BlockRecordSchema.BLOCK_INFO_STATE_KEY; import static com.hedera.node.app.records.schemas.V0490BlockRecordSchema.RUNNING_HASHES_STATE_KEY; import static com.swirlds.platform.state.service.PlatformStateService.PLATFORM_STATE_SERVICE; +import static com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema.UNINITIALIZED_PLATFORM_STATE; import static com.swirlds.state.lifecycle.HapiUtils.asAccountString; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -38,7 +39,6 @@ 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.node.app.blocks.impl.BlockStreamManagerImpl; import com.hedera.node.app.fixtures.AppTestBase; import com.hedera.node.app.info.NodeInfoImpl; import com.hedera.node.app.records.BlockRecordService; @@ -51,6 +51,7 @@ import com.hedera.node.app.records.impl.producers.formats.BlockRecordWriterFactoryImpl; import com.hedera.node.app.records.impl.producers.formats.v6.BlockRecordFormatV6; import com.hedera.node.app.records.schemas.V0490BlockRecordSchema; +import com.hedera.node.app.version.ServicesSoftwareVersion; import com.hedera.node.config.data.BlockRecordStreamConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.platform.state.service.PlatformStateService; @@ -77,7 +78,6 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -104,11 +104,9 @@ final class BlockRecordManagerTest extends AppTestBase { private BlockRecordFormat blockRecordFormat; private BlockRecordWriterFactory blockRecordWriterFactory; - @Mock - private BlockStreamManagerImpl blockStreamManager; - @BeforeEach void setUpEach() throws Exception { + PLATFORM_STATE_SERVICE.setAppVersionFn(ServicesSoftwareVersion::from); // create in memory temp dir fs = Jimfs.newFileSystem(Configuration.unix()); final var tempDir = fs.getPath("/temp"); @@ -140,8 +138,7 @@ RUNNING_HASHES_STATE_KEY, new RunningHashes(STARTING_RUNNING_HASH_OBJ.hash(), nu new BlockInfo(-1, EPOCH, STARTING_RUNNING_HASH_OBJ.hash(), null, false, EPOCH)) .commit(); app.stateMutator(PlatformStateService.NAME) - .withSingletonState( - V0540PlatformStateSchema.PLATFORM_STATE_KEY, V0540PlatformStateSchema.GENESIS_PLATFORM_STATE) + .withSingletonState(V0540PlatformStateSchema.PLATFORM_STATE_KEY, UNINITIALIZED_PLATFORM_STATE) .commit(); blockRecordWriterFactory = new BlockRecordWriterFactoryImpl(app.configProvider(), NODE_INFO, SIGNER, fs); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java index 7b603277b197..96a7f076720d 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java @@ -44,6 +44,7 @@ import com.hedera.hapi.node.transaction.ExchangeRateSet; import com.hedera.node.app.fees.ExchangeRateManager; import com.hedera.node.app.records.ReadableBlockRecordStore; +import com.hedera.node.app.roster.schemas.V0540RosterSchema; import com.hedera.node.app.service.addressbook.AddressBookService; import com.hedera.node.app.service.addressbook.ReadableNodeStore; import com.hedera.node.app.service.addressbook.impl.ReadableNodeStoreImpl; @@ -61,7 +62,6 @@ import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.config.api.Configuration; import com.swirlds.platform.roster.RosterUtils; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; import com.swirlds.state.spi.WritableKVState; import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java index 1adc374da2f7..fd5a1df85693 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java @@ -18,10 +18,10 @@ import static com.hedera.hapi.node.freeze.FreezeType.FREEZE_UPGRADE; import static com.hedera.node.app.fixtures.AppTestBase.DEFAULT_CONFIG; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_KEY; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; import static com.hedera.node.app.service.networkadmin.impl.schemas.V0490FreezeSchema.FREEZE_TIME_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.Mock.Strictness.LENIENT; @@ -82,7 +82,7 @@ class PlatformStateUpdatesTest implements TransactionFactory { @BeforeEach void setUp() { freezeTimeBackingStore = new AtomicReference<>(null); - platformStateBackingStore = new AtomicReference<>(V0540PlatformStateSchema.GENESIS_PLATFORM_STATE); + platformStateBackingStore = new AtomicReference<>(V0540PlatformStateSchema.UNINITIALIZED_PLATFORM_STATE); when(writableStates.getSingleton(FREEZE_TIME_KEY)) .then(invocation -> new WritableSingletonStateBase<>( FREEZE_TIME_KEY, freezeTimeBackingStore::get, freezeTimeBackingStore::set)); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java index fa945f5cd98c..c07adf82a303 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/standalone/TransactionExecutorsTest.java @@ -22,7 +22,6 @@ import static com.hedera.node.app.util.FileUtilities.createFileID; import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; import static com.hedera.node.app.workflows.standalone.TransactionExecutors.TRANSACTION_EXECUTORS; -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import com.hedera.hapi.node.base.AccountID; @@ -31,13 +30,13 @@ import com.hedera.hapi.node.base.FileID; import com.hedera.hapi.node.base.HederaFunctionality; import com.hedera.hapi.node.base.KeyList; +import com.hedera.hapi.node.base.ServiceEndpoint; import com.hedera.hapi.node.base.Timestamp; import com.hedera.hapi.node.base.TransactionID; import com.hedera.hapi.node.contract.ContractCallTransactionBody; import com.hedera.hapi.node.contract.ContractCreateTransactionBody; import com.hedera.hapi.node.file.FileCreateTransactionBody; import com.hedera.hapi.node.state.file.File; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.transaction.ThrottleDefinitions; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.config.BootstrapConfigProviderImpl; @@ -321,6 +320,7 @@ private void registerServices( } private static NetworkInfo fakeNetworkInfo() { + final AccountID someAccount = AccountID.newBuilder().accountNum(12345).build(); final var addressBook = new AddressBook(StreamSupport.stream( Spliterators.spliteratorUnknownSize( RandomAddressBookBuilder.create(new Random()) @@ -343,19 +343,29 @@ public Bytes ledgerId() { @NonNull @Override public NodeInfo selfNodeInfo() { - return new NodeInfoImpl(0, AccountID.DEFAULT, 0, List.of(), getCertBytes(randomX509Certificate())); + return new NodeInfoImpl( + 0, + someAccount, + 0, + List.of(ServiceEndpoint.DEFAULT, ServiceEndpoint.DEFAULT), + getCertBytes(randomX509Certificate())); } @NonNull @Override public List addressBook() { - return List.of( - new NodeInfoImpl(0, AccountID.DEFAULT, 0, List.of(), getCertBytes(randomX509Certificate()))); + return List.of(new NodeInfoImpl( + 0, + someAccount, + 0, + List.of(ServiceEndpoint.DEFAULT, ServiceEndpoint.DEFAULT), + getCertBytes(randomX509Certificate()))); } @Override public NodeInfo nodeInfo(final long nodeId) { - return new NodeInfoImpl(0, AccountID.DEFAULT, 0, List.of(), Bytes.EMPTY); + return new NodeInfoImpl( + 0, someAccount, 0, List.of(ServiceEndpoint.DEFAULT, ServiceEndpoint.DEFAULT), Bytes.EMPTY); } @Override @@ -367,11 +377,6 @@ public boolean containsNode(final long nodeId) { public void updateFrom(final State state) { throw new UnsupportedOperationException("Not implemented"); } - - @Override - public Roster roster() { - return buildRoster(addressBook); - } }; } diff --git a/hedera-node/hedera-app/src/test/resources/bootstrap/network.json b/hedera-node/hedera-app/src/test/resources/bootstrap/network.json index 8a19f6db7e5f..f579261810d8 100644 --- a/hedera-node/hedera-app/src/test/resources/bootstrap/network.json +++ b/hedera-node/hedera-app/src/test/resources/bootstrap/network.json @@ -28,7 +28,8 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - } + }, + "tssEncryptionKey": "ASM=" }, { "rosterEntry": { "nodeId": "1", @@ -60,7 +61,8 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - } + }, + "tssEncryptionKey": "ASM=" }, { "rosterEntry": { "nodeId": "2", @@ -92,7 +94,8 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - } + }, + "tssEncryptionKey": "ASM=" }, { "rosterEntry": { "nodeId": "3", @@ -124,7 +127,8 @@ "adminKey": { "ed25519": "CqjiEGTGHquG4qnBZFZbTnqaQUYQbgps0DqMOVoRDpI=" } - } + }, + "tssEncryptionKey": "ASM=" }], "tssMessages": [{ "targetRosterHash": "x59s9F7BvXzYVRKj6jYJq9qMuzV8pgchc053L920tMaWeGNzXoBXhW1x25snK0by", diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java index d456e5430c5e..a88aa73998c0 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/AppTestBase.java @@ -100,6 +100,9 @@ public class AppTestBase extends TestBase implements TransactionFactory, Scenari public static final ScheduledExecutorService METRIC_EXECUTOR = Executors.newSingleThreadScheduledExecutor(); public static final Configuration DEFAULT_CONFIG = HederaTestConfigBuilder.createConfig(); + public static final Configuration WITH_ROSTER_LIFECYCLE = HederaTestConfigBuilder.create() + .withValue("addressBook.useRosterLifecycle", true) + .getOrCreateConfig(); private static final String ACCOUNTS_KEY = "ACCOUNTS"; private static final String ALIASES_KEY = "ALIASES"; @@ -324,7 +327,7 @@ public App build() { realSelfNodeInfo = new NodeInfoImpl( selfNodeInfo.nodeId(), selfNodeInfo.accountId(), - selfNodeInfo.stake(), + selfNodeInfo.weight(), selfNodeInfo.gossipEndpoints(), selfNodeInfo.sigCertBytes()); } @@ -335,19 +338,20 @@ public App build() { final var addresses = nodes.stream() .map(nodeInfo -> new Address() .copySetNodeId(NodeId.of(nodeInfo.nodeId())) - .copySetWeight(nodeInfo.zeroStake() ? 0 : 10)) + .copySetWeight(nodeInfo.zeroWeight() ? 0 : 10)) .toList(); final var addressBook = new AddressBook(addresses); final var platform = new FakePlatform(realSelfNodeInfo.nodeId(), addressBook); final var initialState = new FakeState(); final var genesisRoster = buildRoster(addressBook); - final var networkInfo = new GenesisNetworkInfo(genesisRoster, Bytes.fromHex("03")); - final var startupNetworks = new FakeStartupNetworks(Network.newBuilder() + final var genesisNetwork = Network.newBuilder() .nodeMetadata(genesisRoster.rosterEntries().stream() .map(entry -> NodeMetadata.newBuilder().rosterEntry(entry).build()) .toList()) - .build()); + .build(); + final var networkInfo = new GenesisNetworkInfo(genesisNetwork, Bytes.fromHex("03")); + final var startupNetworks = new FakeStartupNetworks(genesisNetwork); services.forEach(svc -> { final var reg = new FakeSchemaRegistry(); svc.registerSchemas(reg); diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java index 3c9907532773..51e122ffc25f 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeSchemaRegistry.java @@ -79,6 +79,7 @@ public void migrate( CURRENT_VERSION, networkInfo, DEFAULT_CONFIG, + DEFAULT_CONFIG, new HashMap<>(), new AtomicLong(), startupNetworks); @@ -89,10 +90,19 @@ public void migrate( @NonNull final FakeState state, @Nullable final SemanticVersion previousVersion, @NonNull final NetworkInfo networkInfo, - @NonNull final Configuration config, + @NonNull final Configuration appConfig, + @NonNull final Configuration platformConfig, @NonNull final Map sharedValues, @NonNull final AtomicLong nextEntityNum, @NonNull final StartupNetworks startupNetworks) { + requireNonNull(serviceName); + requireNonNull(state); + requireNonNull(networkInfo); + requireNonNull(appConfig); + requireNonNull(platformConfig); + requireNonNull(sharedValues); + requireNonNull(nextEntityNum); + requireNonNull(startupNetworks); if (schemas.isEmpty()) { logger.info("Service {} does not use state", serviceName); return; @@ -108,14 +118,14 @@ public void migrate( () -> HapiUtils.toString(latestVersion)); for (final var schema : schemas) { final var applications = - schemaApplications.computeApplications(previousVersion, latestVersion, schema, config); + schemaApplications.computeApplications(previousVersion, latestVersion, schema, appConfig); logger.info("Applying {} schema {} ({})", serviceName, schema.getVersion(), applications); final var readableStates = state.getReadableStates(serviceName); final var previousStates = new FilteredReadableStates(readableStates, readableStates.stateKeys()); final WritableStates writableStates; final WritableStates newStates; if (applications.contains(STATE_DEFINITIONS)) { - final var redefinedWritableStates = applyStateDefinitions(serviceName, schema, config, state); + final var redefinedWritableStates = applyStateDefinitions(serviceName, schema, appConfig, state); writableStates = redefinedWritableStates.beforeStates(); newStates = redefinedWritableStates.afterStates(); } else { @@ -125,7 +135,8 @@ public void migrate( previousVersion, previousStates, newStates, - config, + appConfig, + platformConfig, networkInfo, nextEntityNum, sharedValues, @@ -183,7 +194,8 @@ private MigrationContext newMigrationContext( @Nullable final SemanticVersion previousVersion, @NonNull final ReadableStates previousStates, @NonNull final WritableStates writableStates, - @NonNull final Configuration config, + @NonNull final Configuration appConfig, + @NonNull final Configuration platformConfig, @NonNull final NetworkInfo networkInfo, @NonNull final AtomicLong nextEntityNum, @NonNull final Map sharedValues, @@ -224,8 +236,14 @@ public WritableStates newStates() { @NonNull @Override - public Configuration configuration() { - return config; + public Configuration appConfig() { + return appConfig; + } + + @NonNull + @Override + public Configuration platformConfig() { + return platformConfig; } @Override diff --git a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeServiceMigrator.java b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeServiceMigrator.java index 37b183151a17..1d8c59743bb2 100644 --- a/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeServiceMigrator.java +++ b/hedera-node/hedera-app/src/testFixtures/java/com/hedera/node/app/fixtures/state/FakeServiceMigrator.java @@ -50,16 +50,16 @@ public List doMigrations( @NonNull final ServicesRegistry servicesRegistry, @Nullable final SoftwareVersion previousVersion, @NonNull final SoftwareVersion currentVersion, - @NonNull final Configuration nodeConfiguration, - @NonNull final Configuration platformConfiguration, + @NonNull final Configuration appConfig, + @NonNull final Configuration platformConfig, @Nullable final NetworkInfo genesisNetworkInfo, @NonNull final Metrics metrics, @NonNull final StartupNetworks startupNetworks) { requireNonNull(state); requireNonNull(servicesRegistry); requireNonNull(currentVersion); - requireNonNull(nodeConfiguration); - requireNonNull(platformConfiguration); + requireNonNull(appConfig); + requireNonNull(platformConfig); requireNonNull(genesisNetworkInfo); requireNonNull(metrics); @@ -70,8 +70,8 @@ public List doMigrations( throw new IllegalArgumentException("Can only be used with FakeServicesRegistry instances"); } - final AtomicLong prevEntityNum = new AtomicLong( - nodeConfiguration.getConfigData(HederaConfig.class).firstUserEntity() - 1); + final AtomicLong prevEntityNum = + new AtomicLong(appConfig.getConfigData(HederaConfig.class).firstUserEntity() - 1); final Map sharedValues = new HashMap<>(); final var entityIdRegistration = registry.registrations().stream() .filter(service -> @@ -89,7 +89,8 @@ public List doMigrations( fakeState, deserializedPbjVersion, genesisNetworkInfo, - nodeConfiguration, + appConfig, + platformConfig, sharedValues, prevEntityNum, startupNetworks); @@ -104,7 +105,8 @@ public List doMigrations( fakeState, deserializedPbjVersion, genesisNetworkInfo, - platformConfiguration, + appConfig, + platformConfig, sharedValues, prevEntityNum, startupNetworks); diff --git a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TssConfig.java b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TssConfig.java index 00f8e0bb9613..63ca23f43e83 100644 --- a/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TssConfig.java +++ b/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/TssConfig.java @@ -38,7 +38,7 @@ */ @ConfigData("tss") public record TssConfig( - @ConfigProperty(defaultValue = "3") @NetworkProperty long maxSharesPerNode, + @ConfigProperty(defaultValue = "3") @NetworkProperty int maxSharesPerNode, @ConfigProperty(defaultValue = "50") @NetworkProperty int timesToTrySubmission, @ConfigProperty(defaultValue = "5s") @NetworkProperty Duration retryDelay, @ConfigProperty(defaultValue = "10") @NetworkProperty int distinctTxnIdsToTry, diff --git a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java index 978a63c5ff88..40d0efb212b9 100644 --- a/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java +++ b/hedera-node/hedera-file-service-impl/src/main/java/com/hedera/node/app/service/file/impl/schemas/V0490FileSchema.java @@ -217,7 +217,7 @@ public Bytes genesisNodeDetails(@NonNull final NetworkInfo networkInfo) { final var nodeDetails = new ArrayList(); for (final var nodeInfo : networkInfo.addressBook()) { nodeDetails.add(NodeAddress.newBuilder() - .stake(nodeInfo.stake()) + .stake(nodeInfo.weight()) .nodeAccountId(nodeInfo.accountId()) .nodeId(nodeInfo.nodeId()) .rsaPubKey(nodeInfo.hexEncodedPublicKey()) diff --git a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java index 340ca8a4a885..0a00faf9b53c 100644 --- a/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java +++ b/hedera-node/hedera-schedule-service-impl/src/main/java/com/hedera/node/app/service/schedule/impl/handlers/HandlerUtility.java @@ -230,7 +230,7 @@ static TransactionID transactionIdForScheduled(@NonNull final Schedule schedule) * @param schedulingTxnId the scheduling transaction ID * @return the scheduled transaction ID */ - static TransactionID scheduledTxnIdFrom(@NonNull final TransactionID schedulingTxnId) { + public static TransactionID scheduledTxnIdFrom(@NonNull final TransactionID schedulingTxnId) { requireNonNull(schedulingTxnId); return schedulingTxnId.scheduled() ? schedulingTxnId diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0490TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0490TokenSchema.java index c48f5a3cc208..5d8c2fafa15d 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0490TokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0490TokenSchema.java @@ -129,13 +129,13 @@ private void createGenesisSchema(@NonNull final MigrationContext ctx) { } // We will use these various configs for creating accounts. It would be nice to consolidate them somehow - final var ledgerConfig = ctx.configuration().getConfigData(LedgerConfig.class); - final var hederaConfig = ctx.configuration().getConfigData(HederaConfig.class); - final var accountsConfig = ctx.configuration().getConfigData(AccountsConfig.class); + final var ledgerConfig = ctx.appConfig().getConfigData(LedgerConfig.class); + final var hederaConfig = ctx.appConfig().getConfigData(HederaConfig.class); + final var accountsConfig = ctx.appConfig().getConfigData(AccountsConfig.class); // Generate synthetic accounts based on the genesis configuration final Consumer> noOpCb = ignore -> {}; - syntheticAccountCreator.generateSyntheticAccounts(ctx.configuration(), noOpCb, noOpCb, noOpCb, noOpCb, noOpCb); + syntheticAccountCreator.generateSyntheticAccounts(ctx.appConfig(), noOpCb, noOpCb, noOpCb, noOpCb, noOpCb); // ---------- Create system accounts ------------------------- for (final Account acct : syntheticAccountCreator.systemAccounts()) { accounts.put(acct.accountIdOrThrow(), acct); @@ -237,7 +237,7 @@ public static long[] nonContractSystemNums(final long numReservedSystemEntities) } private void initializeStakingNodeInfo(@NonNull final MigrationContext ctx) { - final var config = ctx.configuration(); + final var config = ctx.appConfig(); final var ledgerConfig = config.getConfigData(LedgerConfig.class); final var stakingConfig = config.getConfigData(StakingConfig.class); final var addressBook = ctx.genesisNetworkInfo().addressBook(); diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java index 0e1face5b312..b947535d63df 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/schemas/V0530TokenSchema.java @@ -17,6 +17,7 @@ package com.hedera.node.app.service.token.impl.schemas; import static com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema.STAKING_INFO_KEY; +import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.PendingAirdropId; import com.hedera.hapi.node.base.SemanticVersion; @@ -26,8 +27,13 @@ import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; +import com.swirlds.state.spi.WritableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.Comparator; import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.StreamSupport; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -57,17 +63,16 @@ public void migrate(@NonNull final MigrationContext ctx) { } private void setMinStakeToZero(final MigrationContext ctx) { - final var stakingInfoState = ctx.newStates().get(STAKING_INFO_KEY); - final var addressBook = ctx.genesisNetworkInfo().addressBook(); + final WritableKVState stakingInfos = + ctx.newStates().get(STAKING_INFO_KEY); logger.info("Setting minStake to 0 for all nodes in the address book"); - for (final var node : addressBook) { - final var nodeNumber = - EntityNumber.newBuilder().number(node.nodeId()).build(); - final var stakingInfo = (StakingNodeInfo) stakingInfoState.get(nodeNumber); - if (stakingInfo != null) { - stakingInfoState.put( - nodeNumber, stakingInfo.copyBuilder().minStake(0).build()); - } + final var nodeIds = StreamSupport.stream( + Spliterators.spliteratorUnknownSize(stakingInfos.keys(), Spliterator.NONNULL), false) + .sorted(Comparator.comparingLong(EntityNumber::number)) + .toList(); + for (final var nodeId : nodeIds) { + final var stakingInfo = requireNonNull(stakingInfos.get(nodeId)); + stakingInfos.put(nodeId, stakingInfo.copyBuilder().minStake(0).build()); } } } diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0490TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0490TokenSchemaTest.java index 2e793c4720f8..8531f229aeb3 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0490TokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0490TokenSchemaTest.java @@ -118,6 +118,7 @@ void nonGenesisDoesntCreate() { nonEmptyPrevStates, newStates, config, + config, networkInfo, entityIdStore, CURRENT_VERSION, @@ -142,6 +143,7 @@ void initializesStakingDataOnGenesisStart() { EmptyReadableStates.INSTANCE, newStates, config, + config, networkInfo, entityIdStore, null, @@ -164,6 +166,7 @@ void createsAllAccountsOnGenesisStart() { EmptyReadableStates.INSTANCE, newStates, config, + config, networkInfo, entityIdStore, null, @@ -239,6 +242,7 @@ void blocklistNotEnabled() { EmptyReadableStates.INSTANCE, newStates, config, + config, networkInfo, entityIdStore, CURRENT_VERSION, @@ -266,6 +270,7 @@ void onlyExpectedIdsUsed() { EmptyReadableStates.INSTANCE, newStates, config, + config, networkInfo, entityIdStore, null, diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java index 627e0310bd3d..fad1e6fa8751 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/schemas/V0530TokenSchemaTest.java @@ -109,6 +109,7 @@ void setsStakingInfoMinStakeToZero() { previousStates, newStates, config, + config, networkInfo, entityIdStore, null, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BootstrapOverride.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ConfigOverride.java similarity index 89% rename from hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BootstrapOverride.java rename to hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ConfigOverride.java index 8ccae865a57b..c7e4833b00cd 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/BootstrapOverride.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/ConfigOverride.java @@ -17,9 +17,9 @@ package com.hedera.services.bdd.junit; /** - * An override for a bootstrap property. + * A network configuration override. */ -public @interface BootstrapOverride { +public @interface ConfigOverride { String key(); String value(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/GenesisHapiTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/GenesisHapiTest.java index 50a5b2a941ab..2f9bccfcaff7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/GenesisHapiTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/GenesisHapiTest.java @@ -42,5 +42,5 @@ @ExtendWith({NetworkTargetingExtension.class, SpecNamingExtension.class}) @Isolated public @interface GenesisHapiTest { - BootstrapOverride[] bootstrapOverrides() default {}; + ConfigOverride[] bootstrapOverrides() default {}; } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java index 249f6fbb65fd..aab29477152c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/ExtensionUtils.java @@ -25,6 +25,7 @@ import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.junit.LeakyRepeatableHapiTest; import com.hedera.services.bdd.junit.RepeatableHapiTest; +import com.hedera.services.bdd.junit.restart.RestartHapiTest; import edu.umd.cs.findbugs.annotations.NonNull; import java.lang.reflect.Method; import java.util.Optional; @@ -39,6 +40,7 @@ private static boolean isHapiTest(@NonNull final Method method) { return isAnnotated(method, HapiTest.class) || isAnnotated(method, LeakyHapiTest.class) || isAnnotated(method, GenesisHapiTest.class) + || isAnnotated(method, RestartHapiTest.class) || isAnnotated(method, EmbeddedHapiTest.class) || isAnnotated(method, RepeatableHapiTest.class) || isAnnotated(method, LeakyEmbeddedHapiTest.class) diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java index 9c421d697751..47fa7e9cf465 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/extensions/NetworkTargetingExtension.java @@ -20,11 +20,24 @@ import static com.hedera.services.bdd.junit.ContextRequirement.THROTTLE_OVERRIDES; import static com.hedera.services.bdd.junit.extensions.ExtensionUtils.hapiTestMethodOf; import static com.hedera.services.bdd.junit.hedera.embedded.EmbeddedMode.CONCURRENT; +import static com.hedera.services.bdd.junit.hedera.embedded.EmbeddedMode.REPEATABLE; +import static com.hedera.services.bdd.junit.hedera.utils.AddressBookUtils.CLASSIC_ENCRYPTION_KEYS; +import static com.hedera.services.bdd.junit.hedera.utils.AddressBookUtils.CLASSIC_KEY_MATERIAL_GENERATOR; +import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.workingDirVersion; +import static com.hedera.services.bdd.junit.restart.StartupAssets.ROSTER_AND_ENCRYPTION_KEYS; +import static com.hedera.services.bdd.junit.restart.StartupAssets.ROSTER_AND_FULL_TSS_KEY_MATERIAL; +import static com.hedera.services.bdd.spec.HapiSpec.doTargetSpec; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toMap; import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated; -import com.hedera.services.bdd.junit.BootstrapOverride; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.util.HapiUtils; +import com.hedera.node.app.fixtures.state.FakeState; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.services.bdd.junit.ConfigOverride; import com.hedera.services.bdd.junit.ContextRequirement; import com.hedera.services.bdd.junit.GenesisHapiTest; import com.hedera.services.bdd.junit.HapiTest; @@ -34,16 +47,27 @@ import com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener; import com.hedera.services.bdd.junit.TargetEmbeddedMode; import com.hedera.services.bdd.junit.hedera.HederaNetwork; +import com.hedera.services.bdd.junit.hedera.TssKeyMaterial; import com.hedera.services.bdd.junit.hedera.embedded.EmbeddedMode; import com.hedera.services.bdd.junit.hedera.embedded.EmbeddedNetwork; +import com.hedera.services.bdd.junit.restart.RestartHapiTest; +import com.hedera.services.bdd.junit.restart.SavedStateSpec; +import com.hedera.services.bdd.junit.restart.StartupAssets; import com.hedera.services.bdd.spec.HapiSpec; +import com.hedera.services.bdd.spec.SpecOperation; import com.hedera.services.bdd.spec.keys.RepeatableKeyGenerator; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; +import java.util.function.LongFunction; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -57,9 +81,34 @@ * networks for annotated test classes and targeting them instead of the shared network. */ public class NetworkTargetingExtension implements BeforeEachCallback, AfterEachCallback { + private static final String SPEC_NAME = ""; + private static final Set OVERRIDES_WITH_ENCRYPTION_KEYS = + EnumSet.of(ROSTER_AND_ENCRYPTION_KEYS, ROSTER_AND_FULL_TSS_KEY_MATERIAL); + public static final AtomicReference SHARED_NETWORK = new AtomicReference<>(); public static final AtomicReference REPEATABLE_KEY_GENERATOR = new AtomicReference<>(); + /** + * The functions that provide the TSS encryption key and key material for a TSS node. + * @param tssEncryptionKeyFn the function that provides the TSS encryption key + * @param tssKeyMaterialFn the function that provides the TSS key material + */ + private record TssSourceFns( + @NonNull LongFunction tssEncryptionKeyFn, + @NonNull Function, Optional> tssKeyMaterialFn) { + public static TssSourceFns from(@NonNull final StartupAssets assets) { + requireNonNull(assets); + return new TssSourceFns( + OVERRIDES_WITH_ENCRYPTION_KEYS.contains(assets) + ? CLASSIC_ENCRYPTION_KEYS::get + : nodeId -> Bytes.EMPTY, + assets == ROSTER_AND_FULL_TSS_KEY_MATERIAL + ? rosterEntries -> + Optional.of(CLASSIC_KEY_MATERIAL_GENERATOR.apply(new Roster(rosterEntries))) + : rosterEntries -> Optional.empty()); + } + } + @Override public void beforeEach(@NonNull final ExtensionContext extensionContext) { hapiTestMethodOf(extensionContext).ifPresent(method -> { @@ -68,8 +117,42 @@ public void beforeEach(@NonNull final ExtensionContext extensionContext) { new EmbeddedNetwork(method.getName().toUpperCase(), method.getName(), CONCURRENT); final var a = method.getAnnotation(GenesisHapiTest.class); final var bootstrapOverrides = Arrays.stream(a.bootstrapOverrides()) - .collect(toMap(BootstrapOverride::key, BootstrapOverride::value)); - targetNetwork.startWithOverrides(bootstrapOverrides); + .collect(toMap(ConfigOverride::key, ConfigOverride::value)); + targetNetwork.startWith(bootstrapOverrides, nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); + HapiSpec.TARGET_NETWORK.set(targetNetwork); + } else if (isAnnotated(method, RestartHapiTest.class)) { + final var targetNetwork = + new EmbeddedNetwork(method.getName().toUpperCase(), method.getName(), REPEATABLE); + final var a = method.getAnnotation(RestartHapiTest.class); + + final var setupOverrides = + Arrays.stream(a.setupOverrides()).collect(toMap(ConfigOverride::key, ConfigOverride::value)); + final var setupTssSourceFns = TssSourceFns.from(a.setupAssets()); + + final var restartOverrides = + Arrays.stream(a.restartOverrides()).collect(toMap(ConfigOverride::key, ConfigOverride::value)); + final var restartTssSourceFns = TssSourceFns.from(a.restartAssets()); + + switch (a.restartType()) { + case GENESIS -> targetNetwork.startWith( + restartOverrides, + restartTssSourceFns.tssEncryptionKeyFn(), + restartTssSourceFns.tssKeyMaterialFn()); + case SAME_VERSION -> targetNetwork.startWith( + setupOverrides, + setupTssSourceFns.tssEncryptionKeyFn(), + setupTssSourceFns.tssKeyMaterialFn()); + case UPGRADE_BOUNDARY -> startFromPreviousVersion(targetNetwork, setupOverrides, setupTssSourceFns); + } + switch (a.restartType()) { + case GENESIS -> { + // The restart was from genesis, so nothing else to do + } + case SAME_VERSION, UPGRADE_BOUNDARY -> { + final var state = postGenesisStateOf(targetNetwork, a); + targetNetwork.restart(state, restartOverrides); + } + } HapiSpec.TARGET_NETWORK.set(targetNetwork); } else { ensureEmbeddedNetwork(extensionContext); @@ -100,6 +183,7 @@ public void afterEach(@NonNull final ExtensionContext extensionContext) { /** * Ensures that the embedded network is running, if required by the test class or method. + * * @param extensionContext the extension context */ public static void ensureEmbeddedNetwork(@NonNull final ExtensionContext extensionContext) { @@ -110,6 +194,7 @@ public static void ensureEmbeddedNetwork(@NonNull final ExtensionContext extensi /** * Returns the embedded mode required by the test class or method, if any. + * * @param extensionContext the extension context * @return the embedded mode */ @@ -135,6 +220,7 @@ private void bindThreadTargets( /** * If there is an explicit resource to load, returns it; otherwise returns null if the test's * context requirement does not include the relevant requirement. + * * @param contextRequirements the context requirements of the test * @param relevantRequirement the relevant context requirement * @param resource the path to the resource @@ -149,4 +235,48 @@ private void bindThreadTargets( } return List.of(contextRequirements).contains(relevantRequirement) ? "" : null; } + + /** + * Starts the given target embedded network from the previous version with any other requested overrides. + * + * @param targetNetwork the target network + * @param overrides the overrides + * @param tssSourceFns the TSS source functions + */ + private void startFromPreviousVersion( + @NonNull final EmbeddedNetwork targetNetwork, + @NonNull final Map overrides, + @NonNull final TssSourceFns tssSourceFns) { + final Map netOverrides = new HashMap<>(overrides); + final var currentVersion = workingDirVersion(); + final var previousVersion = currentVersion + .copyBuilder() + .minor(currentVersion.minor() - 1) + .patch(0) + .pre("") + .build("") + .build(); + netOverrides.put("hedera.services.version", HapiUtils.toString(previousVersion)); + targetNetwork.startWith(netOverrides, tssSourceFns.tssEncryptionKeyFn(), tssSourceFns.tssKeyMaterialFn()); + } + + private FakeState postGenesisStateOf( + @NonNull final EmbeddedNetwork targetNetwork, @NonNull final RestartHapiTest a) { + final var spec = new HapiSpec(SPEC_NAME, new SpecOperation[] {cryptoCreate("genesisAccount")}); + doTargetSpec(spec, targetNetwork); + try { + spec.execute(); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + final SavedStateSpec savedStateSpec; + try { + savedStateSpec = a.savedStateSpec().getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + final var state = targetNetwork.embeddedHederaOrThrow().state(); + savedStateSpec.accept(state); + return state; + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractLocalNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractLocalNode.java index 92a73fdb9329..c9c5291b7f49 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractLocalNode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/AbstractLocalNode.java @@ -16,14 +16,29 @@ package com.hedera.services.bdd.junit.hedera; +import static com.hedera.node.app.info.DiskStartupNetworks.ARCHIVE; +import static com.hedera.node.app.info.DiskStartupNetworks.GENESIS_NETWORK_JSON; +import static com.hedera.node.app.info.DiskStartupNetworks.OVERRIDE_NETWORK_JSON; +import static com.hedera.node.app.info.DiskStartupNetworks.ROUND_DIR_PATTERN; +import static com.hedera.services.bdd.junit.hedera.ExternalPath.DATA_CONFIG_DIR; import static com.hedera.services.bdd.junit.hedera.ExternalPath.UPGRADE_ARTIFACTS_DIR; import static com.hedera.services.bdd.junit.hedera.subprocess.ProcessUtils.conditionFuture; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.recreateWorkingDir; import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.internal.network.Network; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.pbj.runtime.io.stream.ReadableStreamingData; import edu.umd.cs.findbugs.annotations.NonNull; +import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.LongFunction; /** * Implementation support for a node that uses a local working directory. @@ -43,9 +58,14 @@ protected AbstractLocalNode(@NonNull final NodeMetadata metadata) { } @Override - public T initWorkingDir(@NonNull final String configTxt) { + public @NonNull T initWorkingDir( + @NonNull final String configTxt, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { requireNonNull(configTxt); - recreateWorkingDir(requireNonNull(metadata.workingDir()), configTxt); + requireNonNull(tssEncryptionKeyFn); + requireNonNull(tssKeyMaterialFn); + recreateWorkingDir(requireNonNull(metadata.workingDir()), configTxt, tssEncryptionKeyFn, tssKeyMaterialFn); workingDirInitialized = true; return self(); } @@ -62,9 +82,53 @@ public CompletableFuture mfFuture(@NonNull final MarkerFile markerFile) { return conditionFuture(() -> mfExists(markerFile), () -> MF_BACKOFF_MS); } + @Override + public Optional startupNetwork() { + return getStartupAddressBook(getExternalPath(DATA_CONFIG_DIR)); + } + protected abstract T self(); private boolean mfExists(@NonNull final MarkerFile markerFile) { return Files.exists(getExternalPath(UPGRADE_ARTIFACTS_DIR).resolve(markerFile.fileName())); } + + /** + * Tries to find any startup address book in the given directory or its {@code .archive} subdirectory. + * @param path the path to search + * @return the address book, if found + */ + private Optional getStartupAddressBook(@NonNull final Path path) { + return getStartupAddressBookIn(path).or(() -> getStartupAddressBookIn(path.resolve(ARCHIVE))); + } + + private Optional getStartupAddressBookIn(@NonNull final Path path) { + return getStartupAddressBookAt(path.resolve(GENESIS_NETWORK_JSON)) + .or(() -> getStartupAddressBookAt(path.resolve(OVERRIDE_NETWORK_JSON))) + .or(() -> { + Optional scopedAddressBook = Optional.empty(); + try (final var dirStream = Files.list(path)) { + scopedAddressBook = dirStream + .filter(Files::isDirectory) + .filter(dir -> ROUND_DIR_PATTERN + .matcher(dir.getFileName().toString()) + .matches()) + .map(dir -> getStartupAddressBookAt(dir.resolve(OVERRIDE_NETWORK_JSON))) + .flatMap(Optional::stream) + .findFirst(); + } catch (IOException ignore) { + } + return scopedAddressBook; + }); + } + + private Optional getStartupAddressBookAt(@NonNull final Path path) { + if (Files.exists(path)) { + try (final var fin = Files.newInputStream(path)) { + return Optional.of(Network.JSON.parse(new ReadableStreamingData(fin))); + } catch (Exception ignore) { + } + } + return Optional.empty(); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java index 904f4ddb8ef4..f7d92b22fae1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNetwork.java @@ -18,6 +18,8 @@ import static java.util.Objects.requireNonNull; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.remote.RemoteNetwork; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.HapiSpec; @@ -32,6 +34,9 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.function.LongFunction; /** * A network of Hedera nodes. @@ -156,10 +161,16 @@ default HederaNode getRequiredNode(@NonNull final NodeSelector selector) { void start(); /** - * Starts all nodes in the network with the given overrides. + * Starts all nodes in the network with the given customizations. + * * @param bootstrapOverrides the overrides + * @param tssEncryptionKeyFn the encryption key function + * @param tssKeyMaterialFn the key material function */ - default void startWithOverrides(@NonNull Map bootstrapOverrides) { + default void startWith( + @NonNull final Map bootstrapOverrides, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { throw new UnsupportedOperationException(); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java index 7ee5d9e72817..0c2d613c26d1 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/HederaNode.java @@ -17,14 +17,21 @@ package com.hedera.services.bdd.junit.hedera; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.internal.network.Network; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.subprocess.NodeStatus; import com.hedera.services.bdd.spec.HapiSpec; import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.file.Path; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongFunction; public interface HederaNode { /** @@ -80,7 +87,21 @@ public interface HederaNode { * @param configTxt the address book the node should start with * @return this */ - HederaNode initWorkingDir(String configTxt); + default HederaNode initWorkingDir(@NonNull final String configTxt) { + return initWorkingDir(configTxt, nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); + } + + /** + * Initializes the working directory for the node. Must be called before the node is started. + * + * @param configTxt the address book the node should start with + * @return this + */ + @NonNull + HederaNode initWorkingDir( + @NonNull String configTxt, + @NonNull LongFunction tssEncryptionKeyFn, + @NonNull Function, Optional> tssKeyMaterialFn); /** * Starts the node software. @@ -143,4 +164,12 @@ default String hapiSpecInfo() { default boolean dumpThreads() { return false; } + + /** + * If this node's startup assets included a genesis or override address book, returns it. + * @return the node's startup address book, if available + */ + default Optional startupNetwork() { + return Optional.empty(); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/TssKeyMaterial.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/TssKeyMaterial.java new file mode 100644 index 000000000000..a0d5377f36bc --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/TssKeyMaterial.java @@ -0,0 +1,31 @@ +/* + * 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; + +import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.node.internal.network.Network; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; + +/** + * A summary of all the TSS key material that could be in a {@link Network}. + * + * @param ledgerId the ledger ID + * @param tssMessages the TSS messages + */ +public record TssKeyMaterial(@NonNull Bytes ledgerId, @NonNull List tssMessages) {} 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 1c7626c1a99e..c1d7c8921e82 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 @@ -19,10 +19,11 @@ import static com.hedera.hapi.util.HapiUtils.parseAccount; import static com.hedera.node.app.hapi.utils.CommonPbjConverters.fromPbj; import static com.hedera.services.bdd.junit.hedera.ExternalPath.ADDRESS_BOOK; -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; +import static com.swirlds.platform.roster.RosterUtils.rosterFrom; import static com.swirlds.platform.state.service.PbjConverter.toPbjAddressBook; import static com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema.PLATFORM_STATE_KEY; import static com.swirlds.platform.system.InitTrigger.GENESIS; +import static com.swirlds.platform.system.InitTrigger.RESTART; import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; import static com.swirlds.platform.system.status.PlatformStatus.FREEZE_COMPLETE; import static java.util.Objects.requireNonNull; @@ -34,12 +35,14 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.platform.state.PlatformState; import com.hedera.node.app.Hedera; +import com.hedera.node.app.Hedera.TssBaseServiceFactory; +import com.hedera.node.app.ServicesMain; import com.hedera.node.app.fixtures.state.FakeServiceMigrator; import com.hedera.node.app.fixtures.state.FakeServicesRegistry; import com.hedera.node.app.fixtures.state.FakeState; import com.hedera.node.app.info.DiskStartupNetworks; -import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.version.ServicesSoftwareVersion; +import com.hedera.node.internal.network.Network; 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; @@ -50,17 +53,14 @@ import com.hederahashgraph.api.proto.java.Timestamp; import com.hederahashgraph.api.proto.java.Transaction; import com.hederahashgraph.api.proto.java.TransactionResponse; +import com.swirlds.base.utility.Pair; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.crypto.Hash; 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.config.legacy.LegacyConfigPropertiesLoader; import com.swirlds.platform.listeners.PlatformStatusChangeNotification; import com.swirlds.platform.state.service.PlatformStateService; -import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -106,9 +106,9 @@ public abstract class AbstractEmbeddedHedera implements EmbeddedHedera { protected final Map nodeIds; protected final Map accountIds; - protected final FakeState state = new FakeState(); protected final AccountID defaultNodeAccountId; protected final AddressBook addressBook; + protected final Network network; protected final Roster roster; protected final NodeId defaultNodeId; protected final AtomicInteger nextNano = new AtomicInteger(0); @@ -116,17 +116,33 @@ public abstract class AbstractEmbeddedHedera implements EmbeddedHedera { protected final ServicesSoftwareVersion version; protected final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + /** + * Non-final because a "saved state" may be provided via {@link EmbeddedHedera#restart(FakeState)}. + */ + protected FakeState state; + /** + * Non-final because the compiler can't tell that the {@link TssBaseServiceFactory} lambda we give the + * {@link Hedera} constructor will always set this (the fake's {@link com.hedera.node.app.tss.TssBaseServiceImpl} + * delegate needs to be constructed from the Hedera instance's {@link com.hedera.node.app.spi.AppContext}). + */ protected FakeTssBaseService tssBaseService; protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { requireNonNull(node); addressBook = loadAddressBook(node.getExternalPath(ADDRESS_BOOK)); - roster = buildRoster(addressBook); - nodeIds = stream(spliteratorUnknownSize(addressBook.iterator(), 0), false) - .collect(toMap(AbstractEmbeddedHedera::accountIdOf, Address::getNodeId)); - accountIds = stream(spliteratorUnknownSize(addressBook.iterator(), 0), false) - .collect(toMap(Address::getNodeId, address -> parseAccount(address.getMemo()))); - defaultNodeId = addressBook.getNodeId(0); + network = node.startupNetwork().orElseThrow(); + roster = rosterFrom(network); + nodeIds = network.nodeMetadata().stream() + .map(metadata -> Pair.of( + fromPbj(metadata.nodeOrThrow().accountIdOrThrow()), + NodeId.of(metadata.rosterEntryOrThrow().nodeId()))) + .collect(toMap(Pair::left, Pair::right)); + accountIds = network.nodeMetadata().stream() + .map(metadata -> Pair.of( + NodeId.of(metadata.rosterEntryOrThrow().nodeId()), + metadata.nodeOrThrow().accountIdOrThrow())) + .collect(toMap(Pair::left, Pair::right)); + defaultNodeId = NodeId.FIRST_NODE_ID; defaultNodeAccountId = fromPbj(accountIds.get(defaultNodeId)); hedera = new Hedera( ConstructableRegistry.getInstance(), @@ -135,25 +151,36 @@ protected AbstractEmbeddedHedera(@NonNull final EmbeddedNode node) { this::now, appContext -> { this.tssBaseService = new FakeTssBaseService(appContext); - return tssBaseService; + return this.tssBaseService; }, - DiskStartupNetworks::new, - NodeId.of(0L)); + DiskStartupNetworks::new); version = (ServicesSoftwareVersion) hedera.getSoftwareVersion(); blockStreamEnabled = hedera.isBlockStreamEnabled(); Runtime.getRuntime().addShutdownHook(new Thread(executorService::shutdownNow)); } @Override - public void start() { - final Configuration configuration = ConfigurationBuilder.create() - .withSource(SystemEnvironmentConfigSource.getInstance()) - .withSource(SystemPropertiesConfigSource.getInstance()) - .autoDiscoverExtensions() - .build(); + public void restart(@NonNull final FakeState state) { + this.state = requireNonNull(state); + start(); + } + @Override + public void start() { + final InitTrigger trigger; + if (state == null) { + trigger = GENESIS; + state = new FakeState(); + } else { + trigger = RESTART; + } hedera.initializeStatesApi( - state, fakePlatform().getContext().getMetrics(), GENESIS, addressBook, configuration); + state, + fakePlatform().getContext().getMetrics(), + trigger, + network, + ServicesMain.buildPlatformConfig(), + addressBook); // TODO - remove this after https://github.com/hashgraph/hedera-services/issues/16552 is done // and we are running all CI tests with the Roster lifecycle enabled @@ -165,12 +192,6 @@ public void start() { .addressBook(toPbjAddressBook(addressBook)) .build()); ((CommittableWritableStates) writableStates).commit(); - if (!hedera.isRosterLifecycleEnabled()) { - final var writableRosterStates = state.getWritableStates(RosterService.NAME); - final WritableRosterStore writableRosterStore = new WritableRosterStore(writableRosterStates); - writableRosterStore.putActiveRoster(buildRoster(addressBook), 0); - ((CommittableWritableStates) writableRosterStates).commit(); - } // --- end of temporary code block --- hedera.setInitialStateHash(FAKE_START_OF_STATE_HASH); @@ -185,6 +206,11 @@ public Hedera hedera() { return hedera; } + @Override + public Roster roster() { + return roster; + } + @Override public void stop() { fakePlatform().notifyListeners(FREEZE_COMPLETE_NOTIFICATION); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/ConcurrentEmbeddedHedera.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/ConcurrentEmbeddedHedera.java index 8b0d1fcd9ffa..5e9458a502ff 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/ConcurrentEmbeddedHedera.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/ConcurrentEmbeddedHedera.java @@ -115,7 +115,7 @@ private class ConcurrentFakePlatform extends AbstractFakePlatform implements Pla private final ScheduledExecutorService executorService; public ConcurrentFakePlatform(@NonNull final ScheduledExecutorService executorService) { - super(defaultNodeId, addressBook, requireNonNull(executorService)); + super(defaultNodeId, roster, requireNonNull(executorService)); this.executorService = executorService; } @@ -159,7 +159,7 @@ private void handleTransactions() { event.getSoftwareVersion()); }) .toList(); - final var round = new FakeRound(roundNo.getAndIncrement(), roster, consensusEvents); + final var round = new FakeRound(roundNo.getAndIncrement(), requireNonNull(roster), consensusEvents); hedera.handleWorkflow().handleRound(state, round); hedera.onSealConsensusRound(round, state); notifyStateHashed(round.getRoundNum()); 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 883f3187298b..f81f12dc3a49 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 @@ -16,6 +16,7 @@ package com.hedera.services.bdd.junit.hedera.embedded; +import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.Hedera; import com.hedera.node.app.fixtures.state.FakeState; import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssBaseService; @@ -37,6 +38,13 @@ public interface EmbeddedHedera { */ void start(); + /** + * Starts the embedded Hedera node from a saved state customized by the given specs. + * + * @param state the state to customize + */ + void restart(@NonNull FakeState state); + /** * Stops the embedded Hedera node. */ @@ -84,6 +92,12 @@ public interface EmbeddedHedera { */ Hedera hedera(); + /** + * Returns the roster of the embedded Hedera node. + * @return the roster of the embedded Hedera node + */ + Roster roster(); + /** * Advances the synthetic time in the embedded Hedera node by a given duration. */ diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java index 5356af896ad7..0ccaae0d02e4 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/EmbeddedNetwork.java @@ -28,10 +28,14 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.blockrecords.RunningHashes; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.app.fixtures.state.FakeState; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.AbstractNetwork; import com.hedera.services.bdd.junit.hedera.HederaNetwork; import com.hedera.services.bdd.junit.hedera.HederaNode; import com.hedera.services.bdd.junit.hedera.SystemFunctionalityTarget; +import com.hedera.services.bdd.junit.hedera.TssKeyMaterial; import com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils; import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.TargetNetworkType; @@ -44,7 +48,12 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; +import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongFunction; import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -97,25 +106,30 @@ public EmbeddedNetwork( this.configTxt = configTxtForLocal(name(), nodes(), 1, 1); } + /** + * Starts the embedded Hedera network from a saved state customized by the given specs. + * + * @param state the state to customize + */ + public void restart(@NonNull final FakeState state, @NonNull final Map bootstrapOverrides) { + requireNonNull(state); + startVia(hedera -> hedera.restart(state), bootstrapOverrides, nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); + } + @Override public void start() { - startWithOverrides(emptyMap()); + startWith(emptyMap(), nodeId -> Bytes.EMPTY, nodes -> Optional.empty()); } @Override - public void startWithOverrides(@NonNull final Map bootstrapOverrides) { + public void startWith( + @NonNull final Map bootstrapOverrides, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { requireNonNull(bootstrapOverrides); - // Initialize the working directory - embeddedNode.initWorkingDir(configTxt); - if (!bootstrapOverrides.isEmpty()) { - updateBootstrapProperties(embeddedNode.getExternalPath(APPLICATION_PROPERTIES), bootstrapOverrides); - } - embeddedNode.start(); - // Start the embedded Hedera "network" - embeddedHedera = switch (mode) { - case REPEATABLE -> new RepeatableEmbeddedHedera(embeddedNode); - case CONCURRENT -> new ConcurrentEmbeddedHedera(embeddedNode);}; - embeddedHedera.start(); + requireNonNull(tssEncryptionKeyFn); + requireNonNull(tssKeyMaterialFn); + startVia(EmbeddedHedera::start, bootstrapOverrides, tssEncryptionKeyFn, tssKeyMaterialFn); } @Override @@ -190,4 +204,22 @@ public EmbeddedMode mode() { protected HapiPropertySource networkOverrides() { return WorkingDirUtils.hapiTestStartupProperties(); } + + private void startVia( + @NonNull final Consumer start, + @NonNull final Map bootstrapOverrides, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { + // Initialize the working directory + embeddedNode.initWorkingDir(configTxt, tssEncryptionKeyFn, tssKeyMaterialFn); + if (!bootstrapOverrides.isEmpty()) { + updateBootstrapProperties(embeddedNode.getExternalPath(APPLICATION_PROPERTIES), bootstrapOverrides); + } + embeddedNode.start(); + // Start the embedded Hedera "network" + embeddedHedera = switch (mode) { + case REPEATABLE -> new RepeatableEmbeddedHedera(embeddedNode); + case CONCURRENT -> new ConcurrentEmbeddedHedera(embeddedNode);}; + start.accept(embeddedHedera); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/RepeatableEmbeddedHedera.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/RepeatableEmbeddedHedera.java index e6a250279da6..db237fc3b353 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/RepeatableEmbeddedHedera.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/RepeatableEmbeddedHedera.java @@ -36,7 +36,6 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.Round; -import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.events.ConsensusEvent; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Duration; @@ -64,7 +63,7 @@ public class RepeatableEmbeddedHedera extends AbstractEmbeddedHedera implements public RepeatableEmbeddedHedera(@NonNull final EmbeddedNode node) { super(node); - platform = new SynchronousFakePlatform(defaultNodeId, addressBook, executorService); + platform = new SynchronousFakePlatform(defaultNodeId, executorService); } @Override @@ -161,10 +160,8 @@ private class SynchronousFakePlatform extends AbstractFakePlatform implements Pl private FakeEvent lastCreatedEvent; public SynchronousFakePlatform( - @NonNull NodeId selfId, - @NonNull AddressBook addressBook, - @NonNull ScheduledExecutorService executorService) { - super(selfId, addressBook, executorService); + @NonNull final NodeId selfId, @NonNull final ScheduledExecutorService executorService) { + super(selfId, roster, executorService); } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/AbstractFakePlatform.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/AbstractFakePlatform.java index b1cffb9094d4..7c620dc29064 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/AbstractFakePlatform.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/embedded/fakes/AbstractFakePlatform.java @@ -26,10 +26,8 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.common.utility.AutoCloseableWrapper; import com.swirlds.platform.listeners.PlatformStatusChangeNotification; -import com.swirlds.platform.roster.RosterRetriever; import com.swirlds.platform.system.Platform; import com.swirlds.platform.system.SwirldState; -import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.atomic.AtomicLong; @@ -42,19 +40,17 @@ public abstract class AbstractFakePlatform implements Platform { protected final AtomicLong consensusOrder = new AtomicLong(1); private final NodeId selfId; - private final AddressBook addressBook; private final Roster roster; private final PlatformContext platformContext; private final FakeNotificationEngine notificationEngine = new FakeNotificationEngine(); public AbstractFakePlatform( @NonNull final NodeId selfId, - @NonNull final AddressBook addressBook, + @NonNull final Roster roster, @NonNull final ScheduledExecutorService executorService) { requireNonNull(executorService); this.selfId = requireNonNull(selfId); - this.addressBook = requireNonNull(addressBook); - this.roster = RosterRetriever.buildRoster(addressBook); + this.roster = requireNonNull(roster); platformContext = new FakePlatformContext(selfId, executorService); } 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 index 0f714498b8d7..68d7226613f5 100644 --- 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 @@ -253,8 +253,8 @@ public void regenerateKeyMaterial(@NonNull final State state) { } @Override - public void generateParticipantDirectory(@NonNull final State state) { - delegate.generateParticipantDirectory(state); + public void ensureParticipantDirectoryKnown(@NonNull final State state) { + delegate.ensureParticipantDirectoryKnown(state); } @Override diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/remote/RemoteNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/remote/RemoteNode.java index ed7a2bd578c8..15f3c8510380 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/remote/RemoteNode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/remote/RemoteNode.java @@ -16,18 +16,25 @@ package com.hedera.services.bdd.junit.hedera.remote; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.AbstractNode; import com.hedera.services.bdd.junit.hedera.ExternalPath; import com.hedera.services.bdd.junit.hedera.HederaNode; import com.hedera.services.bdd.junit.hedera.MarkerFile; import com.hedera.services.bdd.junit.hedera.NodeMetadata; +import com.hedera.services.bdd.junit.hedera.TssKeyMaterial; import com.hedera.services.bdd.junit.hedera.subprocess.NodeStatus; import com.swirlds.platform.system.status.PlatformStatus; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.nio.file.Path; +import java.util.List; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.LongFunction; public class RemoteNode extends AbstractNode implements HederaNode { public RemoteNode(@NonNull final NodeMetadata metadata) { @@ -44,6 +51,15 @@ public HederaNode initWorkingDir(@NonNull final String configTxt) { throw new UnsupportedOperationException("Cannot initialize a remote node's working directory"); } + @NonNull + @Override + public HederaNode initWorkingDir( + @NonNull final String configTxt, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { + throw new UnsupportedOperationException("Cannot initialize a remote node's working directory"); + } + @Override public HederaNode start() { // No-op, remote nodes must already be running diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNetwork.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNetwork.java index 94dec3ce59dc..2fbeaef48572 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNetwork.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNetwork.java @@ -211,6 +211,10 @@ public void terminate() { @Override public void awaitReady(@NonNull final Duration timeout) { if (ready.get() == null) { + log.info( + "Newly waiting for network '{}' to be ready in thread '{}'", + name(), + Thread.currentThread().getName()); final var deferredRun = new DeferredRun(() -> { AssertionError error = null; var retries = MAX_PORT_REASSIGNMENTS; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNode.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNode.java index 93fe96ce6a62..7806c39c7f87 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNode.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/subprocess/SubProcessNode.java @@ -32,7 +32,6 @@ import static com.hedera.services.bdd.junit.hedera.subprocess.StatusLookupAttempt.newLogAttempt; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.ERROR_REDIRECT_FILE; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.OUTPUT_DIR; -import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.recreateWorkingDir; import static com.hedera.services.bdd.spec.HapiPropertySource.asAccount; import static com.swirlds.platform.system.status.PlatformStatus.ACTIVE; import static java.util.Objects.requireNonNull; @@ -106,13 +105,6 @@ public SubProcessNode( requireNonNull(Hedera.class); } - @Override - public SubProcessNode initWorkingDir(@NonNull final String configTxt) { - recreateWorkingDir(requireNonNull(metadata.workingDir()), configTxt); - workingDirInitialized = true; - return this; - } - @Override public SubProcessNode start() { return startWithConfigVersion(LifecycleTest.CURRENT_CONFIG_VERSION.get()); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/AddressBookUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/AddressBookUtils.java index 676890449498..0d2648889b2b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/AddressBookUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/AddressBookUtils.java @@ -16,32 +16,103 @@ package com.hedera.services.bdd.junit.hedera.utils; +import static com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary.FAKE_LEDGER_ID; import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.workingDirFor; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.toMap; import static java.util.stream.StreamSupport.stream; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.protobuf.ByteString; +import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.node.app.tss.api.FakeGroupElement; +import com.hedera.node.app.tss.handlers.TssUtils; +import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.HederaNode; import com.hedera.services.bdd.junit.hedera.NodeMetadata; +import com.hedera.services.bdd.junit.hedera.TssKeyMaterial; +import com.hedera.services.bdd.junit.hedera.embedded.fakes.FakeTssLibrary; import com.hederahashgraph.api.proto.java.ServiceEndpoint; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.io.IOException; +import java.math.BigInteger; import java.nio.file.Path; import java.util.List; +import java.util.Map; +import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.IntStream; +import java.util.stream.LongStream; import java.util.stream.Stream; /** * Utility class for generating an address book configuration file. */ public class AddressBookUtils { + private static Map TEST_GOSSIP_X509_CERTS; + public static final long CLASSIC_FIRST_NODE_ACCOUNT_NUM = 3; public static final String[] CLASSIC_NODE_NAMES = new String[] {"node1", "node2", "node3", "node4", "node5", "node6", "node7", "node8"}; + // TODO - replace with real encryption keys + public static final Map CLASSIC_ENCRYPTION_KEYS = LongStream.range(0, CLASSIC_NODE_NAMES.length) + .boxed() + .collect(toMap(Function.identity(), i -> Bytes.fromHex("aa".repeat(i.intValue() + 1)))); + // TODO - make this parameterizable + public static final int CLASSIC_MAX_SHARES_PER_NODE = 3; + // TODO - generate real shares, encode message using the real encryption keys + public static final Function CLASSIC_KEY_MATERIAL_GENERATOR = roster -> { + final var directory = TssUtils.computeParticipantDirectory( + roster, + CLASSIC_MAX_SHARES_PER_NODE, + nodeId -> new BlsPublicKey( + new FakeGroupElement(new BigInteger( + CLASSIC_ENCRYPTION_KEYS.get(nodeId).toByteArray())), + TssUtils.SIGNATURE_SCHEMA)); + final var rosterHash = RosterUtils.hash(roster).getBytes(); + final var tssMessageOps = IntStream.range(0, directory.getThreshold()) + .mapToObj(i -> TssMessageTransactionBody.newBuilder() + .shareIndex(i + 1L) + .sourceRosterHash(Bytes.EMPTY) + .targetRosterHash(rosterHash) + .tssMessage(Bytes.wrap(FakeTssLibrary.validMessage(i).toBytes())) + .build()) + .toList(); + return new TssKeyMaterial(Bytes.wrap(FAKE_LEDGER_ID.toBytes()), tssMessageOps); + }; + + /** + * Returns the ASN.1 DER encoding of the X.509 certificate the platform generates for the given node id + * in test environments. + * @param nodeId the node id + * @return the ASN.1 DER encoding of the X.509 certificate + */ + @SuppressWarnings("unchecked") + public static Bytes testCertFor(final long nodeId) { + if (TEST_GOSSIP_X509_CERTS == null) { + try { + TEST_GOSSIP_X509_CERTS = ((Map) new ObjectMapper() + .readValue( + AddressBookUtils.class + .getClassLoader() + .getResourceAsStream("hapi-test-gossip-certs.json"), + Map.class)) + .entrySet().stream() + .collect(toMap(e -> Long.parseLong(e.getKey()), e -> Bytes.fromBase64(e.getValue()))); + } catch (IOException e) { + throw new IllegalStateException("Could not load gossip certs", e); + } + } + return TEST_GOSSIP_X509_CERTS.get(nodeId); + } private AddressBookUtils() { throw new UnsupportedOperationException("Utility Class"); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/WorkingDirUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/WorkingDirUtils.java index a3cc4250b098..c044b7bea2c0 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/WorkingDirUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/hedera/utils/WorkingDirUtils.java @@ -16,10 +16,23 @@ package com.hedera.services.bdd.junit.hedera.utils; +import static com.hedera.node.app.hapi.utils.CommonPbjConverters.toPbj; +import static com.hedera.node.app.info.DiskStartupNetworks.GENESIS_NETWORK_JSON; import static java.util.Objects.requireNonNull; import static java.util.Spliterators.spliteratorUnknownSize; import static java.util.stream.StreamSupport.stream; +import com.hedera.hapi.node.base.Key; +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.node.base.ServiceEndpoint; +import com.hedera.hapi.node.state.addressbook.Node; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.node.config.converter.SemanticVersionConverter; +import com.hedera.node.internal.network.Network; +import com.hedera.node.internal.network.NodeMetadata; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.hedera.services.bdd.junit.hedera.TssKeyMaterial; +import com.hedera.services.bdd.spec.HapiPropertySource; import com.hedera.services.bdd.spec.props.JutilPropertySource; import com.swirlds.platform.config.legacy.LegacyConfigPropertiesLoader; import com.swirlds.platform.crypto.CryptoStatic; @@ -36,11 +49,15 @@ import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.security.cert.X509Certificate; +import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.Random; +import java.util.function.Function; +import java.util.function.LongFunction; import java.util.stream.Stream; public class WorkingDirUtils { @@ -100,8 +117,19 @@ public static Path workingDirFor(final long nodeId, @Nullable String scope) { * * @param workingDir the path to the working directory * @param configTxt the contents of the config.txt file + * @param tssEncryptionKeyFn a function that returns the TSS encryption key for a given node ID + * @param tssKeyMaterialFn a function that returns the TSS key material for the network, if available */ - public static void recreateWorkingDir(@NonNull final Path workingDir, @NonNull final String configTxt) { + public static void recreateWorkingDir( + @NonNull final Path workingDir, + @NonNull final String configTxt, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { + requireNonNull(workingDir); + requireNonNull(configTxt); + requireNonNull(tssEncryptionKeyFn); + requireNonNull(tssKeyMaterialFn); + // Clean up any existing directory structure rm(workingDir); // Initialize the data folders @@ -110,8 +138,12 @@ public static void recreateWorkingDir(@NonNull final Path workingDir, @NonNull f // Initialize the current upgrade folder createDirectoriesUnchecked( workingDir.resolve(DATA_DIR).resolve(UPGRADE_DIR).resolve(CURRENT_DIR)); - // Write the address book (config.txt) + // Write the address book (config.txt) and genesis network (genesis-network.json) files writeStringUnchecked(workingDir.resolve(CONFIG_TXT), configTxt); + final var network = networkFrom(configTxt, tssEncryptionKeyFn, tssKeyMaterialFn); + final var networkJson = Network.JSON.toJSON(network); + writeStringUnchecked( + workingDir.resolve(DATA_DIR).resolve(CONFIG_FOLDER).resolve(GENESIS_NETWORK_JSON), networkJson); // Copy the bootstrap assets into the working directory copyBootstrapAssets(bootstrapAssetsLoc(), workingDir); // Update the log4j2.xml file with the correct output directory @@ -132,6 +164,7 @@ public static void updateUpgradeArtifactsProperty( /** * Updates the given key/value property override at the given location + * * @param propertiesPath the path to the properties file * @param overrides the key/value property overrides */ @@ -160,6 +193,19 @@ public static JutilPropertySource hapiTestStartupProperties() { return new JutilPropertySource(bootstrapAssetsLoc().resolve(APPLICATION_PROPERTIES)); } + /** + * Returns the version in the project's {@code version.txt} file. + * + * @return the version + */ + public @NonNull static SemanticVersion workingDirVersion() { + final var loc = Paths.get(System.getProperty("user.dir")).endsWith("hedera-services") + ? "version.txt" + : "../version.txt"; + final var versionLiteral = readStringUnchecked(Paths.get(loc)).trim(); + return requireNonNull(new SemanticVersionConverter().convert(versionLiteral)); + } + private static Path bootstrapAssetsLoc() { return Paths.get(System.getProperty("user.dir")).endsWith("hedera-services") ? Path.of(PROJECT_BOOTSTRAP_ASSETS_LOC) @@ -353,4 +399,49 @@ public static AddressBook loadAddressBookWithDeterministicCerts(@NonNull final P throw new RuntimeException("Error generating keys and certs", e); } } + + private static Network networkFrom( + @NonNull final String configTxt, + @NonNull final LongFunction tssEncryptionKeyFn, + @NonNull final Function, Optional> tssKeyMaterialFn) { + final var nodeMetadata = Arrays.stream(configTxt.split("\n")) + .filter(line -> line.contains("address, ")) + .map(line -> { + final var parts = line.split(", "); + final long nodeId = Long.parseLong(parts[1]); + final long weight = Long.parseLong(parts[4]); + final var gossipEndpoints = + List.of(endpointFrom(parts[5], parts[6]), endpointFrom(parts[7], parts[8])); + final var cert = AddressBookUtils.testCertFor(nodeId); + return NodeMetadata.newBuilder() + .rosterEntry(new RosterEntry(nodeId, weight, cert, gossipEndpoints)) + .node(new Node( + nodeId, + toPbj(HapiPropertySource.asAccount(parts[9])), + "node" + (nodeId + 1), + gossipEndpoints, + List.of(), + cert, + // The gRPC certificate hash is irrelevant for PR checks + Bytes.EMPTY, + weight, + false, + // TODO - Use the real admin key + Key.DEFAULT)) + .tssEncryptionKey(tssEncryptionKeyFn.apply(nodeId)) + .build(); + }) + .toList(); + final var roster = nodeMetadata.stream().map(NodeMetadata::rosterEntry).toList(); + final var tssKeyMaterial = tssKeyMaterialFn.apply(roster); + return Network.newBuilder() + .ledgerId(tssKeyMaterial.map(TssKeyMaterial::ledgerId).orElse(Bytes.EMPTY)) + .tssMessages(tssKeyMaterial.map(TssKeyMaterial::tssMessages).orElse(List.of())) + .nodeMetadata(nodeMetadata) + .build(); + } + + private static ServiceEndpoint endpointFrom(@NonNull final String hostLiteral, @NonNull final String portLiteral) { + return HapiPropertySource.asServiceEndpoint(hostLiteral + ":" + portLiteral); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/NoopSavedStateSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/NoopSavedStateSpec.java new file mode 100644 index 000000000000..b94a597e3aae --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/NoopSavedStateSpec.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.services.bdd.junit.restart; + +import static java.util.Objects.requireNonNull; + +import com.hedera.node.app.fixtures.state.FakeState; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A no-op implementation of a {@link SavedStateSpec}. + */ +public class NoopSavedStateSpec implements SavedStateSpec { + @Override + public void accept(@NonNull final FakeState state) { + requireNonNull(state); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartHapiTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartHapiTest.java new file mode 100644 index 000000000000..ba7726add487 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartHapiTest.java @@ -0,0 +1,86 @@ +/* + * 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.restart; + +import static com.hedera.services.bdd.junit.TestTags.ONLY_REPEATABLE; +import static org.junit.jupiter.api.parallel.ExecutionMode.SAME_THREAD; + +import com.hedera.node.app.fixtures.state.FakeState; +import com.hedera.services.bdd.junit.ConfigOverride; +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.extensions.NetworkTargetingExtension; +import com.hedera.services.bdd.junit.extensions.SpecNamingExtension; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.TestFactory; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.parallel.Execution; + +/** + * Annotation for a repeatable {@link HapiTest} that exercises the restart phase by creating a new embedded network + * separate from the shared network. The type of restart scenario is distinguished by the presence or absence of two + * on-disk assets: A saved state and an override network roster. + *

    + * If a saved state is present, it may be from the previous software version or the current version; and as of release + * {@code 0.57}, it may or may not have a TSS ledger id already in state. (But note that after the production deployment + * of TSS, every saved state must already have a TSS ledger id.) + *

    + * If an override network roster is present, it similarly may or may not come have a pre-generated TSS ledger id. (Even + * after all production networks have TSS enabled, it may be occasionally useful to be able to transplant just roster + * information and have the target network generate a new ledger id.) + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@TestFactory +@ExtendWith({NetworkTargetingExtension.class, SpecNamingExtension.class}) +@Execution(SAME_THREAD) +@Tag(ONLY_REPEATABLE) +public @interface RestartHapiTest { + /** + * The type of restart being tested. + */ + RestartType restartType() default RestartType.GENESIS; + + /** + * Any overrides that should be present when creating the setup state before restart. + */ + ConfigOverride[] setupOverrides() default {}; + + /** + * Any overrides that should be present at restart. + */ + ConfigOverride[] restartOverrides() default {}; + + /** + * The type of startup assets that should be present on disk when creating the setup state before restart. + */ + StartupAssets setupAssets() default StartupAssets.NONE; + + /** + * The type of startup assets that should be present on disk for the test. + */ + StartupAssets restartAssets() default StartupAssets.NONE; + + /** + * The type of saved state spec that should be used to customize the state of the {@link FakeState} when + * using {@link RestartType#SAME_VERSION} or {@link RestartType#UPGRADE_BOUNDARY}. + */ + Class savedStateSpec() default NoopSavedStateSpec.class; +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartType.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartType.java new file mode 100644 index 000000000000..dd30fb606f8a --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/RestartType.java @@ -0,0 +1,35 @@ +/* + * 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.restart; + +/** + * The types of restarts to be covered by a {@link RestartHapiTest}. + */ +public enum RestartType { + /** + * The "restart" is from genesis. + */ + GENESIS, + /** + * The restart uses the same software version as the saved state. + */ + SAME_VERSION, + /** + * The restart uses a later software version than the saved state. + */ + UPGRADE_BOUNDARY, +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/SavedStateSpec.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/SavedStateSpec.java new file mode 100644 index 000000000000..83e4892c59c3 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/SavedStateSpec.java @@ -0,0 +1,26 @@ +/* + * 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.restart; + +import com.hedera.node.app.fixtures.state.FakeState; +import java.util.function.Consumer; + +/** + * A functional interface to customize the state of a {@link FakeState} object when setting up a {@link RestartHapiTest}. + */ +@FunctionalInterface +public interface SavedStateSpec extends Consumer {} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/StartupAssets.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/StartupAssets.java new file mode 100644 index 000000000000..3ae167fc95ae --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/junit/restart/StartupAssets.java @@ -0,0 +1,40 @@ +/* + * 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.restart; + +/** + * The types of startup assets available for a {@link RestartHapiTest}. + */ +public enum StartupAssets { + /** + * No network override is present. + */ + NONE, + /** + * A network override with only the network roster is present. + */ + ROSTER_ONLY, + /** + * A network override with both the roster and the encryption keys are present. + */ + ROSTER_AND_ENCRYPTION_KEYS, + /** + * A network override with both the network roster and all TSS key material, + * including the ledger id, is present. + */ + ROSTER_AND_FULL_TSS_KEY_MATERIAL, +} 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 c87a9a52e843..7180760d486f 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 @@ -22,6 +22,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.hapi.util.HapiUtils.asInstant; import static com.hedera.hapi.util.HapiUtils.asTimestamp; +import static com.hedera.node.app.service.schedule.impl.handlers.HandlerUtility.scheduledTxnIdFrom; import static com.hedera.node.config.types.EntityType.ACCOUNT; import static com.hedera.node.config.types.EntityType.FILE; import static com.hedera.node.config.types.EntityType.NODE; @@ -68,6 +69,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Optional; import java.util.Set; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -89,6 +91,8 @@ public class BaseTranslator { private ExchangeRateSet activeRates; private final Map totalSupplies = new HashMap<>(); private final Map tokenTypes = new HashMap<>(); + private final Map scheduleRefs = new HashMap<>(); + private final Map scheduleTxnIds = new HashMap<>(); private final Set knownAssociations = new HashSet<>(); private final Map pendingAirdrops = new HashMap<>(); @@ -98,11 +102,11 @@ public class BaseTranslator { private long prevHighestKnownEntityNum = 0L; private Instant userTimestamp; - private ScheduleID scheduleRef; private final List sidecarRecords = new ArrayList<>(); private final Map numMints = new HashMap<>(); private final Map> highestPutSerialNos = new HashMap<>(); private final Map> nextCreatedNums = new EnumMap<>(EntityType.class); + private final Set purgedScheduleIds = new HashSet<>(); /** * Defines how a translator specifies details of a translated transaction record. @@ -169,7 +173,7 @@ public void prepareForUnit(@NonNull final BlockTransactionalUnit unit) { highestPutSerialNos.clear(); nextCreatedNums.clear(); sidecarRecords.clear(); - scheduleRef = null; + purgedScheduleIds.clear(); scanUnit(unit); nextCreatedNums.values().forEach(list -> { final Set distinctNums = Set.copyOf(list); @@ -192,6 +196,13 @@ public void prepareForUnit(@NonNull final BlockTransactionalUnit unit) { nextCreatedNums.values().stream().mapToLong(List::getLast).max().orElse(highestKnownEntityNum); } + /** + * Finishes the ongoing transactional unit, purging any schedules that were deleted. + */ + public void finishLastUnit() { + purgedScheduleIds.forEach(scheduleId -> scheduleRefs.remove(scheduleTxnIds.remove(scheduleId))); + } + /** * Determines if the given number was created in the ongoing transactional unit. * @@ -353,8 +364,9 @@ public SingleTransactionRecord recordFrom(@NonNull final BlockTransactionParts p final var output = parts.callContractOutputOrThrow(); recordBuilder.contractCallResult(output.contractCallResultOrThrow()); } + // If this transaction was executed by virtue of being scheduled, set its schedule ref if (parts.transactionIdOrThrow().scheduled()) { - recordBuilder.scheduleRef(scheduleRefOrThrow()); + Optional.ofNullable(scheduleRefs.get(parts.transactionIdOrThrow())).ifPresent(recordBuilder::scheduleRef); } return new SingleTransactionRecord( parts.transactionParts().wrapper(), @@ -386,22 +398,13 @@ public ExchangeRateSet activeRates() { return activeRates; } - /** - * Returns the modified schedule id for the ongoing transactional unit. - * - * @return the modified schedule id - */ - public @NonNull ScheduleID scheduleRefOrThrow() { - return requireNonNull(scheduleRef); - } - private void scanUnit(@NonNull final BlockTransactionalUnit unit) { unit.stateChanges().forEach(stateChange -> { if (stateChange.hasMapDelete()) { final var mapDelete = stateChange.mapDeleteOrThrow(); final var key = mapDelete.keyOrThrow(); if (key.hasScheduleIdKey()) { - scheduleRef = key.scheduleIdKeyOrThrow(); + purgedScheduleIds.add(key.scheduleIdKeyOrThrow()); } } else if (stateChange.hasMapUpdate()) { final var mapUpdate = stateChange.mapUpdateOrThrow(); @@ -439,7 +442,12 @@ private void scanUnit(@NonNull final BlockTransactionalUnit unit) { .computeIfAbsent(SCHEDULE, ignore -> new LinkedList<>()) .add(num); } - scheduleRef = key.scheduleIdKeyOrThrow(); + final var schedule = mapUpdate.valueOrThrow().scheduleValueOrThrow(); + final var scheduleId = key.scheduleIdKeyOrThrow(); + final var scheduledTxnId = scheduledTxnIdFrom( + schedule.originalCreateTransactionOrThrow().transactionIDOrThrow()); + scheduleRefs.put(scheduledTxnId, scheduleId); + scheduleTxnIds.put(scheduleId, scheduledTxnId); } else if (key.hasAccountIdKey()) { final var num = key.accountIdKeyOrThrow().accountNumOrThrow(); if (num > highestKnownEntityNum) { 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 ee6c45595fdd..e9d6217bc1bf 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 @@ -215,6 +215,7 @@ public List translate(@NonNull final BlockTransactional translatedRecords.add(translation); } } + baseTranslator.finishLastUnit(); return translatedRecords; } } 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 4d6b90785b71..0ffecdb538a8 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 @@ -20,6 +20,7 @@ import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; import static com.hedera.node.app.hapi.utils.CommonUtils.sha384DigestOrThrow; import static com.hedera.services.bdd.junit.hedera.ExternalPath.APPLICATION_PROPERTIES; +import static com.hedera.services.bdd.junit.hedera.ExternalPath.DATA_CONFIG_DIR; import static com.hedera.services.bdd.junit.hedera.ExternalPath.SAVED_STATES_DIR; import static com.hedera.services.bdd.junit.hedera.ExternalPath.SWIRLDS_LOG; import static com.hedera.services.bdd.junit.hedera.NodeSelector.byNodeId; @@ -28,7 +29,6 @@ import static com.hedera.services.bdd.junit.hedera.utils.WorkingDirUtils.workingDirFor; import static com.hedera.services.bdd.junit.support.validators.block.ChildHashUtils.hashesByName; import static com.hedera.services.bdd.spec.TargetNetworkType.SUBPROCESS_NETWORK; -import static com.swirlds.platform.state.GenesisStateBuilder.initGenesisPlatformState; import static java.util.Objects.requireNonNull; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -47,47 +47,29 @@ import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.primitives.ProtoLong; import com.hedera.hapi.node.state.primitives.ProtoString; -import com.hedera.node.app.Hedera; +import com.hedera.node.app.ServicesMain; import com.hedera.node.app.blocks.BlockStreamManager; import com.hedera.node.app.blocks.StreamingTreeHasher; import com.hedera.node.app.blocks.impl.NaiveStreamingTreeHasher; import com.hedera.node.app.config.BootstrapConfigProviderImpl; import com.hedera.node.app.info.DiskStartupNetworks; -import com.hedera.node.app.services.OrderedServiceMigrator; -import com.hedera.node.app.services.ServicesRegistryImpl; -import com.hedera.node.app.tss.TssBaseServiceImpl; -import com.hedera.node.app.tss.TssLibraryImpl; -import com.hedera.node.app.version.ServicesSoftwareVersion; -import com.hedera.node.config.converter.BytesConverter; -import com.hedera.node.config.data.HederaConfig; import com.hedera.node.config.data.VersionConfig; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.hedera.services.bdd.junit.hedera.subprocess.SubProcessNetwork; import com.hedera.services.bdd.junit.support.BlockStreamAccess; import com.hedera.services.bdd.junit.support.BlockStreamValidator; import com.hedera.services.bdd.spec.HapiSpec; -import com.swirlds.common.config.StateCommonConfig; -import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.crypto.Hash; -import com.swirlds.common.crypto.config.CryptoConfig; -import com.swirlds.common.io.config.TemporaryFileConfig; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.merkle.crypto.MerkleCryptography; import com.swirlds.common.merkle.utility.MerkleTreeVisualizer; -import com.swirlds.common.metrics.config.MetricsConfig; import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.common.platform.NodeId; -import com.swirlds.config.api.Configuration; -import com.swirlds.config.api.ConfigurationBuilder; -import com.swirlds.merkledb.config.MerkleDbConfig; -import com.swirlds.platform.config.BasicConfig; -import com.swirlds.platform.config.TransactionConfig; import com.swirlds.platform.state.PlatformMerkleStateRoot; import com.swirlds.platform.system.InitTrigger; import com.swirlds.state.lifecycle.Service; import com.swirlds.state.merkle.MerkleStateRoot; import com.swirlds.state.spi.CommittableWritableStates; -import com.swirlds.virtualmap.config.VirtualMapConfig; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; @@ -96,13 +78,12 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.time.InstantSource; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SplittableRandom; import java.util.TreeMap; -import java.util.concurrent.ForkJoinPool; import java.util.regex.Pattern; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -114,20 +95,26 @@ */ public class StateChangesValidator implements BlockStreamValidator { private static final Logger logger = LogManager.getLogger(StateChangesValidator.class); + private static final SplittableRandom RANDOM = new SplittableRandom(System.currentTimeMillis()); private static final MerkleCryptography CRYPTO = MerkleCryptoFactory.getInstance(); private static final int HASH_SIZE = 48; private static final int VISUALIZATION_HASH_DEPTH = 5; + /** + * The probability that the validator will verify an intermediate block proof; we always verify the first and last. + */ + private static final double PROOF_VERIFICATION_PROB = 0.05; + private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+"); private static final Pattern CHILD_STATE_PATTERN = Pattern.compile("\\s+\\d+ \\w+\\s+(\\S+)\\s+.+\\s+(.+)"); + private final Hash genesisStateHash; private final Path pathToNode0SwirldsLog; private final Bytes expectedRootHash; private final Set servicesWritten = new HashSet<>(); private final StateChangesSummary stateChangesSummary = new StateChangesSummary(new TreeMap<>()); private PlatformMerkleStateRoot state; - private Hash genesisStateHash; public static void main(String[] args) { final var node0Dir = Paths.get("hedera-node/test-clients") @@ -136,11 +123,11 @@ public static void main(String[] args) { .normalize(); final var validator = new StateChangesValidator( Bytes.fromHex( - "65374e72c2572aaaca17fe3a0e879841c0f5ae919348fc18231f8167bd28e326438c6f93a07a45eda7888b69e9812c4d"), + "912d5cf1478f1585f0d23ff8c7ecb05860b8a6c8c1f1d1ffe91d0fa45b642a98d54487d41f5966721a613ca646b28652"), node0Dir.resolve("output/swirlds.log"), node0Dir.resolve("config.txt"), node0Dir.resolve("data/config/application.properties"), - Bytes.fromHex("03")); + node0Dir.resolve("data/config")); final var blocks = BlockStreamAccess.BLOCK_STREAM_ACCESS.readBlocks(node0Dir.resolve("data/blockStreams/block-0.0.3")); validator.validateBlocks(blocks); @@ -189,8 +176,7 @@ public static StateChangesValidator newValidatorFor(@NonNull final HapiSpec spec node0.getExternalPath(SWIRLDS_LOG), genesisConfigTxt, node0.getExternalPath(APPLICATION_PROPERTIES), - requireNonNull(new BytesConverter() - .convert(spec.startupProperties().get("ledger.id")))); + node0.getExternalPath(DATA_CONFIG_DIR)); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -201,50 +187,32 @@ public StateChangesValidator( @NonNull final Path pathToNode0SwirldsLog, @NonNull final Path pathToAddressBook, @NonNull final Path pathToOverrideProperties, - @NonNull final Bytes ledgerId) { + @NonNull final Path pathToUpgradeSysFilesLoc) { this.expectedRootHash = requireNonNull(expectedRootHash); this.pathToNode0SwirldsLog = requireNonNull(pathToNode0SwirldsLog); - // Ensure the bootstrap config sees our blockStream.streamMode=BOTH override - // and registers the BlockStreamService schemas System.setProperty( "hedera.app.properties.path", pathToOverrideProperties.toAbsolutePath().toString()); + System.setProperty( + "networkAdmin.upgradeSysFilesLoc", + pathToUpgradeSysFilesLoc.toAbsolutePath().toString()); + unarchiveGenesisNetworkJson(pathToUpgradeSysFilesLoc); final var bootstrapConfig = new BootstrapConfigProviderImpl().getConfiguration(); final var versionConfig = bootstrapConfig.getConfigData(VersionConfig.class); final var servicesVersion = versionConfig.servicesVersion(); final var addressBook = loadAddressBookWithDeterministicCerts(pathToAddressBook); - final var configVersion = - bootstrapConfig.getConfigData(HederaConfig.class).configVersion(); - final var currentVersion = new ServicesSoftwareVersion(servicesVersion, configVersion); final var metrics = new NoOpMetrics(); - final var hedera = new Hedera( - ConstructableRegistry.getInstance(), - ServicesRegistryImpl::new, - new OrderedServiceMigrator(), - InstantSource.system(), - appContext -> new TssBaseServiceImpl( - appContext, - ForkJoinPool.commonPool(), - ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), - ForkJoinPool.commonPool(), - metrics), - DiskStartupNetworks::new, - NodeId.of(0L)); + final var hedera = ServicesMain.newHedera(NodeId.of(0L), metrics); this.state = (PlatformMerkleStateRoot) hedera.newMerkleStateRoot(); - final Configuration platformConfig = ConfigurationBuilder.create() - .withConfigDataType(MetricsConfig.class) - .withConfigDataType(TransactionConfig.class) - .withConfigDataType(CryptoConfig.class) - .withConfigDataType(BasicConfig.class) - .withConfigDataType(VirtualMapConfig.class) - .withConfigDataType(MerkleDbConfig.class) - .withConfigDataType(TemporaryFileConfig.class) - .withConfigDataType(StateCommonConfig.class) - .build(); - hedera.initializeStatesApi(state, metrics, InitTrigger.GENESIS, addressBook, platformConfig); - initGenesisPlatformState(platformConfig, this.state.getWritablePlatformState(), addressBook, currentVersion); + final var platformConfig = ServicesMain.buildPlatformConfig(); + hedera.initializeStatesApi( + state, + metrics, + InitTrigger.GENESIS, + DiskStartupNetworks.fromLegacyAddressBook(addressBook), + platformConfig, + addressBook); final var stateToBeCopied = state; state = state.copy(); // get the state hash before applying the state changes from current block @@ -259,9 +227,11 @@ public void validateBlocks(@NonNull final List blocks) { var previousBlockHash = BlockStreamManager.ZERO_BLOCK_HASH; var startOfStateHash = requireNonNull(genesisStateHash).getBytes(); - for (int i = 0; i < blocks.size(); i++) { + final int n = blocks.size(); + for (int i = 0; i < n; i++) { final var block = blocks.get(i); - if (i != 0) { + final var shouldVerifyProof = i == 0 || i == n - 1 || RANDOM.nextDouble() < PROOF_VERIFICATION_PROB; + if (i != 0 && shouldVerifyProof) { final var stateToBeCopied = state; this.state = stateToBeCopied.copy(); startOfStateHash = CRYPTO.digestTreeSync(stateToBeCopied).getBytes(); @@ -270,7 +240,9 @@ public void validateBlocks(@NonNull final List blocks) { final StreamingTreeHasher outputTreeHasher = new NaiveStreamingTreeHasher(); for (final var item : block.items()) { servicesWritten.clear(); - hashInputOutputTree(item, inputTreeHasher, outputTreeHasher); + if (shouldVerifyProof) { + hashInputOutputTree(item, inputTreeHasher, outputTreeHasher); + } if (item.hasStateChanges()) { applyStateChanges(item.stateChangesOrThrow()); } @@ -284,10 +256,20 @@ public void validateBlocks(@NonNull final List blocks) { blockProof.previousBlockRootHash(), "Previous block hash mismatch for block " + blockProof.block()); - final var expectedBlockHash = - computeBlockHash(startOfStateHash, previousBlockHash, inputTreeHasher, outputTreeHasher); - validateBlockProof(blockProof, expectedBlockHash); - previousBlockHash = expectedBlockHash; + if (shouldVerifyProof) { + final var expectedBlockHash = + computeBlockHash(startOfStateHash, previousBlockHash, inputTreeHasher, outputTreeHasher); + validateBlockProof(blockProof, expectedBlockHash); + previousBlockHash = expectedBlockHash; + } else { + previousBlockHash = i < n - 1 + ? blocks.get(i + 1) + .items() + .getFirst() + .blockHeaderOrThrow() + .previousBlockHash() + : Bytes.EMPTY; + } } logger.info("Summary of changes by service:\n{}", stateChangesSummary); CRYPTO.digestTreeSync(state); @@ -411,6 +393,28 @@ private void applyStateChanges(@NonNull final StateChanges stateChanges) { } } + /** + * If the given path does not contain the genesis network JSON, recovers it from the archive directory. + * @param path the path to the network directory + * @throws IllegalStateException if the genesis network JSON cannot be found + * @throws UncheckedIOException if an I/O error occurs + */ + private void unarchiveGenesisNetworkJson(@NonNull final Path path) { + final var desiredPath = path.resolve(DiskStartupNetworks.GENESIS_NETWORK_JSON); + if (!desiredPath.toFile().exists()) { + final var archivedPath = + path.resolve(DiskStartupNetworks.ARCHIVE).resolve(DiskStartupNetworks.GENESIS_NETWORK_JSON); + if (!archivedPath.toFile().exists()) { + throw new IllegalStateException("No archived genesis network JSON found at " + archivedPath); + } + try { + Files.move(archivedPath, desiredPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + private record ServiceChangesSummary( Map singletonPuts, Map mapUpdates, @@ -591,7 +595,6 @@ private static Object singletonPutFor(@NonNull final SingletonUpdateChange singl case BLOCK_STREAM_INFO_VALUE -> singletonUpdateChange.blockStreamInfoValueOrThrow(); case PLATFORM_STATE_VALUE -> singletonUpdateChange.platformStateValueOrThrow(); case ROSTER_STATE_VALUE -> singletonUpdateChange.rosterStateValueOrThrow(); - case TSS_STATUS_STATE_VALUE -> singletonUpdateChange.tssStatusStateValueOrThrow(); }; } @@ -649,7 +652,7 @@ private static Object mapValueFor(@NonNull final MapChangeValue mapChangeValue) case ROSTER_VALUE -> mapChangeValue.rosterValueOrThrow(); case SCHEDULED_COUNTS_VALUE -> mapChangeValue.scheduledCountsValueOrThrow(); case THROTTLE_USAGE_SNAPSHOTS_VALUE -> mapChangeValue.throttleUsageSnapshotsValue(); - case TSS_ENCRYPTION_KEY_VALUE -> mapChangeValue.tssEncryptionKeyValueOrThrow(); + case TSS_ENCRYPTION_KEYS_VALUE -> mapChangeValue.tssEncryptionKeysValue(); case TSS_MESSAGE_VALUE -> mapChangeValue.tssMessageValueOrThrow(); case TSS_VOTE_VALUE -> mapChangeValue.tssVoteValueOrThrow(); }; 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 f32daa2a258a..d6d7b09df00f 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 @@ -16,6 +16,7 @@ package com.hedera.services.bdd.spec; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static com.hedera.node.app.service.addressbook.impl.schemas.V053AddressBookSchema.NODES_KEY; import static com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema.ACCOUNTS_KEY; import static com.hedera.node.app.service.token.impl.schemas.V0490TokenSchema.TOKENS_KEY; @@ -47,7 +48,6 @@ import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_CONTRACT_SENDER; import static com.hedera.services.bdd.suites.HapiSuite.ETH_SUFFIX; import static com.hedera.services.bdd.suites.HapiSuite.SECP_256K1_SOURCE_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static java.util.Collections.emptyList; import static java.util.Objects.requireNonNull; import static java.util.concurrent.CompletableFuture.allOf; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java index 9fdbcc7ccf2b..c81e2633a9f2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/tss/RekeyScenarioOp.java @@ -54,6 +54,7 @@ import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.node.state.tss.TssMessageMapKey; import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.node.transaction.SignedTransaction; @@ -415,13 +416,19 @@ private Stream simulateOtherNodeTssMessages() { private SpecOperation extractRosterMetadata() { return doingContextual(spec -> { final var state = spec.embeddedStateOrThrow(); - final var hedera = spec.repeatableEmbeddedHederaOrThrow(); - final var roundNo = hedera.lastRoundNo(); - + final var embeddedHedera = spec.repeatableEmbeddedHederaOrThrow(); + final var roundNo = embeddedHedera.lastRoundNo(); + final var hedera = embeddedHedera.hedera(); final var writableStates = state.getWritableStates(RosterService.NAME); final var rosterStore = new WritableRosterStore(writableStates); - final var activeEntries = - new ArrayList<>(rosterStore.getActiveRoster().rosterEntries()); + + if (!hedera.isRosterLifecycleEnabled() && rosterStore.getActiveRoster() == null) { + rosterStore.putActiveRoster(embeddedHedera.roster(), 0); + ((CommittableWritableStates) writableStates).commit(); + } + + final List activeEntries = new ArrayList<>( + requireNonNull(rosterStore.getActiveRoster()).rosterEntries()); activeEntries.set( activeEntries.size() - 1, activeEntries.getLast().copyBuilder().weight(0).build()); @@ -439,7 +446,8 @@ private SpecOperation extractRosterMetadata() { .forEach((nodeId, numShares) -> activeShares.put(nodeId, numShares.intValue())); expectedMessages = activeShares.get(0L); // Prepare the FakeTssLibrary to decrypt the private shares of the embedded node - hedera.tssBaseService() + embeddedHedera + .tssBaseService() .fakeTssLibrary() .setupDecryption( directory -> {}, diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/LongTermScheduleUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/LongTermScheduleUtils.java index 618287392b04..a5d8f3e37eeb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/LongTermScheduleUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/LongTermScheduleUtils.java @@ -151,6 +151,6 @@ public static SpecOperation[] triggerSchedule(String schedule, long waitForSecon } public static SpecOperation[] triggerSchedule(String schedule) { - return triggerSchedule(schedule, 5); + return triggerSchedule(schedule, 6); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip869/NodeCreateTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip869/NodeCreateTest.java index 7b5918a50612..42a7276baf35 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip869/NodeCreateTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip869/NodeCreateTest.java @@ -63,13 +63,11 @@ import com.hedera.services.bdd.junit.HapiTestLifecycle; import com.hedera.services.bdd.junit.LeakyEmbeddedHapiTest; import com.hedera.services.bdd.junit.LeakyHapiTest; -import com.hedera.services.bdd.junit.support.TestLifecycle; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hederahashgraph.api.proto.java.AccountID; import com.hederahashgraph.api.proto.java.ServiceEndpoint; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; -import edu.umd.cs.findbugs.annotations.NonNull; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; @@ -100,7 +98,7 @@ public class NodeCreateTest { private static List gossipCertificates; @BeforeAll - static void beforeAll(@NonNull final TestLifecycle testLifecycle) { + static void beforeAll() { gossipCertificates = generateX509Certificates(2); } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/ConcurrentIntegrationTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/ConcurrentIntegrationTests.java index 2c5e450ab7aa..aeac2ed037e7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/ConcurrentIntegrationTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/ConcurrentIntegrationTests.java @@ -19,6 +19,8 @@ import static com.hedera.hapi.node.base.HederaFunctionality.NODE_STAKE_UPDATE; import static com.hedera.hapi.node.base.ResponseCodeEnum.BUSY; import static com.hedera.hapi.node.base.ResponseCodeEnum.FAIL_INVALID; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_KEY; +import static com.hedera.node.app.roster.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static com.hedera.services.bdd.junit.EmbeddedReason.MANIPULATES_EVENT_VERSION; import static com.hedera.services.bdd.junit.SharedNetworkLauncherSessionListener.CLASSIC_HAPI_TEST_NETWORK_SIZE; import static com.hedera.services.bdd.junit.TestTags.INTEGRATION; @@ -64,8 +66,6 @@ import static com.hedera.services.bdd.suites.hip869.NodeCreateTest.generateX509Certificates; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.RECORD_NOT_FOUND; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_NOT_ASSOCIATED_TO_ACCOUNT; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_KEY; -import static com.swirlds.platform.state.service.schemas.V0540RosterSchema.ROSTER_STATES_KEY; import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.hapi.block.stream.BlockItem; @@ -76,7 +76,7 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterState; import com.hedera.node.app.roster.RosterService; -import com.hedera.services.bdd.junit.BootstrapOverride; +import com.hedera.services.bdd.junit.ConfigOverride; import com.hedera.services.bdd.junit.EmbeddedHapiTest; import com.hedera.services.bdd.junit.GenesisHapiTest; import com.hedera.services.bdd.junit.HapiTest; @@ -209,7 +209,7 @@ final Stream failInvalidDuringDispatchRechargesFees() { Optional.ofNullable(amount == ONE_HUNDRED_HBARS ? "Fee was not recharged" : null))); } - @GenesisHapiTest(bootstrapOverrides = {@BootstrapOverride(key = "addressBook.useRosterLifecycle", value = "true")}) + @GenesisHapiTest(bootstrapOverrides = {@ConfigOverride(key = "addressBook.useRosterLifecycle", value = "true")}) @DisplayName("freeze upgrade with roster lifecycle sets candidate roster") final Stream freezeUpgradeWithRosterLifecycleSetsCandidateRoster() throws CertificateEncodingException { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableHip423Tests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableHip423Tests.java index 3f8f38e43d42..08aa8d8bf0df 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableHip423Tests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableHip423Tests.java @@ -203,16 +203,13 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.TestMethodOrder; @Order(3) @Tag(INTEGRATION) @HapiTestLifecycle @TargetEmbeddedMode(REPEATABLE) -@TestMethodOrder(OrderAnnotation.class) public class RepeatableHip423Tests { private static final long ONE_MINUTE = 60; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableRosterLifecycleRestartTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableRosterLifecycleRestartTests.java new file mode 100644 index 000000000000..e92ee121038c --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/integration/RepeatableRosterLifecycleRestartTests.java @@ -0,0 +1,44 @@ +/* + * 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.integration; + +import static com.hedera.services.bdd.junit.TestTags.INTEGRATION; +import static com.hedera.services.bdd.junit.hedera.embedded.EmbeddedMode.REPEATABLE; +import static com.hedera.services.bdd.junit.restart.RestartType.GENESIS; +import static com.hedera.services.bdd.junit.restart.StartupAssets.ROSTER_ONLY; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; + +import com.hedera.services.bdd.junit.ConfigOverride; +import com.hedera.services.bdd.junit.TargetEmbeddedMode; +import com.hedera.services.bdd.junit.restart.RestartHapiTest; +import java.util.stream.Stream; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Tag; + +@Order(3) +@Tag(INTEGRATION) +@TargetEmbeddedMode(REPEATABLE) +public class RepeatableRosterLifecycleRestartTests { + @RestartHapiTest( + restartType = GENESIS, + restartOverrides = {@ConfigOverride(key = "addressBook.useRosterLifecycle", value = "true")}, + restartAssets = ROSTER_ONLY) + Stream genesisMigrationReflectsInitialRoster() { + return hapiTest(); + } +} diff --git a/hedera-node/test-clients/src/main/java/module-info.java b/hedera-node/test-clients/src/main/java/module-info.java index 003d08fb50f0..9a86ac44db27 100644 --- a/hedera-node/test-clients/src/main/java/module-info.java +++ b/hedera-node/test-clients/src/main/java/module-info.java @@ -62,6 +62,7 @@ exports com.hedera.services.bdd.junit.support.validators.utils; exports com.hedera.services.bdd.junit.support.validators.block; exports com.hedera.services.bdd.utils; + exports com.hedera.services.bdd.junit.restart; requires transitive com.hedera.node.app.hapi.fees; requires transitive com.hedera.node.app.hapi.utils; @@ -98,7 +99,6 @@ requires com.hedera.node.app.service.token.impl; requires com.hedera.node.app.service.token; requires com.swirlds.base.test.fixtures; - requires com.swirlds.config.extensions; requires com.swirlds.merkledb; requires com.swirlds.platform.core.test.fixtures; requires com.swirlds.state.impl; diff --git a/hedera-node/test-clients/src/main/resources/hapi-test-gossip-certs.json b/hedera-node/test-clients/src/main/resources/hapi-test-gossip-certs.json new file mode 100644 index 000000000000..15a3691cd251 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/hapi-test-gossip-certs.json @@ -0,0 +1,7 @@ +{ + "0" : "MIIDpzCCAg+gAwIBAgIJAK05TS8KZeb1MA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTEwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTEwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDBoP9dI3K1PRLRK7h90D9eNCfgzuHTyJi70yDEs90XJXlE6jmgf1NE2av83VAhQHLxu8Ehc/55M9Ayx9IQc0zJLSS+IrRM9QwqoG8ZvNdRgNw+je3V/8rAK/mHId+cPnnyDplCyskyi5kWCv6kTULIewFH8/KVZwhe0/hB2+N6ujWixURrxjjGLHA6b2gPoGAb/nxiVOn+L0cWcOzcyiYShxagj0FBWV7AxKx65Ynzfe7eF0gOzBUA+IM10OM5KXJejk53Xz5KpEyGe8htO/bXFlpLdm3UzrYiIhY0oKPYKECAC1s+VAZA6i+MV0nDpqDgxHRRXD8O2arauPhEI6iVT9f05AtzElrs7U95HbpQUuP1sxkaQw+bLdMOQHHMVCgMgw2g0eDdVDAMJD7wjZ+Bs6kDc/EJELb0l1uy2GEnOZMiHkK4K1r4IyZ/ed6QpyIRKfBCNyT5IIpMoVpzRYxVXgjgFdudd8iErKyvSXHThU6nu92c+vSd+FLBFHPpb6ECAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAdga5NYtV48uDCd4vIsmpGWpKuUHtDVDlCvzHc2ij8DxAR6OFp+hIRNEBXkzg1KS5qP8Wba5ptmGoV4f89HemP+AL3Azde+HjpYRtffdfTdQwmMbw7xJg2lKkEo11gDo5+zPZnVbfb3FsZ+IXKji0QshQBfg+ddTkFG3TJG1ttq3ZDw94RxFQivVnkj1p+Ogel/DuBNRWQobFVe5VrmJqbuwwN8AdrPae1dMrkZatF91On5+cpVLGfk96fYUhDohDt6KKQ6DdhvFk5rhd0vsHGMQq2gAW2+Or6ZVsKkHKx8CPINpJVKAdpE0tItI+loMO02jf9oRI/8cThWP1vNAeWnr0D6m275EZf/4qem/DdJ0FJIVou3P7tsq7eSdueDnj5RmcbW/vOBtvlXpD3SqsVRn6sltZ0sk24p+6ZMzopevCZEMf/nL3OzGvSadisXb39H9DgwkNLlefju1QLgHWf0TGfeNHluDgVDhU8+/1/KUGtr2SnZ5EVO1l59FWHALj", + "1" : "MIIDpjCCAg6gAwIBAgIIHWg7e2Q/smQwDQYJKoZIhvcNAQEMBQAwEjEQMA4GA1UEAxMHcy1ub2RlMjAgFw0wMDAxMDEwMDAwMDBaGA8yMTAwMDEwMTAwMDAwMFowEjEQMA4GA1UEAxMHcy1ub2RlMjCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAKr5WsBepS3+y/0/yfBjzMWje7zianEz7sszrNWV3cGu2KUlR7v2+9wp/EtX1+BdcGlTTojgFs5nEBN4lM76Cp6JjFH461yN8GSkIkpe8GZnb1w4KEjZj5UYMbq+qOUI6QmwmgLeO8RHAsS6lCP1AyGFalb2ZVJ09DcYDxCRXeFj4BqvNbtD5r5DTCtpVT4ax3eb3pzNSGsjQUG9zhyp/WcsAmwmzKdMl72tk6qF8tlAWXyzwiCujWHS0Kln0C5pyEjeFNsG299toC4pgT8juxijgseTeIFRnNHmGSeSmXpAkEELlwLKR8HOnqeiS5UXNqdbxNemx/EpJSc5rTB6kzLX24dIuRsgyIIFWx73goOzmaHUolN4xmenifoMYlSNNM07WrsvmjRC5OLc/uGhdWqhZGBCH6AJB8Cmw84QLXVdHE6LiueP1oMd7g++N4X880wJkuh0ebfV3i7etUIn0jLlM50AkRucG9kwZDJ/M4LY7FT2F85R1/o2FaB/537ARQIDAQABMA0GCSqGSIb3DQEBDAUAA4IBgQB5lTkqYw0hEW+BJTFsQ8jEHfIDNRJ0kNbVuibfP+u7kzlJy15lCEi+Qw6E3d8hA1QBX3xJMxNBlrtYPrdG26hh/tOwo5Np/OfxQC5jo0Q7n7hu7aLxZRUB/q7AfdDbOun4Za6rJhT3+EsFocyARWp8bYSk3YILBMkP+2VYDRkgQidzKgKtO5yv21Y9sEgziSprc+dQb/tqn5aQZLWavFwCLwnB3t4r4qwLHkkH00Jw51uOvLeM49/t333V5Caa7wmWzMcE+KSWW0QWFRxeJrodSyjPdmDi4D8lKN5WJHSAU5L2yWIODUyWD/cvsAapTv7xXk9ja/Ssb9DpMQnM1xh0hYaESajNeL1QbGuZgPxAwrw981h7kprR2P2iMGRVGA6u4ezxmhW3s7D+yJ3+Yxs/x2J/sw65Z16mRYXRWYWHQmhgaVQjIviiAkVB6CWZo1kHl/eYaVedQzKlrTpbr3JtmwGwhYEOnrkzsC63h8/AG9gRtIAIGWGqTPWbn2pEm8M=", + "2" : "MIIDpzCCAg+gAwIBAgIJAJg3GRFp5bT9MA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTMwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTMwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCl5ut2dCleDmgEneRYpAKa9Pe2qnXzgF+BEIuTfizG2OcPQi/ltv+6HxSrJXtuWNaiX/G4iP7iBzWj2ysaAYwfYj0ezTSMLRqM9hXzVgLtW0LJEF6a8vUXPsJt4GEJkUKiYCCO1MP1NLd3y/3SVJrFhwJSPqKYm2pQNg84WfPDWSkzSneOIO4Z0uWDXgs+vzSNyChWOxVieFQhLjcELtyj6narmLox+Jdo/SxUzPuktuFB3ebNgUqWPkjljgZpl00BTmbRIVHgHfDVulo2PBpXd0VplIDgdPr5zMKdTrKCuDKey8Mft72RkPKMe9LZVZ/21+rXVEh+olvvUCySsP2RkWPUJJD90c8wKo01rZsjAOXscJKQcBYlam5XXO4ZBRYzEdxuivbkPwsOoQ83swCR3alPvwfbg11Va+zXE6sRbUM9LqkYo/M3Hwg8tSIXu8oah6csputanz867dzWwyVJEPzmiXZ6ncVDQO31QlB7RndWCqKTjOQpnpblUMsrE9MCAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAnUA8+kz7L+eSOm/iVvUNYF10PKO2nZtxWWL7R1vwK/2Up765PwqxKb0eSEM4bjgvZq1GuGXs9X/Y7dos42yntXvgeUY+/2JzCnw4J5tzxytZ+IKX6DR67NjDzDzVZQfptjLQrb8E7yzml0uxsqrhNPWl57Bmfe66Kg2lD11jImeeEhExlRggFukoiUWVwRNU21Q1jMUWrg2ZwfP+6fFTgRt0WR+X5zkyYPbvI6/yv7reYGjPDuZTOFhbwG8LUTQxdttDswPjnQ606kMyninL+aNelSdV/UIII7lpr/dTvgQAnrlBaGXvdy6brh3wWEwia0FZFZcKEs6M+jZ3MrFxvlTfUIdI3jRq12L10cCDi2VhORg4JmvlM+Tk6kJeSku30ZLAVo3S7GbTdvkuesOxz3UwnF7yfOA1KYOPvhv1oLxGV5z05glsn1OBKnXMdzsKFbAYYHj81bgBni2WLuIpv3oXlai2uc4y9m8LvWAQ+h/ivyog34Ai3Pvr5ZZOFgjy", + "3" : "MIIDpzCCAg+gAwIBAgIJAN7hww13zBZEMA0GCSqGSIb3DQEBDAUAMBIxEDAOBgNVBAMTB3Mtbm9kZTQwIBcNMDAwMTAxMDAwMDAwWhgPMjEwMDAxMDEwMDAwMDBaMBIxEDAOBgNVBAMTB3Mtbm9kZTQwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDK/bVyv0ZUeJZ4cIOImM+wmqtYjCw4jPAC549WQPPV1vG0lzSpgV+nRKqmWBexhLlKN3bsvrfNCUpKSq8meFyCtdppT1dhUOmEZcoNhLZzqxXb2HYYqRPv82tR+tbh+27WFsBOOqYrYTvr72ECD7qDOuw/Xob6KImaw/b/SIAPecMoYy25fkgYkJSETwd8HUpwssYH/JTLBF8eGjjTTMuu14ARQKeH8BXSs+jjV1+3IItXERS8ryUGDjqc5vC8ZW1kDVQbb91IDxRjqZbFyhuasocCqTAcZuiEgE8Wilwp2g1vbAUnHnvKNfiaEAHoEV6vF4lelaWhOnN2U5tnox/ns6PiDqIbOfs0pmXxjAK0vxc6oZM3TwdRtzo6cSb/AYfQdnmQzkra980kHN12r3f7PK2PzGBuVUPT7fLGA4S3vQDYO4rqcgTc/OLobtqLtdBusOFjZscfIfUW4GVWJUI1j+fwvHacxWLmyZwlQ5Q47UtrtjWpFru7CTn5S477lqMCAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAdW6AWDhT0eOJw+0O6MYngmCgkXfFsgBC/B1plaE596hHo58FHxzCNiLFdvfRj37rxujvqsDAkADWUmOzLzLHYMXu302HzDqAMNY6FZJc32y4ZDsIQpaUOAuiNHAHwFXuPRInVpCqztfJMgw4RhOhcCTEsoIJsqoIN1t4M0pEVAv6x3nJwFKZqSNOZrQ7sOW32FjwWS3kHwRsCTtqdk5n2KxU6wr/fggV3QsSPRMYro8sUfwu93mqggtswwWqfeKlsz5WiaR9aqLnb8z1R6HLvA0bcoPWzjgn8RdP+9we4z06iZ5vdBuNpwBjrCKUELWISyAoekLGGxyS8pPqYiSBRNUoaPITSuUjcCBbJ9EFvm72QgCBesbwF71KPabTPbMPhLmf+uAi+zmeu8ZeVvT6DrX9OHSkIvIEQFry9BrqOT3ce6KBHSO1HpXIetj5Wcd3WHXtz9ulBL9ikWC8eh7/+we51ucmLvFzNKznElhT2Dp+czXUVNEUjp3u/66pyRA4", + "4" : "MIIDnjCCAgagAwIBAgIIcjC6xOVm1mcwDQYJKoZIhvcNAQEMBQAwDjEMMAoGA1UEAxMDcy0wMCAXDTAwMDEwMTAwMDAwMFoYDzIxMDAwMTAxMDAwMDAwWjAOMQwwCgYDVQQDEwNzLTAwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCUXyWFpLmVcLYpB7IUaO4BpFNuIszXcyukusZAsbaiu5AQ6vuqSfxSbDTxUmn6IPcCP3fJUUmuGwITl/GDCalNfc6TF657ah3cBKWAyLUpnsmmTbCQ0QHUd2bjWEjm0XGb2cTLOYm8GlxCZFyFUEP8W/4orwZTlrUqi93ob5u3ZBY+orybEMN8zzkjLGrhc5d/lW7gFtHqpPC+RVFK9Nto34heYqZOYuWqJ/t/Aci5cO3cWsPNEdRSPdd5DGtpjlNVW86IA9xsMrd2dO8Civs9OF7I7K6UWWCqM+fKBHGr5Dsc5yy/mmUPo9e9afvSR3dDGDP7mInEyBlX5pOBSR/y1hn946uk0j51QWx2vjdhml2qZ6un8R92fCsBADVVkL6wFIZTsxQUgOEmkFOZmei+ikApNhh19gvJg5akl67Vnasp7xQOzZz1JweP7u18GFOcvLCXYPSXX9PSjPvZ76VGq0XfifzRYvKSBXwkz2poFFA8UllqAECdEfiZ19R3O+UCAwEAATANBgkqhkiG9w0BAQwFAAOCAYEAWrgsDVe06Y0hqL8i73JF0Q7aNJaTqaotWpTgH7eEBe3nqFTAJK3eLzhOHPSRiqk2iN1Vx8Y7iQ6XvIXJadIawPQgCxjPGsWN2EhJKq77agFtHiHnfo8RdTK09B/IF4OhNOiJ+vCvpRofXMVnagtQ0tcKrHETI9h6QHOAwK7ZhTI7bs5lDElFx8UMHBbZeSkyRCHleV+3VP+HT+1z9waHa+D07EwOoECG/BP1HXw4aZgZQPQlb+gGinZHv+Ou96+4DckCdcS32ihY/9XbbKHyn8KtrEsMD8Jk0NFRPT/IJToAWv4RrRDA4ie7AEnn1lOA7rXhiQojapldfmuDWRyXwYf+Igd0H79apJ7H7TchYXkUptajxLysobPNSY/kKuH/kqYCeXRvPVD3XcRbiUfFn3B3tnBU0hNsJzYIUA20iyT/iEjH9DFNm4bLqwyM9FD1LV/PJqnxoLm66Q38QYQxX16uXC7Rwmy96BI1b/rLIrXF9ZhPpVBqdBfGGRJdMemL" +} \ No newline at end of file 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 7764a915fff9..26c157b7d23a 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 @@ -20,6 +20,8 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.node.state.roster.RoundRosterPair; +import com.hedera.node.internal.network.Network; +import com.hedera.node.internal.network.NodeMetadata; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.CryptographyException; import com.swirlds.common.crypto.Hash; @@ -338,4 +340,15 @@ public static AddressBook buildAddressBook(@NonNull final Roster roster) { return addressBook; } + + /** + * Build a Roster object out of a given {@link Network} address book. + * @param network a network + * @return a Roster + */ + public static @NonNull Roster rosterFrom(@NonNull final Network network) { + return new Roster(network.nodeMetadata().stream() + .map(NodeMetadata::rosterEntryOrThrow) + .toList()); + } } 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 deleted file mode 100644 index da8f2a9b68b9..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/GenesisStateBuilder.java +++ /dev/null @@ -1,85 +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.swirlds.platform.state; - -import com.swirlds.config.api.Configuration; -import com.swirlds.platform.config.AddressBookConfig; -import com.swirlds.platform.config.BasicConfig; -import com.swirlds.platform.crypto.CryptoStatic; -import com.swirlds.platform.state.signed.ReservedSignedState; -import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.address.AddressBook; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; - -/** - * Responsible for building the genesis state. - */ -public final class GenesisStateBuilder { - - private GenesisStateBuilder() {} - - /** - * Initializes a genesis platform state. - * - */ - public static void initGenesisPlatformState( - final Configuration configuration, - final PlatformStateModifier platformState, - final AddressBook addressBook, - final SoftwareVersion appVersion) { - platformState.bulkUpdate(v -> { - v.setAddressBook(addressBook.copy()); - v.setCreationSoftwareVersion(appVersion); - v.setRound(0); - v.setLegacyRunningEventHash(null); - v.setConsensusTimestamp(Instant.ofEpochSecond(0L)); - - final BasicConfig basicConfig = configuration.getConfigData(BasicConfig.class); - - final long genesisFreezeTime = basicConfig.genesisFreezeTime(); - if (genesisFreezeTime > 0) { - v.setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); - } - }); - } - - /** - * Build and initialize a genesis state. - * - * @param configuration the configuration for this node - * @param addressBook the current address book - * @param appVersion the software version of the app - * @param stateRoot the merkle root node of the state - * @return a reserved genesis signed state - */ - public static ReservedSignedState buildGenesisState( - @NonNull final Configuration configuration, - @NonNull final AddressBook addressBook, - @NonNull final SoftwareVersion appVersion, - @NonNull final MerkleRoot stateRoot) { - - if (!configuration.getConfigData(AddressBookConfig.class).useRosterLifecycle()) { - initGenesisPlatformState(configuration, stateRoot.getWritablePlatformState(), addressBook, appVersion); - } - - final SignedState signedState = new SignedState( - configuration, CryptoStatic::verifySignature, stateRoot, "genesis state", false, false, false); - return signedState.reserve("initial reservation on genesis state"); - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformMerkleStateRoot.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformMerkleStateRoot.java index d18a56265bd5..b6c9b1c7fc37 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformMerkleStateRoot.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/PlatformMerkleStateRoot.java @@ -231,7 +231,7 @@ private WritablePlatformStateStore writablePlatformStateStore() { private com.hedera.hapi.platform.state.PlatformState getPlatformState() { final var index = findNodeIndex(PlatformStateService.NAME, PLATFORM_STATE_KEY); return index == -1 - ? V0540PlatformStateSchema.GENESIS_PLATFORM_STATE + ? V0540PlatformStateSchema.UNINITIALIZED_PLATFORM_STATE : ((SingletonNode) getChild(index)).getValue(); } 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 55eef7dd48b9..5463506ee474 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 @@ -20,12 +20,13 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.platform.state.ConsensusSnapshot; import com.hedera.hapi.platform.state.PlatformState; +import com.swirlds.config.api.Configuration; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; -import com.swirlds.platform.state.service.schemas.V057PlatformStateSchema; +import com.swirlds.platform.state.service.schemas.V058RosterLifecycleTransitionSchema; import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.lifecycle.Service; @@ -36,7 +37,7 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; +import java.util.function.Function; /** * A service that provides the schema for the platform state, used by {@link MerkleStateRoot} @@ -45,13 +46,26 @@ public enum PlatformStateService implements Service { PLATFORM_STATE_SERVICE; - private static final AtomicReference> ACTIVE_ROSTER = new AtomicReference<>(); - private static final AtomicReference> APP_VERSION = new AtomicReference<>(); + /** + * Temporary access to a function that computes an application version from config. + */ + private static final AtomicReference> APP_VERSION_FN = + new AtomicReference<>(); + /** + * Temporary access to the disk address book used in upgrade or network transplant + * scenarios before the roster lifecycle is enabled. + */ + @Deprecated + private static final AtomicReference DISK_ADDRESS_BOOK = new AtomicReference<>(); + /** + * The schemas to register with the {@link SchemaRegistry}. + */ private static final Collection SCHEMAS = List.of( - new V0540PlatformStateSchema(), - new V057PlatformStateSchema( - () -> requireNonNull(ACTIVE_ROSTER.get()).get(), - () -> requireNonNull(APP_VERSION.get()).get(), + new V0540PlatformStateSchema(DISK_ADDRESS_BOOK::get, config -> requireNonNull(APP_VERSION_FN.get()) + .apply(config)), + new V058RosterLifecycleTransitionSchema( + DISK_ADDRESS_BOOK::get, + config -> requireNonNull(APP_VERSION_FN.get()).apply(config), WritablePlatformStateStore::new)); public static final String NAME = "PlatformStateService"; @@ -69,26 +83,25 @@ public void registerSchemas(@NonNull final SchemaRegistry registry) { } /** - * Sets the active roster to the given roster. - * @param roster the roster to set as active + * Sets the application version to the given version. + * @param appVersionFn the version to set as the application version */ - public void setActiveRosterFn(@NonNull final Supplier roster) { - ACTIVE_ROSTER.set(requireNonNull(roster)); + public void setAppVersionFn(@NonNull final Function appVersionFn) { + APP_VERSION_FN.set(requireNonNull(appVersionFn)); } /** - * Clears the active roster. + * Sets the disk address book to the given address book. */ - public void clearActiveRosterFn() { - ACTIVE_ROSTER.set(null); + public void setDiskAddressBook(@NonNull final AddressBook addressBook) { + DISK_ADDRESS_BOOK.set(requireNonNull(addressBook)); } /** - * Sets the application version to the given version. - * @param appVersionFn the version to set as the application version + * Clears the disk address book. */ - public void setAppVersionFn(@NonNull final Supplier appVersionFn) { - APP_VERSION.set(requireNonNull(appVersionFn)); + public void clearDiskAddressBook() { + DISK_ADDRESS_BOOK.set(null); } /** @@ -96,7 +109,7 @@ public void setAppVersionFn(@NonNull final Supplier appVersionF * @param root the root to extract the creation version from * @return the creation version of the platform state, or null if the state is a genesis state */ - public SemanticVersion creationVersionOf(@NonNull final MerkleStateRoot root) { + public SemanticVersion creationVersionOf(@NonNull final MerkleStateRoot root) { requireNonNull(root); final var state = platformStateOf(root); return state == null ? null : state.creationSoftwareVersionOrThrow(); @@ -107,7 +120,7 @@ public SemanticVersion creationVersionOf(@NonNull final MerkleStateRoot root) { * @param root the root to extract the round number from * @return the round number of the platform state, or zero if the state is a genesis state */ - public long roundOf(@NonNull final MerkleStateRoot root) { + public long roundOf(@NonNull final MerkleStateRoot root) { requireNonNull(root); final var platformState = platformStateOf(root); return platformState == null @@ -118,7 +131,7 @@ public long roundOf(@NonNull final MerkleStateRoot root) { } @SuppressWarnings("unchecked") - public @Nullable PlatformState platformStateOf(@NonNull final MerkleStateRoot root) { + public @Nullable PlatformState platformStateOf(@NonNull final MerkleStateRoot root) { final var index = root.findNodeIndex(NAME, PLATFORM_STATE_KEY); if (index == -1) { return null; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java index 5a47b98fe1ea..dc1b52745605 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java @@ -91,7 +91,7 @@ public void putCandidateRoster(@NonNull final Roster candidateRoster) { RosterUtils.hash(candidateRoster).getBytes(); // update the roster state/map - final RosterState previousRosterState = rosterStateOrThrow(); + final RosterState previousRosterState = rosterStateOrDefault(); final Bytes previousCandidateRosterHash = previousRosterState.candidateRosterHash(); final Builder newRosterStateBuilder = previousRosterState.copyBuilder().candidateRosterHash(incomingCandidateRosterHash); @@ -115,7 +115,7 @@ public void putActiveRoster(@NonNull final Roster roster, final long round) { RosterValidator.validate(roster); // update the roster state - final RosterState previousRosterState = rosterStateOrThrow(); + final RosterState previousRosterState = rosterStateOrDefault(); final List roundRosterPairs = new LinkedList<>(previousRosterState.roundRosterPairs()); if (!roundRosterPairs.isEmpty()) { final RoundRosterPair activeRosterPair = roundRosterPairs.getFirst(); @@ -152,13 +152,13 @@ public void putActiveRoster(@NonNull final Roster roster, final long round) { } /** - * Returns the roster state or throws an exception if the state is null. + * Returns the roster state; or the default roster state if the roster state is not yet set at genesis. * @return the roster state - * @throws NullPointerException if the roster state is null */ @NonNull - private RosterState rosterStateOrThrow() { - return requireNonNull(rosterState.get()); + private RosterState rosterStateOrDefault() { + RosterState state; + return (state = rosterState.get()) == null ? RosterState.DEFAULT : state; } /** @@ -168,7 +168,7 @@ private RosterState rosterStateOrThrow() { * @param rosterHash the hash of the roster */ private void removeRoster(@NonNull final Bytes rosterHash) { - final List activeRosterHistory = rosterStateOrThrow().roundRosterPairs(); + final List activeRosterHistory = rosterStateOrDefault().roundRosterPairs(); if (activeRosterHistory.stream() .noneMatch(rosterPair -> rosterPair.activeRosterHash().equals(rosterHash))) { this.rosterMap.remove(ProtoBytes.newBuilder().value(rosterHash).build()); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540PlatformStateSchema.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540PlatformStateSchema.java index caf3f69b2079..2bfe21cfb961 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540PlatformStateSchema.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540PlatformStateSchema.java @@ -16,30 +16,68 @@ package com.swirlds.platform.state.service.schemas; +import static java.util.Objects.requireNonNull; + import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.platform.state.ConsensusSnapshot; import com.hedera.hapi.platform.state.PlatformState; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.config.api.Configuration; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.config.BasicConfig; +import com.swirlds.platform.state.PlatformStateModifier; +import com.swirlds.platform.state.service.WritablePlatformStateStore; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; import edu.umd.cs.findbugs.annotations.NonNull; +import java.time.Instant; import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; /** - * This is a schema for the platform state that is represented by a singleton. The schema is responsible for - * registering {@link #GENESIS_PLATFORM_STATE} state if an instance of the state does not exist. + * Defines the {@link PlatformState} singleton and initializes it at genesis. */ public class V0540PlatformStateSchema extends Schema { - public static final PlatformState GENESIS_PLATFORM_STATE = new PlatformState( - SemanticVersion.DEFAULT, 0, ConsensusSnapshot.DEFAULT, null, null, Bytes.EMPTY, 0L, 0L, null, null, null); + private static final Supplier UNAVAILABLE_DISK_ADDRESS_BOOK = () -> { + throw new IllegalStateException("No disk address book available"); + }; + private static final Function UNAVAILABLE_VERSION_FN = config -> { + throw new IllegalStateException("No version information available"); + }; + public static final String PLATFORM_STATE_KEY = "PLATFORM_STATE"; + /** + * A platform state to be used as the non-null platform state under any circumstance a genesis state + * is encountered before initializing the States API. + */ + public static final PlatformState UNINITIALIZED_PLATFORM_STATE = new PlatformState( + SemanticVersion.DEFAULT, 0, ConsensusSnapshot.DEFAULT, null, null, Bytes.EMPTY, 0L, 0L, null, null, null); private static final SemanticVersion VERSION = SemanticVersion.newBuilder().major(0).minor(54).patch(0).build(); + private final Supplier addressBook; + private final Function versionFn; + public V0540PlatformStateSchema() { + this(UNAVAILABLE_DISK_ADDRESS_BOOK, UNAVAILABLE_VERSION_FN); + } + + public V0540PlatformStateSchema(@NonNull final Function versionFn) { + this(UNAVAILABLE_DISK_ADDRESS_BOOK, versionFn); + } + + public V0540PlatformStateSchema( + @NonNull final Supplier addressBook, + @NonNull final Function versionFn) { super(VERSION); + this.addressBook = requireNonNull(addressBook); + this.versionFn = requireNonNull(versionFn); } @NonNull @@ -50,9 +88,42 @@ public Set statesToCreate() { @Override public void migrate(@NonNull final MigrationContext ctx) { - final var platformState = ctx.newStates().getSingleton(PLATFORM_STATE_KEY); - if (platformState.get() == null) { - platformState.put(GENESIS_PLATFORM_STATE); + final var stateSingleton = ctx.newStates().getSingleton(PLATFORM_STATE_KEY); + if (ctx.isGenesis()) { + stateSingleton.put(UNINITIALIZED_PLATFORM_STATE); + final var genesisStateSpec = genesisStateSpec(ctx); + final var platformStateStore = new WritablePlatformStateStore(ctx.newStates()); + if (ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + // When using the roster lifecycle at genesis, platform code will never + // use the legacy previous/current AddressBook fields, so omit them + platformStateStore.bulkUpdate(genesisStateSpec); + } else { + final var book = addressBook.get(); + requireNonNull(book); + platformStateStore.bulkUpdate(genesisStateSpec.andThen(v -> { + v.setPreviousAddressBook(null); + v.setAddressBook(book.copy()); + })); + } + } else { + // (FUTURE) Delete this code path, it is only reached through the Browser entrypoint + if (stateSingleton.get() == null) { + stateSingleton.put(UNINITIALIZED_PLATFORM_STATE); + } } } + + private Consumer genesisStateSpec(@NonNull final MigrationContext ctx) { + return v -> { + v.setCreationSoftwareVersion(versionFn.apply(ctx.appConfig())); + v.setRound(0); + v.setLegacyRunningEventHash(null); + v.setConsensusTimestamp(Instant.EPOCH); + final var basicConfig = ctx.platformConfig().getConfigData(BasicConfig.class); + final long genesisFreezeTime = basicConfig.genesisFreezeTime(); + if (genesisFreezeTime > 0) { + v.setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); + } + }; + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterSchema.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterBaseSchema.java similarity index 95% rename from platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterSchema.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterBaseSchema.java index 32bba2830219..0fe1391c0406 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterSchema.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V0540RosterBaseSchema.java @@ -31,11 +31,11 @@ /** * Roster Schema */ -public class V0540RosterSchema extends Schema { +public class V0540RosterBaseSchema extends Schema { public static final String ROSTER_KEY = "ROSTERS"; public static final String ROSTER_STATES_KEY = "ROSTER_STATE"; - private static final Logger log = LogManager.getLogger(V0540RosterSchema.class); + private static final Logger log = LogManager.getLogger(V0540RosterBaseSchema.class); /** * this can't be increased later so we pick some number large enough, 2^16. */ @@ -50,7 +50,7 @@ public class V0540RosterSchema extends Schema { /** * Create a new instance */ - public V0540RosterSchema() { + public V0540RosterBaseSchema() { super(VERSION); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchema.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchema.java deleted file mode 100644 index 3ca476adb33f..000000000000 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchema.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.state.service.schemas; - -import static com.swirlds.state.lifecycle.HapiUtils.SEMANTIC_VERSION_COMPARATOR; -import static java.util.Objects.requireNonNull; - -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.node.internal.network.NodeMetadata; -import com.swirlds.platform.config.AddressBookConfig; -import com.swirlds.platform.config.BasicConfig; -import com.swirlds.platform.roster.RosterUtils; -import com.swirlds.platform.state.service.WritablePlatformStateStore; -import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.state.lifecycle.MigrationContext; -import com.swirlds.state.lifecycle.Schema; -import com.swirlds.state.spi.WritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.time.Instant; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * Restart-only schema that ensures the platform state at startup - * reflects the active roster. - */ -public class V057PlatformStateSchema extends Schema { - private static final SemanticVersion VERSION = - SemanticVersion.newBuilder().major(0).minor(57).build(); - - /** - * A supplier for the active roster. - */ - private final Supplier activeRoster; - - private final Supplier appVersion; - private final Function platformStateStoreFactory; - - public V057PlatformStateSchema( - @NonNull final Supplier activeRoster, - @NonNull final Supplier appVersion, - @NonNull final Function platformStateStoreFactory) { - super(VERSION); - this.activeRoster = requireNonNull(activeRoster); - this.appVersion = requireNonNull(appVersion); - this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); - } - - @Override - public void restart(@NonNull final MigrationContext ctx) { - requireNonNull(ctx); - if (!ctx.configuration().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { - return; - } - - final var platformStateStore = platformStateStoreFactory.apply(ctx.newStates()); - final var startupNetworks = ctx.startupNetworks(); - if (ctx.isGenesis()) { - final var genesisNetwork = startupNetworks.genesisNetworkOrThrow(); - final var roster = new Roster(genesisNetwork.nodeMetadata().stream() - .map(NodeMetadata::rosterEntryOrThrow) - .toList()); - final var addressBook = RosterUtils.buildAddressBook(roster); - platformStateStore.bulkUpdate(v -> { - v.setAddressBook(addressBook); - v.setPreviousAddressBook(null); - v.setCreationSoftwareVersion(appVersion.get()); - v.setRound(0); - v.setLegacyRunningEventHash(null); - v.setConsensusTimestamp(Instant.ofEpochSecond(0L)); - - final BasicConfig basicConfig = ctx.configuration().getConfigData(BasicConfig.class); - - final long genesisFreezeTime = basicConfig.genesisFreezeTime(); - if (genesisFreezeTime > 0) { - v.setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); - } - }); - } else if (isUpgrade(ctx)) { - final var candidateAddressBook = RosterUtils.buildAddressBook(activeRoster.get()); - final var previousAddressBook = platformStateStore.getAddressBook(); - platformStateStore.bulkUpdate(v -> { - v.setAddressBook(candidateAddressBook.copy()); - v.setPreviousAddressBook(previousAddressBook == null ? null : previousAddressBook.copy()); - }); - } - } - - private boolean isUpgrade(@NonNull final MigrationContext ctx) { - final var currentVersion = appVersion.get().getPbjSemanticVersion(); - final var previousVersion = ctx.previousVersion(); - return SEMANTIC_VERSION_COMPARATOR.compare(currentVersion, (requireNonNull(previousVersion))) > 0; - } -} diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchema.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchema.java new file mode 100644 index 000000000000..e2203d654fae --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchema.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.swirlds.platform.state.service.schemas; + +import static com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema.PLATFORM_STATE_KEY; +import static com.swirlds.state.lifecycle.HapiUtils.SEMANTIC_VERSION_COMPARATOR; +import static java.util.Objects.requireNonNull; + +import com.hedera.hapi.node.base.SemanticVersion; +import com.hedera.hapi.platform.state.PlatformState; +import com.swirlds.config.api.Configuration; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.state.service.WritablePlatformStateStore; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.lifecycle.Schema; +import com.swirlds.state.spi.WritableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * A restart-only schema to ensure the platform state has its active and previous + * address books configured correctly when the roster lifecycle is disabled. + *

    + * (FUTURE) Delete at the same time as the {@link AddressBookConfig#useRosterLifecycle()} + * feature flag. + */ +@Deprecated +public class V058RosterLifecycleTransitionSchema extends Schema { + private static final SemanticVersion VERSION = + SemanticVersion.newBuilder().major(0).minor(58).build(); + + private final Supplier addressBook; + private final Function appVersionFn; + private final Function platformStateStoreFn; + + public V058RosterLifecycleTransitionSchema( + @NonNull final Supplier addressBook, + @NonNull final Function appVersionFn, + @NonNull final Function platformStateStoreFn) { + super(VERSION); + this.addressBook = requireNonNull(addressBook); + this.appVersionFn = requireNonNull(appVersionFn); + this.platformStateStoreFn = requireNonNull(platformStateStoreFn); + } + + @Override + public void migrate(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + if (ctx.appConfig().getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + final var stateSingleton = ctx.newStates().getSingleton(PLATFORM_STATE_KEY); + final var state = requireNonNull(stateSingleton.get()); + // Null out the legacy address book fields + stateSingleton.put(state.copyBuilder() + .previousAddressBook((com.hedera.hapi.platform.state.AddressBook) null) + .addressBook(((com.hedera.hapi.platform.state.AddressBook) null)) + .build()); + } + } + + @Override + public void restart(@NonNull final MigrationContext ctx) { + requireNonNull(ctx); + final var addressBookConfig = ctx.appConfig().getConfigData(AddressBookConfig.class); + if (!addressBookConfig.useRosterLifecycle() && !ctx.isGenesis()) { + final boolean addressBookChanged = ctx.isUpgrade( + config -> new Semver(appVersionFn.apply(config).getPbjSemanticVersion()), Semver::new) + || addressBookConfig.forceUseOfConfigAddressBook(); + if (addressBookChanged) { + final var stateStore = platformStateStoreFn.apply(ctx.newStates()); + final var currentBook = stateStore.getAddressBook(); + stateStore.bulkUpdate(v -> { + v.setPreviousAddressBook(currentBook == null ? null : currentBook.copy()); + v.setAddressBook(requireNonNull(addressBook.get()).copy()); + }); + } + } + } + + /** + * A comparable wrapper around a {@link SemanticVersion} to allow for version comparison. + * @param version the version to wrap + */ + private record Semver(@NonNull SemanticVersion version) implements Comparable { + @Override + public int compareTo(@NonNull final Semver that) { + return SEMANTIC_VERSION_COMPARATOR.compare(this.version, that.version); + } + } +} 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 75b1a6624329..09e1e772815d 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 @@ -19,7 +19,6 @@ import static com.swirlds.common.merkle.utility.MerkleUtils.rehashTree; import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.logging.legacy.LogMarker.STARTUP; -import static com.swirlds.platform.state.GenesisStateBuilder.buildGenesisState; import static com.swirlds.platform.state.signed.ReservedSignedState.createNullReservation; import static com.swirlds.platform.state.snapshot.SignedStateFileReader.readStateFile; import static java.util.Objects.requireNonNull; @@ -31,10 +30,13 @@ import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.logging.legacy.payload.SavedStateLoadedPayload; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.config.BasicConfig; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.internal.SignedStateLoadingException; import com.swirlds.platform.state.MerkleRoot; +import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SavedStateInfo; import com.swirlds.platform.state.snapshot.SignedStateFilePath; @@ -44,6 +46,7 @@ import edu.umd.cs.findbugs.annotations.Nullable; import java.io.IOException; import java.io.UncheckedIOException; +import java.time.Instant; import java.util.List; import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; @@ -59,8 +62,8 @@ public final class StartupStateUtils { private StartupStateUtils() {} /** - * Get the initial state to be used by this node. May return a state loaded from disk, or may return a genesis state - * if no valid state is found on disk. + * Used exclusively by {@link com.swirlds.platform.Browser} to get the initial state to be used by this node. + * May return a state loaded from disk, or may return a genesis state if no valid state is found on disk. * * @param configuration the configuration for this node * @param softwareVersion the software version of the app @@ -74,6 +77,7 @@ private StartupStateUtils() {} * delete malformed states */ @NonNull + @Deprecated(forRemoval = true) public static HashedReservedSignedState getInitialState( @NonNull final Configuration configuration, @NonNull final RecycleBin recycleBin, @@ -126,7 +130,7 @@ public static HashedReservedSignedState getInitialState( * delete malformed states */ @NonNull - static ReservedSignedState loadStateFile( + public static ReservedSignedState loadStateFile( @NonNull final Configuration configuration, @NonNull final RecycleBin recycleBin, @NonNull final NodeId selfId, @@ -305,4 +309,56 @@ private static void recycleState(@NonNull final RecycleBin recycleBin, @NonNull throw new UncheckedIOException("unable to recycle state", e); } } + + /** + * Build and initialize a genesis state. + * + * @param configuration the configuration for this node + * @param addressBook the current address book + * @param appVersion the software version of the app + * @param stateRoot the merkle root node of the state + * @return a reserved genesis signed state + */ + private static ReservedSignedState buildGenesisState( + @NonNull final Configuration configuration, + @NonNull final AddressBook addressBook, + @NonNull final SoftwareVersion appVersion, + @NonNull final MerkleRoot stateRoot) { + + if (!configuration.getConfigData(AddressBookConfig.class).useRosterLifecycle()) { + initGenesisPlatformState(configuration, stateRoot.getWritablePlatformState(), addressBook, appVersion); + } + + final SignedState signedState = new SignedState( + configuration, CryptoStatic::verifySignature, stateRoot, "genesis state", false, false, false); + return signedState.reserve("initial reservation on genesis state"); + } + + /** + * Initializes a genesis platform state. + * @param configuration the configuration for this node + * @param platformState the platform state to initialize + * @param addressBook the current address book + * @param appVersion the software version of the app + */ + private static void initGenesisPlatformState( + final Configuration configuration, + final PlatformStateModifier platformState, + final AddressBook addressBook, + final SoftwareVersion appVersion) { + platformState.bulkUpdate(v -> { + v.setAddressBook(addressBook.copy()); + v.setCreationSoftwareVersion(appVersion); + v.setRound(0); + v.setLegacyRunningEventHash(null); + v.setConsensusTimestamp(Instant.ofEpochSecond(0L)); + + final BasicConfig basicConfig = configuration.getConfigData(BasicConfig.class); + + final long genesisFreezeTime = basicConfig.genesisFreezeTime(); + if (genesisFreezeTime > 0) { + v.setFreezeTime(Instant.ofEpochSecond(genesisFreezeTime)); + } + }); + } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java index 8a6ccf77d000..899964b20678 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java @@ -32,7 +32,7 @@ import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; +import com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.state.State; import com.swirlds.state.lifecycle.Schema; @@ -189,7 +189,7 @@ public static void registerServiceStates(@NonNull final SignedState signedState) */ public static void registerServiceStates(@NonNull final State state) { registerServiceState(state, new V0540PlatformStateSchema(), PlatformStateService.NAME); - registerServiceState(state, new V0540RosterSchema(), RosterStateId.NAME); + registerServiceState(state, new V0540RosterBaseSchema(), RosterStateId.NAME); } private static void registerServiceState( diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PlatformStateServiceTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PlatformStateServiceTest.java index a73b5d0262ac..095a3567bb39 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PlatformStateServiceTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/PlatformStateServiceTest.java @@ -17,7 +17,6 @@ package com.swirlds.platform.state.service; import static com.swirlds.platform.state.service.PlatformStateService.PLATFORM_STATE_SERVICE; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertInstanceOf; import static org.junit.jupiter.api.Assertions.assertNull; @@ -25,10 +24,9 @@ import static org.mockito.BDDMockito.given; import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.platform.state.PlatformState; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; -import com.swirlds.platform.state.service.schemas.V057PlatformStateSchema; +import com.swirlds.platform.state.service.schemas.V058RosterLifecycleTransitionSchema; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.merkle.MerkleStateRoot; @@ -50,12 +48,6 @@ class PlatformStateServiceTest { @Mock private SingletonNode platformState; - @Test - void canSetAndClearActiveRosterFn() { - assertDoesNotThrow(() -> PLATFORM_STATE_SERVICE.setActiveRosterFn(() -> Roster.DEFAULT)); - assertDoesNotThrow(PLATFORM_STATE_SERVICE::clearActiveRosterFn); - } - @Test void registersOneSchema() { final ArgumentCaptor captor = ArgumentCaptor.forClass(Schema.class); @@ -64,7 +56,7 @@ void registersOneSchema() { final var schemas = captor.getAllValues(); assertEquals(2, schemas.size()); assertInstanceOf(V0540PlatformStateSchema.class, schemas.getFirst()); - assertInstanceOf(V057PlatformStateSchema.class, schemas.getLast()); + assertInstanceOf(V058RosterLifecycleTransitionSchema.class, schemas.getLast()); } @Test diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchemaTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchemaTest.java deleted file mode 100644 index 5bcd3b4a80cc..000000000000 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V057PlatformStateSchemaTest.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.state.service.schemas; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import com.hedera.hapi.node.base.SemanticVersion; -import com.hedera.hapi.node.state.roster.Roster; -import com.hedera.hapi.node.state.roster.RosterEntry; -import com.hedera.node.internal.network.Network; -import com.hedera.node.internal.network.NodeMetadata; -import com.hedera.pbj.runtime.io.buffer.Bytes; -import com.swirlds.config.api.Configuration; -import com.swirlds.platform.config.AddressBookConfig; -import com.swirlds.platform.state.service.WritablePlatformStateStore; -import com.swirlds.platform.system.BasicSoftwareVersion; -import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.state.lifecycle.MigrationContext; -import com.swirlds.state.lifecycle.StartupNetworks; -import com.swirlds.state.spi.WritableStates; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.util.function.Function; -import java.util.function.Supplier; -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 V057PlatformStateSchemaTest { - private static final Network NETWORK = Network.newBuilder() - .nodeMetadata(NodeMetadata.newBuilder() - .rosterEntry(RosterEntry.newBuilder() - .nodeId(1L) - .gossipCaCertificate( - Bytes.fromHex( - "308203b130820219a003020102020900ad394d2f0a65e6f5300d06092a864886f70d01010c05003017311530130603550403130c732d7a77396a6566793079763020170d3030303130313030303030305a180f32313030303130313030303030305a3017311530130603550403130c732d7a77396a656679307976308201a2300d06092a864886f70d01010105000382018f003082018a0282018100c1a0ff5d2372b53d12d12bb87dd03f5e3427e0cee1d3c898bbd320c4b3dd17257944ea39a07f5344d9abfcdd50214072f1bbc12173fe7933d032c7d210734cc92d24be22b44cf50c2aa06f19bcd75180dc3e8dedd5ffcac02bf98721df9c3e79f20e9942cac9328b99160afea44d42c87b0147f3f29567085ed3f841dbe37aba35a2c5446bc638c62c703a6f680fa0601bfe7c6254e9fe2f471670ecdcca26128716a08f4141595ec0c4ac7ae589f37deede17480ecc1500f88335d0e33929725e8e4e775f3e4aa44c867bc86d3bf6d7165a4b766dd4ceb622221634a0a3d82840800b5b3e540640ea2f8c5749c3a6a0e0c474515c3f0ed9aadab8f84423a8954fd7f4e40b73125aeced4f791dba5052e3f5b3191a430f9b2dd30e4071cc54280c830da0d1e0dd54300c243ef08d9f81b3a90373f10910b6f4975bb2d861273993221e42b82b5af823267f79de90a7221129f0423724f9208a4ca15a73458c555e08e015db9d77c884acacaf4971d3854ea7bbdd9cfaf49df852c11473e96fa10203010001300d06092a864886f70d01010c05000382018100455e8d6c1b276d3d20a4b1ccc0abbbb36460cd985612d1068aab8cd5a479877f524c808c44469d3f17a752d7ce24d6e1536d5f02b8788890f6249c135ed05583126b6d38f7bf2d42c5a404f34379387d659eff5ff8a6ac1938254c2a3cee6abbbaca7b8e7069f7da8d5ce157a3c40bc0220abd05d8f54f0ac4aba3757a076ba14f598f1f835e566f71a50f933af979501499d959d356e71c5e954fdb0428578115fa540417b32156861c0f7960fa1e0473c76b2d579fbc30aa7ce718c7c413811b024f66d0e7e3350b30bd39a74f1f325818e5d26eececec78108bc77d55615b1568fb9b74e7567679606541d36f4f44c2bd07f5adb81c384d4b8ea1a287fbd356278344ec2582f040187f2f241ff812d54861754a47838b9cbe94f9d3e9333183c4f651a2e2ba3f5dcd77ff0560db17cb0d3481718d68aaafa076c6612674d3c264d42352811c2510a418987d1fba46ccaf5fe5d3b579fe002c106ffd4cd83ff0d0e16c9d92694a1764637d6fd2298fc1389c10de4e43b7fd1738d3acc13660")) - .build()) - .build()) - .build(); - - private static final Roster ROSTER = new Roster(NETWORK.nodeMetadata().stream() - .map(NodeMetadata::rosterEntryOrThrow) - .toList()); - - private static final SemanticVersion THEN = - SemanticVersion.newBuilder().major(7).build(); - - @Mock - private Supplier activeRosterSupplier; - - @Mock - private Supplier appVersionSupplier; - - @Mock - private MigrationContext migrationContext; - - @Mock - private Configuration configuration; - - @Mock - private WritableStates writableStates; - - @Mock - private Function platformStateStoreFactory; - - @Mock - private WritablePlatformStateStore platformStateStore; - - @Mock - private StartupNetworks startupNetworks; - - private V057PlatformStateSchema schema; - - @BeforeEach - void setUp() { - schema = new V057PlatformStateSchema(activeRosterSupplier, appVersionSupplier, platformStateStoreFactory); - } - - @Test - void noOpIfNotUsingRosterLifecycle() { - givenContextWith(CurrentVersion.NA, RosterLifecycle.OFF, AvailableNetwork.NONE); - - schema.restart(migrationContext); - - verify(migrationContext, never()).newStates(); - } - - @Test - void platformStateIsUpdatedOnGenesis() { - givenContextWith(CurrentVersion.NA, RosterLifecycle.ON, AvailableNetwork.GENESIS); - - schema.restart(migrationContext); - - verify(platformStateStore, times(1)).bulkUpdate(any()); - } - - @Test - void platformStateNotUpdatedIfNotUpgradeBoundary() { - givenContextWith(CurrentVersion.OLD, RosterLifecycle.ON, AvailableNetwork.NONE); - given(migrationContext.previousVersion()).willReturn(THEN); - - schema.restart(migrationContext); - - verify(platformStateStore, never()).bulkUpdate(any()); - } - - @Test - void platformStateIsUpdatedOnUpgradeBoundary() { - givenContextWith(CurrentVersion.NEW, RosterLifecycle.ON, AvailableNetwork.NONE); - given(migrationContext.previousVersion()).willReturn(THEN); - given(activeRosterSupplier.get()).willReturn(ROSTER); - - schema.restart(migrationContext); - - verify(platformStateStore, times(1)).bulkUpdate(any()); - } - - private enum CurrentVersion { - NA, - OLD, - NEW, - } - - private enum RosterLifecycle { - ON, - OFF - } - - private enum AvailableNetwork { - GENESIS, - NONE - } - - private void givenContextWith( - @NonNull final CurrentVersion currentVersion, - @NonNull final RosterLifecycle rosterLifecycle, - @NonNull final AvailableNetwork availableNetwork) { - switch (currentVersion) { - case NA -> { - // No-op - } - case OLD -> given(appVersionSupplier.get()).willReturn(new BasicSoftwareVersion(7)); - case NEW -> given(appVersionSupplier.get()).willReturn(new BasicSoftwareVersion(42)); - } - - given(migrationContext.configuration()).willReturn(configuration); - given(configuration.getConfigData(AddressBookConfig.class)) - .willReturn(new AddressBookConfig( - true, - false, - null, - 50, - switch (rosterLifecycle) { - case ON -> true; - case OFF -> false; - })); - - if (rosterLifecycle == RosterLifecycle.ON) { - given(migrationContext.newStates()).willReturn(writableStates); - given(platformStateStoreFactory.apply(writableStates)).willReturn(platformStateStore); - given(migrationContext.startupNetworks()).willReturn(startupNetworks); - } - - if (availableNetwork == AvailableNetwork.GENESIS) { - given(migrationContext.isGenesis()).willReturn(true); - given(startupNetworks.genesisNetworkOrThrow()).willReturn(NETWORK); - } - } -} diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchemaTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchemaTest.java new file mode 100644 index 000000000000..1ec3c0d8b7b1 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/service/schemas/V058RosterLifecycleTransitionSchemaTest.java @@ -0,0 +1,146 @@ +/* + * 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.state.service.schemas; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import com.hedera.hapi.platform.state.PlatformState; +import com.swirlds.config.api.Configuration; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.state.PlatformStateModifier; +import com.swirlds.platform.state.service.WritablePlatformStateStore; +import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.state.lifecycle.MigrationContext; +import com.swirlds.state.spi.WritableSingletonState; +import com.swirlds.state.spi.WritableStates; +import java.util.function.Consumer; +import java.util.function.Function; +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 V058RosterLifecycleTransitionSchemaTest { + @Mock + private AddressBook addressBook; + + @Mock + private Configuration config; + + @Mock + private AddressBookConfig addressBookConfig; + + @Mock + private MigrationContext ctx; + + @Mock + private WritableStates writableStates; + + @Mock + private WritablePlatformStateStore platformStateStore; + + @Mock + private WritableSingletonState stateSingleton; + + @Mock + private Function appVersionFn; + + @Mock + private Function platformStateStoreFn; + + private V058RosterLifecycleTransitionSchema subject; + + @BeforeEach + void setUp() { + subject = new V058RosterLifecycleTransitionSchema(() -> addressBook, appVersionFn, platformStateStoreFn); + } + + @Test + void migrateNullsOutAddressBooksAtBoundary() { + final var oldState = PlatformState.newBuilder() + .previousAddressBook(com.hedera.hapi.platform.state.AddressBook.DEFAULT) + .addressBook(com.hedera.hapi.platform.state.AddressBook.DEFAULT) + .build(); + given(ctx.newStates()).willReturn(writableStates); + given(writableStates.getSingleton("PLATFORM_STATE")).willReturn(stateSingleton); + given(stateSingleton.get()).willReturn(oldState); + given(ctx.appConfig()).willReturn(config); + given(config.getConfigData(AddressBookConfig.class)).willReturn(addressBookConfig); + given(addressBookConfig.useRosterLifecycle()).willReturn(true); + subject.migrate(ctx); + verify(stateSingleton).put(PlatformState.DEFAULT); + } + + @Test + void doesNotApplyAtGenesis() { + given(ctx.appConfig()).willReturn(config); + given(config.getConfigData(AddressBookConfig.class)).willReturn(addressBookConfig); + given(ctx.isGenesis()).willReturn(true); + subject.restart(ctx); + verifyNoInteractions(platformStateStoreFn); + } + + @Test + void noOpIfAddressBookNotChanged() { + given(ctx.appConfig()).willReturn(config); + given(config.getConfigData(AddressBookConfig.class)).willReturn(addressBookConfig); + subject.restart(ctx); + verifyNoInteractions(platformStateStoreFn); + } + + @Test + @SuppressWarnings("unchecked") + void changesAtUpgradeBoundary() { + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Consumer.class); + given(ctx.appConfig()).willReturn(config); + given(ctx.isUpgrade(any(), any())).willReturn(true); + given(config.getConfigData(AddressBookConfig.class)).willReturn(addressBookConfig); + given(addressBook.copy()).willReturn(addressBook); + given(ctx.newStates()).willReturn(writableStates); + given(platformStateStoreFn.apply(writableStates)).willReturn(platformStateStore); + given(platformStateStore.getAddressBook()).willReturn(addressBook); + subject.restart(ctx); + verify(platformStateStore).bulkUpdate(captor.capture()); + captor.getValue().accept(platformStateStore); + verify(platformStateStore).setPreviousAddressBook(addressBook); + verify(platformStateStore).setAddressBook(addressBook); + } + + @Test + @SuppressWarnings("unchecked") + void changesWhenForced() { + final ArgumentCaptor> captor = ArgumentCaptor.forClass(Consumer.class); + given(ctx.appConfig()).willReturn(config); + given(config.getConfigData(AddressBookConfig.class)).willReturn(addressBookConfig); + given(addressBookConfig.forceUseOfConfigAddressBook()).willReturn(true); + given(addressBook.copy()).willReturn(addressBook); + given(ctx.newStates()).willReturn(writableStates); + given(platformStateStoreFn.apply(writableStates)).willReturn(platformStateStore); + subject.restart(ctx); + verify(platformStateStore).bulkUpdate(captor.capture()); + captor.getValue().accept(platformStateStore); + verify(platformStateStore).setPreviousAddressBook(null); + verify(platformStateStore).setAddressBook(addressBook); + } +} diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/FakeMerkleStateLifecycles.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/FakeMerkleStateLifecycles.java index 9460420c1514..3a4040762dcc 100644 --- a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/FakeMerkleStateLifecycles.java +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/state/FakeMerkleStateLifecycles.java @@ -35,11 +35,13 @@ import com.swirlds.merkledb.MerkleDbDataSourceBuilder; import com.swirlds.merkledb.MerkleDbTableConfig; import com.swirlds.merkledb.config.MerkleDbConfig; +import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.config.BasicConfig; import com.swirlds.platform.state.MerkleStateLifecycles; import com.swirlds.platform.state.PlatformMerkleStateRoot; import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; -import com.swirlds.platform.state.service.schemas.V0540RosterSchema; +import com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.InitTrigger; import com.swirlds.platform.system.Platform; @@ -73,6 +75,8 @@ public enum FakeMerkleStateLifecycles implements MerkleStateLifecycles { FAKE_MERKLE_STATE_LIFECYCLES; public static final Configuration CONFIGURATION = ConfigurationBuilder.create() + .withConfigDataType(AddressBookConfig.class) + .withConfigDataType(BasicConfig.class) .withConfigDataType(MerkleDbConfig.class) .withConfigDataType(VirtualMapConfig.class) .withConfigDataType(TemporaryFileConfig.class) @@ -101,7 +105,7 @@ public static void registerMerkleStateRootClassIds() { VirtualNodeCache.class, () -> new VirtualNodeCache(CONFIGURATION.getConfigData(VirtualMapConfig.class)))); registerConstructablesForSchema(registry, new V0540PlatformStateSchema(), PlatformStateService.NAME); - registerConstructablesForSchema(registry, new V0540RosterSchema(), RosterStateId.NAME); + registerConstructablesForSchema(registry, new V0540RosterBaseSchema(), RosterStateId.NAME); } catch (ConstructableRegistryException e) { throw new IllegalStateException(e); } @@ -125,7 +129,7 @@ public List initPlatformState(@NonNull final State state) if (!(state instanceof MerkleStateRoot merkleStateRoot)) { throw new IllegalArgumentException("Can only be used with MerkleStateRoot instances"); } - final var schema = new V0540PlatformStateSchema(); + final var schema = new V0540PlatformStateSchema(config -> new BasicSoftwareVersion(1)); schema.statesToCreate().stream() .sorted(Comparator.comparing(StateDefinition::stateKey)) .forEach(def -> { @@ -155,7 +159,7 @@ public List initRosterState(@NonNull final State state) { if (!(state instanceof MerkleStateRoot merkleStateRoot)) { throw new IllegalArgumentException("Can only be used with MerkleStateRoot instances"); } - final var schema = new V0540RosterSchema(); + final var schema = new V0540RosterBaseSchema(); schema.statesToCreate().stream() .sorted(Comparator.comparing(StateDefinition::stateKey)) .forEach(def -> { diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/MigrationContext.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/MigrationContext.java index 495891b8920d..7b049977b728 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/MigrationContext.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/MigrationContext.java @@ -24,6 +24,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.Map; +import java.util.function.Function; /** * Provides the context for a migration of state from one {@link Schema} version to another. @@ -55,14 +56,22 @@ public interface MigrationContext { WritableStates newStates(); /** - * The {@link Configuration} for this migration. Any portion of this configuration which was based on state (such - * as, in our case, file 121) will be current as of the previous state. This configuration is read-only. Having this - * configuration is useful for migrations that should behavior differently based on configuration. + * The app {@link Configuration} for this migration. Any portion of this configuration which was based on state + * (such as, in our case, file 121) will be current as of the previous state. This configuration is read-only. + * Having this configuration is useful for migrations that should behavior differently based on configuration. * - * @return The configuration to use. + * @return The application configuration to use. */ @NonNull - Configuration configuration(); + Configuration appConfig(); + + /** + * The platform {@link Configuration} for this migration. + * + * @return The platform configuration to use + */ + @NonNull + Configuration platformConfig(); /** * Information about the network itself. Generally, this is not useful information for migrations, but is used at @@ -101,6 +110,14 @@ public interface MigrationContext { @Nullable SemanticVersion previousVersion(); + /** + * Returns a mutable "scratchpad" that can be used to share values between different services + * during a migration. + * + * @return the shared values map + */ + Map sharedValues(); + /** * Returns whether this is a genesis migration. */ @@ -109,10 +126,19 @@ default boolean isGenesis() { } /** - * Returns a mutable "scratchpad" that can be used to share values between different services - * during a migration. - * - * @return the shared values map + * Returns whether the current version is an upgrade from the previous version, relative to the ordering + * implied by the given functions used to compare the version in the current app configuration and the + * previous state version. + * @param currentVersionFn the function to compute the current version from the app configuration + * @param previousVersionFn the function to compute the previous version from the saved state + * @return whether the current version is an upgrade from the previous version + * @param the type of the version */ - Map sharedValues(); + default > boolean isUpgrade( + @NonNull final Function currentVersionFn, + @NonNull final Function previousVersionFn) { + final var current = currentVersionFn.apply(appConfig()); + final var previous = previousVersion(); + return currentVersionFn.apply(appConfig()).compareTo(previousVersionFn.apply(previousVersion())) > 0; + } } diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NetworkInfo.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NetworkInfo.java index 9a535fcf002b..fd639069592a 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NetworkInfo.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NetworkInfo.java @@ -16,7 +16,6 @@ package com.swirlds.state.lifecycle.info; -import com.hedera.hapi.node.state.roster.Roster; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; @@ -58,10 +57,4 @@ public interface NetworkInfo { * @param state the state to update from */ void updateFrom(State state); - - /** - * Returns the currently active roster used by the network. - * @return the currently active roster - */ - Roster roster(); } diff --git a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NodeInfo.java b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NodeInfo.java index ef8e203cc9ef..98974dbc132c 100644 --- a/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NodeInfo.java +++ b/platform-sdk/swirlds-state-api/src/main/java/com/swirlds/state/lifecycle/info/NodeInfo.java @@ -34,12 +34,12 @@ public interface NodeInfo { /** - * Convenience method to check if this node is zero-stake. + * Convenience method to check if this node has zero weight. * - * @return whether this node has zero stake. + * @return whether this node has zero weight */ - default boolean zeroStake() { - return stake() == 0; + default boolean zeroWeight() { + return weight() == 0; } /** @@ -65,7 +65,7 @@ default boolean zeroStake() { * The stake weight of this node. * @return the stake weight */ - long stake(); + long weight(); /** * The signing x509 certificate bytes of the member @@ -82,22 +82,26 @@ default boolean zeroStake() { List gossipEndpoints(); /** - * The public key of this node, as a hex-encoded string. It is extracted from the certificate bytes. - * - * @return the public key + * The gossip X.509 certificate of this node. + * @return the gossip X.509 certificate + * @throws IllegalStateException if the certificate could not be extracted */ - default String hexEncodedPublicKey() { + default X509Certificate sigCert() { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); - - // Convert the byte array to an InputStream and generate the X509Certificate object - X509Certificate certificate = (X509Certificate) certificateFactory.generateCertificate( + return (X509Certificate) certificateFactory.generateCertificate( new ByteArrayInputStream(sigCertBytes().toByteArray())); - - // Return the public key from the certificate - return CommonUtils.hex(certificate.getPublicKey().getEncoded()); } catch (CertificateException e) { throw new IllegalStateException("Error extracting public key from certificate", e); } } + + /** + * The public key of this node, as a hex-encoded string. It is extracted from the certificate bytes. + * + * @return the public key + */ + default String hexEncodedPublicKey() { + return CommonUtils.hex(sigCert().getPublicKey().getEncoded()); + } } From f7492ec52828d7e583b819edd8531de131a61bf7 Mon Sep 17 00:00:00 2001 From: JeffreyDallas <39912573+JeffreyDallas@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:31:53 -0600 Subject: [PATCH 07/39] chore: stop 0.56 and 0.55 nightly regressions (#17059) Signed-off-by: Jeffrey Tang --- .github/workflows/node-zxcron-release-fsts-regression.yaml | 2 +- .github/workflows/platform-zxcron-release-jrs-regression.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node-zxcron-release-fsts-regression.yaml b/.github/workflows/node-zxcron-release-fsts-regression.yaml index 5b062f275bf1..439de9d9064b 100644 --- a/.github/workflows/node-zxcron-release-fsts-regression.yaml +++ b/.github/workflows/node-zxcron-release-fsts-regression.yaml @@ -57,7 +57,7 @@ jobs: major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" - if [[ "${major}" -eq 0 && "${minor}" -lt 55 ]]; then + if [[ "${major}" -eq 0 && "${minor}" -lt 57 ]]; then continue fi diff --git a/.github/workflows/platform-zxcron-release-jrs-regression.yaml b/.github/workflows/platform-zxcron-release-jrs-regression.yaml index a46717c63c9d..7d5a29343336 100644 --- a/.github/workflows/platform-zxcron-release-jrs-regression.yaml +++ b/.github/workflows/platform-zxcron-release-jrs-regression.yaml @@ -59,7 +59,7 @@ jobs: major="${BASH_REMATCH[1]}" minor="${BASH_REMATCH[2]}" - if [[ "${major}" -eq 0 && "${minor}" -lt 55 ]]; then + if [[ "${major}" -eq 0 && "${minor}" -lt 57 ]]; then continue fi From 3831e6afe373a1b603f9c7bef89e16939eece4e5 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:01:31 -0600 Subject: [PATCH 08/39] feat: Tss Startup functionality (#16929) Signed-off-by: Neeharika-Sompalli --- .../com/hedera/node/app/tss/RosterToKey.java | 37 ++ .../hedera/node/app/tss/TssBaseService.java | 16 +- .../node/app/tss/TssBaseServiceComponent.java | 2 + .../node/app/tss/TssBaseServiceImpl.java | 400 ++++++++++++++++- .../node/app/tss/TssCryptographyManager.java | 91 ++-- .../node/app/tss/TssDirectoryAccessor.java | 18 + .../hedera/node/app/tss/TssKeyingStatus.java | 44 ++ .../hedera/node/app/tss/TssKeysAccessor.java | 6 +- .../com/hedera/node/app/tss/TssStatus.java | 26 ++ .../app/tss/handlers/TssMessageHandler.java | 7 +- .../node/app/tss/handlers/TssSubmissions.java | 50 ++- .../node/app/tss/handlers/TssUtils.java | 56 ++- .../node/app/tss/stores/ReadableTssStore.java | 52 ++- .../app/tss/stores/ReadableTssStoreImpl.java | 16 +- .../app/workflows/handle/HandleWorkflow.java | 53 ++- .../handle/steps/StakePeriodChanges.java | 6 +- .../node/app/tss/TssBaseServiceImplTest.java | 420 +++++++++++++++++- .../node/app/tss/TssBaseServiceTest.java | 9 +- .../app/tss/TssCryptographyManagerTest.java | 28 +- .../tss/handlers/TssMessageHandlerTest.java | 10 +- .../app/tss/handlers/TssSubmissionsTest.java | 5 +- .../node/app/tss/handlers/TssUtilsTest.java | 19 +- .../app/tss/stores/ReadableTssStoreTest.java | 51 +++ .../handle/steps/NodeStakeUpdatesTest.java | 5 + .../steps/PlatformStateUpdatesTest.java | 4 +- .../embedded/fakes/FakeTssBaseService.java | 9 +- .../hip423/ScheduleLongTermCreateTests.java | 11 - 27 files changed, 1259 insertions(+), 192 deletions(-) create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/RosterToKey.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java create mode 100644 hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssStatus.java diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/RosterToKey.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/RosterToKey.java new file mode 100644 index 000000000000..99c7555d4342 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/RosterToKey.java @@ -0,0 +1,37 @@ +/* + * 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; +/** + * An enum representing the key either active roster or candidate roster. + * This value will be to key active roster if it is genesis stage. + */ +public enum RosterToKey { + /** + * Key the active roster. This is true when we are keying roster on genesis stage. + */ + ACTIVE_ROSTER, + + /** + * Key the candidate roster. This is true when we are keying roster on non-genesis stage. + */ + CANDIDATE_ROSTER, + + /** + * Key none of the roster. This is true when we are not keying any roster. + */ + NONE +} 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 index 69f39eaf4dd8..b14cc9e58f84 100644 --- 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 @@ -21,6 +21,7 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.services.ServiceMigrator; +import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.tss.handlers.TssHandlers; import com.hedera.node.app.tss.stores.ReadableTssStoreImpl; @@ -186,7 +187,18 @@ Roster chooseRosterForNetwork( /** * Manages and does work based on the TSS status. - * @param state the network state + * It is called each second and computes the TSS status, based on the network state. + * If the self-node has any pending TSS submissions that can help progress the TSS Status, then it will + * submit them. + * + * @param state the network state + * @param isStakePeriodBoundary whether the current consensus round is a stake period boundary + * @param consensusNow the current consensus time + * @param storeMetricsService the store metrics service */ - void manageTssStatus(State state); + void manageTssStatus( + final State state, + final boolean isStakePeriodBoundary, + final Instant consensusNow, + final StoreMetricsService storeMetricsService); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceComponent.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceComponent.java index eef8c1743f12..f31d293f32c8 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceComponent.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceComponent.java @@ -57,4 +57,6 @@ TssBaseServiceComponent create( TssKeysAccessor tssKeysAccessor(); TssDirectoryAccessor tssDirectoryAccessor(); + + TssCryptographyManager tssCryptographyManager(); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java index 5f43d1c2ca4c..aa0d2292f699 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssBaseServiceImpl.java @@ -17,10 +17,18 @@ package com.hedera.node.app.tss; import static com.hedera.node.app.hapi.utils.CommonUtils.noThrowSha384HashOf; +import static com.hedera.node.app.tss.RosterToKey.ACTIVE_ROSTER; +import static com.hedera.node.app.tss.RosterToKey.CANDIDATE_ROSTER; +import static com.hedera.node.app.tss.RosterToKey.NONE; import static com.hedera.node.app.tss.TssBaseService.Status.PENDING_LEDGER_ID; +import static com.hedera.node.app.tss.TssKeyingStatus.KEYING_COMPLETE; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_ENCRYPTION_KEYS; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_THRESHOLD_TSS_MESSAGES; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_THRESHOLD_TSS_VOTES; import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; import static com.hedera.node.app.tss.handlers.TssUtils.computeParticipantDirectory; import static com.hedera.node.app.tss.handlers.TssUtils.hasMetThreshold; +import static com.hedera.node.app.tss.handlers.TssUtils.voteForValidMessages; import static com.swirlds.platform.roster.RosterRetriever.getCandidateRosterHash; import static com.swirlds.platform.roster.RosterRetriever.retrieveActiveOrGenesisRoster; import static com.swirlds.platform.system.InitTrigger.GENESIS; @@ -32,13 +40,16 @@ import com.hedera.cryptography.tss.api.TssParticipantDirectory; import com.hedera.hapi.node.state.primitives.ProtoBytes; import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssShareSignatureTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.roster.schemas.V0540RosterSchema; import com.hedera.node.app.services.ServiceMigrator; import com.hedera.node.app.spi.AppContext; +import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.store.ReadableStoreFactory; import com.hedera.node.app.tss.api.FakeGroupElement; @@ -62,11 +73,14 @@ import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.spi.ReadableKVState; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.math.BigInteger; import java.time.Instant; import java.time.InstantSource; import java.util.LinkedHashMap; import java.util.List; +import java.util.Objects; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; @@ -74,7 +88,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.LongFunction; -import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -98,7 +111,21 @@ public class TssBaseServiceImpl implements TssBaseService { private final Executor signingExecutor; private final TssKeysAccessor tssKeysAccessor; private final TssDirectoryAccessor tssDirectoryAccessor; - private final Supplier configSupplier; + private final AppContext appContext; + private final TssCryptographyManager tssCryptographyManager; + // Indicates whether the current node has already submitted a tss message for the target roster. + // This is false by default and will be set to true when the node submits a message for the target roster. + // This is reset to false when we start keying a candidate roster + private boolean haveSentMessageForTargetRoster; + // Indicates whether the current node has already submitted a tss vote for the target roster. + // This is false by default and will be set to true when the node submits a vote for the target roster. + // This is reset to false when we start keying a candidate roster + private boolean haveSentVoteForTargetRoster; + // Indicates the current TssStatus of the network. + // This is used to determine the next steps in the TSS lifecycle. + // This is set to null by default and will be updated from state when each second is processed. + // This is also null when the network restarts or reconnects. + private TssStatus tssStatus; public TssBaseServiceImpl( @NonNull final AppContext appContext, @@ -111,7 +138,7 @@ public TssBaseServiceImpl( this.tssLibrary = requireNonNull(tssLibrary); this.signingExecutor = requireNonNull(signingExecutor); this.tssLibraryExecutor = requireNonNull(tssLibraryExecutor); - this.configSupplier = appContext.configSupplier(); + this.appContext = requireNonNull(appContext); final var component = DaggerTssBaseServiceComponent.factory() .create( tssLibrary, @@ -127,6 +154,7 @@ public TssBaseServiceImpl( this.tssHandlers = new TssHandlers( component.tssMessageHandler(), component.tssVoteHandler(), component.tssShareSignatureHandler()); this.tssSubmissions = component.tssSubmissions(); + this.tssCryptographyManager = component.tssCryptographyManager(); } @Override @@ -179,7 +207,7 @@ public void setCandidateRoster(@NonNull final Roster candidateRoster, @NonNull f final var candidateDirectory = computeParticipantDirectory(candidateRoster, maxSharesPerNode, encryptionKeyFn); final var activeRoster = requireNonNull( context.storeFactory().readableStore(ReadableRosterStore.class).getActiveRoster()); - final var activeRosterHash = RosterUtils.hash(activeRoster).getBytes(); + final var sourceRosterHash = RosterUtils.hash(activeRoster).getBytes(); final var tssPrivateShares = tssKeysAccessor.accessTssKeys().activeRosterShares(); @@ -191,7 +219,7 @@ public void setCandidateRoster(@NonNull final Roster candidateRoster, @NonNull f () -> { final var msg = tssLibrary.generateTssMessage(candidateDirectory, tssPrivateShare); final var tssMessage = TssMessageTransactionBody.newBuilder() - .sourceRosterHash(activeRosterHash) + .sourceRosterHash(sourceRosterHash) .targetRosterHash(candidateRosterHash) .shareIndex(shareIndex.getAndAdd(1)) .tssMessage(Bytes.wrap(msg.toBytes())) @@ -214,7 +242,11 @@ public void requestLedgerSignature( final var mockSignature = noThrowSha384HashOf(messageHash); CompletableFuture.runAsync( () -> { - if (configSupplier.get().getConfigData(TssConfig.class).signWithLedgerId()) { + if (appContext + .configSupplier() + .get() + .getConfigData(TssConfig.class) + .signWithLedgerId()) { submitShareSignatures(messageHash, lastUsedConsensusTime); } else { // This is only for testing purposes when the candidate roster is @@ -384,12 +416,364 @@ public TssMessage getTssMessageFromBytes(Bytes wrap, TssParticipantDirectory dir } @Override - public void manageTssStatus(final State state) { - // TODO: Implement this method + public void manageTssStatus( + final State state, + final boolean isStakePeriodBoundary, + final Instant consensusNow, + final StoreMetricsService storeMetricsService) { + if (!appContext.configSupplier().get().getConfigData(TssConfig.class).keyCandidateRoster()) { + return; + } + final var readableStoreFactory = new ReadableStoreFactory(state); + final var tssStore = readableStoreFactory.getStore(ReadableTssStore.class); + final var rosterStore = readableStoreFactory.getStore(ReadableRosterStore.class); + // If the Tss Status is not computed yet during restart or reconnect, compute it from state. + if (tssStatus == null) { + tssStatus = computeInitialTssStatus(tssStore, rosterStore); + } + + // In order for the TSS state machine to run asynchronously in a separate thread, all the necessary + // information is collected and passed to the manageTssStatus method. + final var targetRosterHash = getTargetRosterHash( + requireNonNull(rosterStore.getActiveRoster()), rosterStore.getCandidateRoster(), tssStatus); + + // collect tss encryption keys for all nodes in the active roster that are not null + final var targetRoster = rosterStore.get(targetRosterHash); + final List targetRosterEncryptionKeys = targetRoster == null + ? List.of() + : targetRoster.rosterEntries().stream() + .map(entry -> tssStore.getTssEncryptionKeys(entry.nodeId())) + .filter(Objects::nonNull) + .filter(k -> k.currentEncryptionKey().equals(Bytes.EMPTY)) + .toList(); + + final var voteKey = new TssVoteMapKey( + targetRosterHash, appContext.selfNodeInfoSupplier().get().nodeId()); + final var info = new RosterAndTssInfo( + rosterStore.getActiveRoster(), + requireNonNull(rosterStore.getCurrentRosterHash()), + rosterStore.getCandidateRoster(), + targetRosterHash, + tssStore.getMessagesForTarget(targetRosterHash), + tssStore.anyWinningVoteFrom(rosterStore.getCurrentRosterHash(), targetRosterHash, rosterStore), + targetRosterEncryptionKeys, + tssStore.getVote(voteKey)); + CompletableFuture.runAsync( + () -> updateTssStatus(isStakePeriodBoundary, consensusNow, info), tssLibraryExecutor); + } + + /** + * Computes the initial TssStatus when the network restarts or reconnects or on genesis. + * This is called only once when JVM restarts. + * + * @param tssStore the TSS store + * @param rosterStore the roster store + * @return the initial TssStatus + */ + TssStatus computeInitialTssStatus(final ReadableTssStore tssStore, final ReadableRosterStore rosterStore) { + final var activeRosterHash = requireNonNull(rosterStore.getCurrentRosterHash()); + final var candidateRoster = rosterStore.getCandidateRoster(); + final var candidateRosterHash = + candidateRoster != null ? RosterUtils.hash(candidateRoster).getBytes() : null; + + final var winningVoteActive = tssStore.anyWinningVoteFor(activeRosterHash, rosterStore); + if (winningVoteActive.isEmpty()) { + final var keyingStatus = getTssKeyingStatus(tssStore, activeRosterHash, rosterStore.getActiveRoster()); + return new TssStatus(keyingStatus, ACTIVE_ROSTER, Bytes.EMPTY); + } + + final var activeRosterLedgerId = winningVoteActive.get().ledgerId(); + if (candidateRosterHash != null) { + final var winningVoteCandidate = + tssStore.anyWinningVoteFrom(activeRosterHash, candidateRosterHash, rosterStore); + return winningVoteCandidate + .map(voteBody -> new TssStatus(KEYING_COMPLETE, NONE, voteBody.ledgerId())) + .orElseGet(() -> { + final var keyingStatus = + getTssKeyingStatus(tssStore, candidateRosterHash, rosterStore.getCandidateRoster()); + return new TssStatus(keyingStatus, CANDIDATE_ROSTER, activeRosterLedgerId); + }); + } + + return new TssStatus(KEYING_COMPLETE, NONE, activeRosterLedgerId); + } + + /** + * Verifies the current TSS status when computing initial status. + * + * @param tssStore the TSS store + * @param targetRosterHash the target roster hash + * @param targetRoster the target roster + * @return the TSS keying status + */ + private TssKeyingStatus getTssKeyingStatus( + final ReadableTssStore tssStore, final Bytes targetRosterHash, final Roster targetRoster) { + final var numEncryptionKeys = requireNonNull(targetRoster).rosterEntries().stream() + .map(entry -> tssStore.getTssEncryptionKeys(entry.nodeId())) + .filter(Objects::nonNull) + .filter(k -> !k.currentEncryptionKey().equals(Bytes.EMPTY)) + .count(); + if (numEncryptionKeys != targetRoster.rosterEntries().size()) { + return WAITING_FOR_ENCRYPTION_KEYS; + } + // Since this is called only once when JVM restarts, it is okay to do these synchronously. + final var activeDirectory = tssDirectoryAccessor.activeParticipantDirectory(); + final var tssMessages = tssStore.getMessagesForTarget(targetRosterHash); + final var result = voteForValidMessages(tssMessages, activeDirectory, tssLibrary); + if (result.isEmpty()) { + return WAITING_FOR_THRESHOLD_TSS_MESSAGES; + } else { + return WAITING_FOR_THRESHOLD_TSS_VOTES; + } + } + + /** + * Computes the next TSS status from the state. + * + * @param isStakePeriodBoundary whether the current consensus round is a stake period boundary + * @param consensusNow the current consensus time + * @param info the roster and TSS information + */ + void updateTssStatus(final boolean isStakePeriodBoundary, final Instant consensusNow, final RosterAndTssInfo info) { + final var statusChange = new StatusChange(isStakePeriodBoundary, consensusNow, info); + this.tssStatus = statusChange.computeNewStatus(); } @VisibleForTesting public TssKeysAccessor getTssKeysAccessor() { return tssKeysAccessor; } + + /** + * A class to manage the status change of the TSS. + * It computes the new status based on the old status and the current state of the system. + * If needed, it schedules work to generate TSS messages and votes. + */ + public class StatusChange { + private TssKeyingStatus newKeyingStatus; + private RosterToKey newRosterToKey; + private Bytes newLedgerId; + private final boolean isStakePeriodBoundary; + private final Instant consensusNow; + private final RosterAndTssInfo info; + + public StatusChange( + final boolean isStakePeriodBoundary, final Instant consensusNow, final RosterAndTssInfo info) { + this.isStakePeriodBoundary = isStakePeriodBoundary; + this.info = info; + this.newKeyingStatus = tssStatus.tssKeyingStatus(); + this.newRosterToKey = tssStatus.rosterToKey(); + this.newLedgerId = tssStatus.ledgerId(); + this.consensusNow = consensusNow; + } + + /** + * Computes the new status based on the old status and the current state of the system. + * If needed, it schedules work to generate TSS messages and votes. + * + * @return the new status + */ + public TssStatus computeNewStatus() { + switch (tssStatus.rosterToKey()) { + case NONE -> { + if (isStakePeriodBoundary) { + newRosterToKey = CANDIDATE_ROSTER; + newKeyingStatus = WAITING_FOR_ENCRYPTION_KEYS; + haveSentMessageForTargetRoster = false; + haveSentVoteForTargetRoster = false; + } + } + case CANDIDATE_ROSTER -> { + final var activeRosterHash = requireNonNull(info.activeRosterHash()); + final var candidateRosterHash = RosterUtils.hash(requireNonNull(info.candidateRoster())) + .getBytes(); + + switch (tssStatus.tssKeyingStatus()) { + case KEYING_COMPLETE -> newRosterToKey = NONE; + case WAITING_FOR_THRESHOLD_TSS_MESSAGES -> validateMessagesAndSubmitIfNeeded( + activeRosterHash, candidateRosterHash); + case WAITING_FOR_THRESHOLD_TSS_VOTES -> validateVotesAndSubmitIfNeeded( + activeRosterHash, candidateRosterHash); + case WAITING_FOR_ENCRYPTION_KEYS -> validateThresholdEncryptionKeysReached( + info.candidateRoster()); + } + } + case ACTIVE_ROSTER -> { + requireNonNull(info.activeRosterHash()); + switch (tssStatus.tssKeyingStatus()) { + case KEYING_COMPLETE -> newRosterToKey = NONE; + case WAITING_FOR_THRESHOLD_TSS_MESSAGES -> validateMessagesAndSubmitIfNeeded( + Bytes.EMPTY, info.activeRosterHash()); + case WAITING_FOR_THRESHOLD_TSS_VOTES -> validateVotesAndSubmitIfNeeded( + Bytes.EMPTY, info.activeRosterHash()); + case WAITING_FOR_ENCRYPTION_KEYS -> validateThresholdEncryptionKeysReached(info.activeRoster()); + } + } + } + return new TssStatus(newKeyingStatus, newRosterToKey, newLedgerId); + } + + /** + * Validates the votes and submits a vote for the current node if needed to reach the threshold. + * + * @param targetRosterHash the target roster hash + * @param sourceRosterHash the source roster hash + */ + private void validateVotesAndSubmitIfNeeded(final Bytes sourceRosterHash, final Bytes targetRosterHash) { + final var voteBodies = info.winningVote(); + if (voteBodies.isPresent()) { + newKeyingStatus = KEYING_COMPLETE; + newLedgerId = voteBodies.get().ledgerId(); + } else if (!haveSentVoteForTargetRoster && info.selfVote() == null) { + // Obtain the directory of participants for the source roster + final var directory = tssDirectoryAccessor.activeParticipantDirectory(); + final var vote = tssCryptographyManager.getVote(info.tssMessages(), directory); + if (vote != null) { + final var tssVote = TssVoteTransactionBody.newBuilder() + .tssVote(vote.bitSet()) + .sourceRosterHash(sourceRosterHash) + .targetRosterHash(targetRosterHash) + .ledgerId(vote.ledgerId()) + .nodeSignature(vote.signature().getBytes()) + .build(); + tssSubmissions.submitTssVote(tssVote, consensusNow); + haveSentVoteForTargetRoster = true; + } + } + } + + /** + * Validates the messages and submits a message for the current node if needed to reach the threshold. + * + * @param sourceRosterHash the source roster hash + * @param targetRosterHash the target roster hash + */ + private void validateMessagesAndSubmitIfNeeded(final Bytes sourceRosterHash, final Bytes targetRosterHash) { + final var thresholdReached = validateThresholdTssMessages(); + if (thresholdReached) { + newKeyingStatus = WAITING_FOR_THRESHOLD_TSS_VOTES; + } else if (!haveSentMessageForTargetRoster) { + if (tssStatus.rosterToKey() == ACTIVE_ROSTER) { + final var msg = tssLibrary.generateTssMessage(tssDirectoryAccessor.activeParticipantDirectory()); + final var tssMessage = TssMessageTransactionBody.newBuilder() + .sourceRosterHash(sourceRosterHash) + .targetRosterHash(targetRosterHash) + .shareIndex(appContext.selfNodeInfoSupplier().get().nodeId() + 1) + .tssMessage(Bytes.wrap(msg.toBytes())) + .build(); + // need to use consensusNow here + tssSubmissions.submitTssMessage(tssMessage, consensusNow); + haveSentMessageForTargetRoster = true; + } else if (tssStatus.rosterToKey() == CANDIDATE_ROSTER) { + // Obtain the directory of participants for the target roster + // submit ours and set haveSentMessageForTargetRoster to true + final var tssPrivateShares = tssKeysAccessor.accessTssKeys().activeRosterShares(); + for (final var tssPrivateShare : tssPrivateShares) { + final var msg = tssLibrary.generateTssMessage( + tssDirectoryAccessor.generateTssParticipantDirectoryFor(info.candidateRoster()), + tssPrivateShare); + final var tssMessage = TssMessageTransactionBody.newBuilder() + .sourceRosterHash(sourceRosterHash) + .targetRosterHash(targetRosterHash) + .shareIndex(tssPrivateShare.shareId()) + .tssMessage(Bytes.wrap(msg.toBytes())) + .build(); + // need to use consensusNow here + tssSubmissions.submitTssMessage(tssMessage, consensusNow); + haveSentMessageForTargetRoster = true; + } + } + } + } + + /** + * Validates the threshold of TSS messages. + * + * @return true if the threshold is met, false otherwise + */ + private boolean validateThresholdTssMessages() { + final var participantDirectory = tssDirectoryAccessor.activeParticipantDirectory(); + final var tssMessageBodies = info.tssMessages(); + return voteForValidMessages(tssMessageBodies, participantDirectory, tssLibrary) + .isPresent(); + } + + /** + * Validates the threshold of encryption keys and creates the encryption key for self if not present. + */ + private void validateThresholdEncryptionKeysReached(final Roster roster) { + var numTssEncryptionKeys = info.targetRosterEncryptionKeys().size(); + final var thresholdReached = numTssEncryptionKeys + >= (2 * requireNonNull(roster).rosterEntries().size()) / 3; + if (thresholdReached) { + newKeyingStatus = TssKeyingStatus.WAITING_FOR_THRESHOLD_TSS_MESSAGES; + } else { + // TODO: Create the encryption key for self if not present + } + } + } + + /** + * A record to hold the roster and TSS information that is needed to compute new TSS status. + * + * @param activeRoster the active roster + * @param activeRosterHash the active roster hash + * @param candidateRoster the candidate roster + * @param targetRosterHash the target roster hash + * @param tssMessages the TSS messages for the target roster + * @param winningVote the winning vote for the target roster + * @param targetRosterEncryptionKeys the encryption keys for the active roster + * @param selfVote the self vote for the current node + */ + public record RosterAndTssInfo( + @NonNull Roster activeRoster, + @NonNull Bytes activeRosterHash, + @Nullable Roster candidateRoster, + @NonNull Bytes targetRosterHash, + @NonNull List tssMessages, + @NonNull Optional winningVote, + @NonNull List targetRosterEncryptionKeys, + TssVoteTransactionBody selfVote) {} + + /** + * Returns the target roster hash based on the current TSS status roster to key. + * + * @param sourceRoster the active roster + * @param candidateRoster the candidate roster + * @param tssStatus the TSS status + * @return the target roster hash + */ + @NonNull + private Bytes getTargetRosterHash( + @NonNull final Roster sourceRoster, + @Nullable final Roster candidateRoster, + @NonNull final TssStatus tssStatus) { + final var rosterToKey = tssStatus.rosterToKey(); + return switch (rosterToKey) { + case ACTIVE_ROSTER -> RosterUtils.hash(requireNonNull(sourceRoster)).getBytes(); + case CANDIDATE_ROSTER -> RosterUtils.hash(requireNonNull(candidateRoster)) + .getBytes(); + case NONE -> Bytes.EMPTY; + }; + } + + @VisibleForTesting + public TssStatus getTssStatus() { + return tssStatus; + } + + @VisibleForTesting + public void setTssStatus(final TssStatus tssStatus) { + this.tssStatus = tssStatus; + } + + @VisibleForTesting + public boolean haveSentVoteForTargetRoster() { + return haveSentVoteForTargetRoster; + } + + @VisibleForTesting + public boolean haveSentMessageForTargetRoster() { + return haveSentMessageForTargetRoster; + } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java index 7d6fa982fbfe..122d0c89c096 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssCryptographyManager.java @@ -16,24 +16,21 @@ package com.hedera.node.app.tss; -import static com.hedera.node.app.tss.handlers.TssUtils.getThresholdForTssMessages; import static com.hedera.node.app.tss.handlers.TssUtils.getTssMessages; -import static com.hedera.node.app.tss.handlers.TssUtils.validateTssMessages; +import static com.hedera.node.app.tss.handlers.TssUtils.voteForValidMessages; import static java.util.Objects.requireNonNull; import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.tss.api.TssMessage; import com.hedera.cryptography.tss.api.TssParticipantDirectory; -import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.spi.AppContext; -import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.node.app.tss.stores.WritableTssStore; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Signature; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; import java.time.Duration; import java.time.InstantSource; import java.util.BitSet; @@ -91,21 +88,16 @@ public record Vote( * given hash, based on incorporating all available {@link TssMessage}s, if * the threshold number of messages are available. The signature is with the node's RSA key used for gossip. * - * @param targetRosterHash the hash of the target roster - * @param directory the TSS participant directory - * @param context the handle context to use in setting up the computation + * @param directory the TSS participant directory + * @param tssMessageBodies the list of TSS message bodies + * @param voteBody the vote body * @return a future resolving to the signed vote if given message passes the threshold, or null otherwise */ public CompletableFuture getVoteFuture( - @NonNull final Bytes targetRosterHash, @NonNull final TssParticipantDirectory directory, - @NonNull final HandleContext context) { - final var tssStore = context.storeFactory().writableStore(WritableTssStore.class); - final var tssMessageBodies = tssStore.getMessagesForTarget(targetRosterHash); - final var voteKey = new TssVoteMapKey( - targetRosterHash, context.networkInfo().selfNodeInfo().nodeId()); - // We only vote once for a given target roster hash - if (tssStore.getVote(voteKey) == null) { + @NonNull final List tssMessageBodies, + @Nullable final TssVoteTransactionBody voteBody) { + if (voteBody == null) { return computeVote(tssMessageBodies, directory).exceptionally(e -> { log.error("Error computing public keys and signing", e); return null; @@ -125,56 +117,27 @@ public CompletableFuture getVoteFuture( private CompletableFuture computeVote( @NonNull final List tssMessageBodies, @NonNull final TssParticipantDirectory tssParticipantDirectory) { - return CompletableFuture.supplyAsync( - () -> { - final var tssMessages = validateTssMessages(tssMessageBodies, tssParticipantDirectory, tssLibrary); - if (!isThresholdMet(tssMessages, tssParticipantDirectory)) { - return null; - } - final var aggregationStart = instantSource.instant(); - final var validTssMessages = getTssMessages(tssMessages, tssParticipantDirectory, tssLibrary); - final var publicShares = tssLibrary.computePublicShares(tssParticipantDirectory, validTssMessages); - final var ledgerId = tssLibrary.aggregatePublicShares(publicShares); - final var signature = gossip.sign(ledgerId.toBytes()); - final var thresholdMessages = asBitSet(tssMessages); - final var aggregationEnd = instantSource.instant(); - tssMetrics.updateAggregationTime( - Duration.between(aggregationStart, aggregationEnd).toMillis()); - return new Vote(ledgerId, signature, thresholdMessages); - }, - libraryExecutor); + return CompletableFuture.supplyAsync(() -> getVote(tssMessageBodies, tssParticipantDirectory), libraryExecutor); } - /** - * Compute the TSS vote bit set. No need to validate the TSS messages here as they have already been validated. - * - * @param thresholdMessages the valid TSS messages - * @return the TSS vote bit set - */ - private BitSet asBitSet(@NonNull final List thresholdMessages) { - // TODO - fix this, nodes vote for TSS messages based on their position - // in consensus order of messages received for a roster hash, NOT by - // the message's share index - final var tssVoteBitSet = new BitSet(); - for (TssMessageTransactionBody op : thresholdMessages) { - tssVoteBitSet.set((int) op.shareIndex()); + @Nullable + public Vote getVote( + final @NonNull List tssMessageBodies, + final @NonNull TssParticipantDirectory tssParticipantDirectory) { + final var result = voteForValidMessages(tssMessageBodies, tssParticipantDirectory, tssLibrary); + if (result.isEmpty()) { + return null; } - return tssVoteBitSet; - } - - /** - * Check if the threshold consensus weight is met to submit a {@link TssVoteTransactionBody}. - * The threshold is met if more than half the consensus weight has been received. - * - * @param validTssMessages the valid TSS messages - * @param tssParticipantDirectory the TSS participant directory - * @return true if the threshold is met, false otherwise - */ - private boolean isThresholdMet( - @NonNull final List validTssMessages, - @NonNull final TssParticipantDirectory tssParticipantDirectory) { - final var numShares = tssParticipantDirectory.getShareIds().size(); - // If more than 1/2 the consensus weight has been received, then the threshold is met - return validTssMessages.size() >= getThresholdForTssMessages(numShares); + final var aggregationStart = instantSource.instant(); + final var validTssMessages = + getTssMessages(result.get().validTssMessages(), tssParticipantDirectory, tssLibrary); + final var publicShares = tssLibrary.computePublicShares(tssParticipantDirectory, validTssMessages); + final var ledgerId = tssLibrary.aggregatePublicShares(publicShares); + final var signature = gossip.sign(ledgerId.toBytes()); + final var vote = result.get().vote(); + final var aggregationEnd = instantSource.instant(); + tssMetrics.updateAggregationTime( + Duration.between(aggregationStart, aggregationEnd).toMillis()); + return new Vote(ledgerId, signature, vote); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java index 088f071a38c1..c4fca0e45523 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssDirectoryAccessor.java @@ -22,6 +22,7 @@ import com.hedera.cryptography.bls.BlsPublicKey; import com.hedera.cryptography.tss.api.TssParticipantDirectory; +import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.spi.AppContext; import com.hedera.node.app.store.ReadableStoreFactory; import com.hedera.node.app.tss.api.FakeGroupElement; @@ -87,6 +88,23 @@ public TssParticipantDirectory activeParticipantDirectoryFrom( return tssParticipantDirectory; } + /** + * Generates the participant directory for the given roster. + * + * @param roster Roster to generate participant directory for + */ + public TssParticipantDirectory generateTssParticipantDirectoryFor(@NonNull final Roster roster) { + final LongFunction encryptionKeyFn = + nodeId -> new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(nodeId)), SIGNATURE_SCHEMA); + final var maxSharesPerNode = + configurationSupplier.get().getConfigData(TssConfig.class).maxSharesPerNode(); + return computeParticipantDirectory(roster, maxSharesPerNode, encryptionKeyFn); + } + + public TssParticipantDirectory activeParticipantDirectory() { + return tssParticipantDirectory; + } + /** * Returns the {@link TssParticipantDirectory} for the active roster. * @return the {@link TssParticipantDirectory} for the active roster diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java new file mode 100644 index 000000000000..6379ab07537b --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeyingStatus.java @@ -0,0 +1,44 @@ +/* + * 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; +/** + * An enum representing the status of the TSS keying process. + * This status SHALL be used to determine the state of the TSS keying process. + */ +public enum TssKeyingStatus { + + /** + * The TSS keying process has not yet reached the threshold for encryption + * keys. + */ + WAITING_FOR_ENCRYPTION_KEYS, + + /** + * The TSS keying process has not yet reached the threshold for TSS messages. + */ + WAITING_FOR_THRESHOLD_TSS_MESSAGES, + + /** + * The TSS keying process has not yet reached the threshold for TSS votes. + */ + WAITING_FOR_THRESHOLD_TSS_VOTES, + + /** + * The TSS keying process has completed and the ledger id is set. + */ + KEYING_COMPLETE +} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java index 2880a681d355..1e6fb02d2d44 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssKeysAccessor.java @@ -17,7 +17,7 @@ package com.hedera.node.app.tss; import static com.hedera.node.app.tss.handlers.TssUtils.getTssMessages; -import static com.hedera.node.app.tss.handlers.TssUtils.validateTssMessages; +import static com.hedera.node.app.tss.handlers.TssUtils.getValidMessages; import static java.util.Objects.requireNonNull; import com.google.common.annotations.VisibleForTesting; @@ -84,9 +84,9 @@ private List getTssPrivateShares( @NonNull final TssParticipantDirectory activeRosterParticipantDirectory, @NonNull final ReadableTssStore tssStore, @NonNull final Bytes activeRosterHash) { - final var validTssOps = validateTssMessages( + final var tssMessages = getValidMessages( tssStore.getMessagesForTarget(activeRosterHash), activeRosterParticipantDirectory, tssLibrary); - final var validTssMessages = getTssMessages(validTssOps, activeRosterParticipantDirectory, tssLibrary); + final var validTssMessages = getTssMessages(tssMessages, activeRosterParticipantDirectory, tssLibrary); return tssLibrary.decryptPrivateShares(activeRosterParticipantDirectory, validTssMessages); } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssStatus.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssStatus.java new file mode 100644 index 000000000000..25e6aa686f06 --- /dev/null +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/TssStatus.java @@ -0,0 +1,26 @@ +/* + * 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.hedera.pbj.runtime.io.buffer.Bytes; +import edu.umd.cs.findbugs.annotations.NonNull; + +/** + * A Singleton state object that represents the status of the TSS keying process. + * This key SHALL be used to determine the stage of the TSS keying process. + */ +public record TssStatus(TssKeyingStatus tssKeyingStatus, RosterToKey rosterToKey, @NonNull Bytes ledgerId) {} diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java index 1e5489cd7bb3..09e13b84c340 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssMessageHandler.java @@ -19,6 +19,7 @@ import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.tss.TssMessageMapKey; +import com.hedera.hapi.node.state.tss.TssVoteMapKey; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; @@ -86,8 +87,12 @@ public void handle(@NonNull final HandleContext context) throws HandleException final var directory = tssDirectoryAccessor.activeParticipantDirectoryOrThrow(); // Schedule work to potentially compute a signed vote for the new key material of the target // roster, if this message was valid and passed the threshold number of messages required + final var selfNodeId = context.networkInfo().selfNodeInfo().nodeId(); + final var tssMessageBodies = tssStore.getMessagesForTarget(targetRosterHash); + final var voteKey = new TssVoteMapKey(targetRosterHash, selfNodeId); + final var voteBody = tssStore.getVote(voteKey); tssCryptographyManager - .getVoteFuture(op.targetRosterHash(), directory, context) + .getVoteFuture(directory, tssMessageBodies, voteBody) .thenAccept(vote -> { if (vote != null) { // FUTURE: Validate the ledgerId computed is same as the current ledgerId diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssSubmissions.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssSubmissions.java index 79b821a4525a..926d67bdf23d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssSubmissions.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssSubmissions.java @@ -81,43 +81,67 @@ public TssSubmissions(@NonNull final AppContext appContext, @NonNull final Execu /** * Attempts to submit a TSS message to the network. * - * @param body the TSS message to submit + * @param body the TSS message to submit * @param context the TSS context * @return a future that completes when the message has been submitted */ public CompletableFuture submitTssMessage( @NonNull final TssMessageTransactionBody body, @NonNull final HandleContext context) { + return submitTssMessage(body, nextValidStartFor(context)); + } + + /** + * Attempts to submit a TSS message to the network. + * + * @param body the TSS message to submit + * @param lastUsedConsensusTime the TSS context + * @return a future that completes when the message has been submitted + */ + public CompletableFuture submitTssMessage( + @NonNull final TssMessageTransactionBody body, @NonNull final Instant lastUsedConsensusTime) { requireNonNull(body); - requireNonNull(context); + requireNonNull(lastUsedConsensusTime); return submit( b -> b.tssMessage(body), - context.configuration(), - context.networkInfo().selfNodeInfo().accountId(), - nextValidStartFor(context)); + appContext.configSupplier().get(), + appContext.selfNodeInfoSupplier().get().accountId(), + lastUsedConsensusTime); } /** * Attempts to submit a TSS vote to the network. * - * @param body the TSS vote to submit - * @param context the TSS context + * @param body the TSS vote to submit + * @param context the TSS context * @return a future that completes when the vote has been submitted */ public CompletableFuture submitTssVote( - @NonNull final TssVoteTransactionBody body, @NonNull final HandleContext context) { + @NonNull final TssVoteTransactionBody body, final HandleContext context) { + return submitTssVote(body, nextValidStartFor(context)); + } + + /** + * Attempts to submit a TSS vote to the network. + * + * @param body the TSS vote to submit + * @param lastUsedConsensusTime the + * @return a future that completes when the vote has been submitted + */ + public CompletableFuture submitTssVote( + @NonNull final TssVoteTransactionBody body, final Instant lastUsedConsensusTime) { requireNonNull(body); - requireNonNull(context); + requireNonNull(lastUsedConsensusTime); return submit( b -> b.tssVote(body), - context.configuration(), - context.networkInfo().selfNodeInfo().accountId(), - nextValidStartFor(context)); + appContext.configSupplier().get(), + appContext.selfNodeInfoSupplier().get().accountId(), + lastUsedConsensusTime); } /** * Attempts to submit a TSS share signature to the network. * - * @param body the TSS share signature to submit + * @param body the TSS share signature to submit * @param lastUsedConsensusTime the last used consensus time * @return a future that completes when the share signature has been submitted */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java index befa5d0ed375..94211209b0a0 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/handlers/TssUtils.java @@ -28,15 +28,18 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.tss.api.FakeGroupElement; import com.hedera.node.app.tss.api.TssLibrary; import com.hedera.node.internal.network.Network; import edu.umd.cs.findbugs.annotations.NonNull; import java.math.BigInteger; +import java.util.BitSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.LongFunction; public class TssUtils { @@ -90,7 +93,7 @@ public static TssParticipantDirectory computeParticipantDirectory( } /** - * Compute the threshold of consensus weight needed for submitting a {@link com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody} + * Compute the threshold of consensus weight needed for submitting a {@link TssVoteTransactionBody} * If more than 1/2 the consensus weight has been received, then the threshold is met * * @param totalShares the total number of shares @@ -102,20 +105,55 @@ public static int getThresholdForTssMessages(final long totalShares) { /** * Validate TSS messages using the TSS library. If the message is valid, add it to the list of valid TSS messages. + * If the threshold is met, return the list of valid TSS messages and the vote bit set. * * @param tssMessages list of TSS messages to validate * @param tssParticipantDirectory the participant directory * @return list of valid TSS messages */ - public static List validateTssMessages( + public static Optional voteForValidMessages( @NonNull final List tssMessages, @NonNull final TssParticipantDirectory tssParticipantDirectory, @NonNull final TssLibrary tssLibrary) { + final var threshold = + getThresholdForTssMessages(tssParticipantDirectory.getShareIds().size()); + int numValidMessages = 0; final var validTssMessages = new LinkedList(); - for (final var op : tssMessages) { - final var isValid = tssLibrary.verifyTssMessage(tssParticipantDirectory, op.tssMessage()); + final var bitSet = new BitSet(); + for (int i = 0; i < tssMessages.size(); i++) { + final var isValid = tssLibrary.verifyTssMessage( + tssParticipantDirectory, tssMessages.get(i).tssMessage()); if (isValid) { - validTssMessages.add(op); + bitSet.set(i); + validTssMessages.add(tssMessages.get(i)); + numValidMessages++; + } + if (numValidMessages >= threshold) { + return Optional.of(new ValidMessagesWithVote(validTssMessages, bitSet)); + } + } + return Optional.empty(); + } + + /** + * Validate TSS messages using the TSS library. If the message is valid, add it to the list of valid TSS messages. + * + * @param tssMessages list of TSS messages to validate + * @param tssParticipantDirectory the participant directory + * @return list of valid TSS messages + */ + public static List getValidMessages( + @NonNull final List tssMessages, + @NonNull final TssParticipantDirectory tssParticipantDirectory, + @NonNull final TssLibrary tssLibrary) { + final var validTssMessages = new LinkedList(); + final var bitSet = new BitSet(); + for (int i = 0; i < tssMessages.size(); i++) { + final var isValid = tssLibrary.verifyTssMessage( + tssParticipantDirectory, tssMessages.get(i).tssMessage()); + if (isValid) { + bitSet.set(i); + validTssMessages.add(tssMessages.get(i)); } } return validTssMessages; @@ -155,7 +193,8 @@ public static Map computeNodeShares( /** * Compute the number of shares each node should have based on the weight of the node. - * @param weights the map of node ID to weight + * + * @param weights the map of node ID to weight * @param maxShares the maximum number of shares * @return a map of node ID to the number of shares */ @@ -175,11 +214,14 @@ public static Map computeSharesFromWeights( /** * Returns whether a vote bitset with the given weight has met the threshold for a roster with the given * total weight. - * @param voteWeight the weight of the vote bitset + * + * @param voteWeight the weight of the vote bitset * @param totalWeight the total weight of the roster * @return true if the threshold has been met, false otherwise */ public static boolean hasMetThreshold(final long voteWeight, final long totalWeight) { return voteWeight >= (totalWeight + 2) / 3; } + + public record ValidMessagesWithVote(List validTssMessages, BitSet vote) {} } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java index e00ace87359c..933aace5fc8d 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStore.java @@ -30,6 +30,7 @@ import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.util.BitSet; +import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.function.LongUnaryOperator; @@ -38,8 +39,9 @@ public interface ReadableTssStore { /** * The selected TSS messages and implied ledger id for some roster. + * * @param tssMessages the selected TSS messages - * @param ledgerId the implied ledger id + * @param ledgerId the implied ledger id */ record RosterKeys(@NonNull List tssMessages, @NonNull Bytes ledgerId) { public RosterKeys { @@ -53,7 +55,7 @@ record RosterKeys(@NonNull List tssMessages, @NonNull * * @param sourceRosterHash the source roster hash * @param targetRosterHash the target roster hash - * @param rosterStore the roster store + * @param rosterStore the roster store */ default Optional consensusRosterKeys( @NonNull final Bytes sourceRosterHash, @@ -79,7 +81,7 @@ default Optional consensusRosterKeys( * * @param sourceRosterHash the source roster hash * @param targetRosterHash the target roster hash - * @param rosterStore the roster store + * @param rosterStore the roster store * @return the roster keys, if available */ default Optional anyWinningVoteFrom( @@ -107,14 +109,44 @@ default Optional anyWinningVoteFrom( return anyWinningVoteFrom(sourceRosterHash, targetRosterHash, sourceRosterWeight, nodeWeightFn); } + /** + * If present, returns one of the winning votes from the given target roster hash. + * This iterates through all the votes for the target roster hash and returns the first one that is valid for + * any source roster. + * + * @param targetRosterHash the target roster hash + * @param rosterStore the roster store + * @return the winning vote, if present + */ + default Optional anyWinningVoteFor( + @NonNull final Bytes targetRosterHash, @NonNull final ReadableRosterStore rosterStore) { + requireNonNull(targetRosterHash); + requireNonNull(rosterStore); + final var possibleSourceRosters = new HashSet(); + final var votesForTargetRoster = allVotes().stream() + .filter(vote -> targetRosterHash.equals(vote.targetRosterHash())) + .toList(); + for (final var vote : votesForTargetRoster) { + possibleSourceRosters.add(vote.sourceRosterHash()); + } + + for (final var sourceRosterHash : possibleSourceRosters) { + final var vote = anyWinningVoteFrom(sourceRosterHash, targetRosterHash, rosterStore); + if (vote.isPresent()) { + return vote; + } + } + return Optional.empty(); + } + /** * If present, returns one of the winning votes from the given source roster hash for the keys of the target roster, * using the given total weight and per-node weight for the source roster. There is no guarantee of ordering between * multiple winning votes. * - * @param sourceRosterHash the source roster hash the vote must be from - * @param targetRosterHash the target roster hash the vote must be for - * @param sourceRosterWeight the total weight of the source the vote must be from + * @param sourceRosterHash the source roster hash the vote must be from + * @param targetRosterHash the target roster hash the vote must be for + * @param sourceRosterWeight the total weight of the source the vote must be from * @param sourceRosterWeightFn a function that returns the weight of a node in the source roster given its id * @return a winning vote, if present */ @@ -148,6 +180,13 @@ Optional anyWinningVoteFrom( */ TssVoteTransactionBody getVote(@NonNull TssVoteMapKey tssVoteMapKey); + /** + * Get all TSS votes in the store. This is needed for a specific case when we are checking the status + * for the keying of an active roster. + * @return The list of TSS votes. + */ + List allVotes(); + /** * Check if a TSS vote exists for the given key. * @@ -158,6 +197,7 @@ Optional anyWinningVoteFrom( /** * Get the list of Tss messages for the given roster hash. + * * @param rosterHash The roster hash to look up. * @return The list of Tss messages, or an empty list if not found. */ diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java index 4f59c15a323b..0fb99c9ab08c 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/tss/stores/ReadableTssStoreImpl.java @@ -21,12 +21,10 @@ import static com.hedera.node.app.tss.schemas.V0560TssBaseSchema.TSS_VOTE_MAP_KEY; import static com.hedera.node.app.tss.schemas.V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY; import static java.util.Objects.requireNonNull; -import static java.util.Spliterator.NONNULL; -import static java.util.Spliterators.spliterator; import static java.util.stream.Collectors.groupingBy; import static java.util.stream.Collectors.toList; -import static java.util.stream.StreamSupport.stream; +import com.google.common.collect.Streams; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.tss.TssEncryptionKeys; import com.hedera.hapi.node.state.tss.TssMessageMapKey; @@ -76,7 +74,7 @@ public Optional anyWinningVoteFrom( requireNonNull(sourceRosterHash); requireNonNull(targetRosterHash); requireNonNull(sourceRosterWeightFn); - return stream(spliterator(readableTssVoteState.keys(), readableTssVoteState.size(), NONNULL), false) + return Streams.stream(readableTssVoteState.keys()) .filter(key -> targetRosterHash.equals(key.rosterHash())) .map(key -> new WeightedVote( sourceRosterWeightFn.applyAsLong(key.nodeId()), requireNonNull(readableTssVoteState.get(key)))) @@ -124,6 +122,16 @@ public TssVoteTransactionBody getVote(@NonNull final TssVoteMapKey tssVoteKey) { return readableTssVoteState.get(tssVoteKey); } + /** + * {@inheritDoc} + */ + @Override + public List allVotes() { + return Streams.stream(readableTssVoteState.keys()) + .map(readableTssVoteState::get) + .toList(); + } + /** * {@inheritDoc} */ 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 69f608052b57..dadd26d316ab 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 @@ -253,6 +253,7 @@ public void handleRound(@NonNull final State state, @NonNull final Round round) /** * Applies all effects of the events in the given round to the given state, writing stream items * that capture these effects in the process. + * * @param state the state to apply the effects to * @param round the round to apply the effects of */ @@ -319,9 +320,9 @@ private void handleEvents(@NonNull final State state, @NonNull final Round round * executing the workflow for the transaction. This produces a stream of records that are then passed to the * {@link BlockRecordManager} to be externalized. * - * @param state the writable {@link State} that this transaction will work on - * @param creator the {@link NodeInfo} of the creator of the transaction - * @param txn the {@link ConsensusTransaction} to be handled + * @param state the writable {@link State} that this transaction will work on + * @param creator the {@link NodeInfo} of the creator of the transaction + * @param txn the {@link ConsensusTransaction} to be handled * @param txnVersion the software version for the event containing the transaction */ private void handlePlatformTransaction( @@ -396,11 +397,12 @@ private void handlePlatformTransaction( * As a side effect on the workflow internal state, updates the {@link BlockStreamManager}'s last interval process * time to the latest time known to have been processed; and the {@link #lastExecutedSecond} value to the last * second of the interval for which all scheduled transactions were executed. - * @param state the state to execute scheduled transactions from + * + * @param state the state to execute scheduled transactions from * @param executionStart the start of the interval to execute transactions in - * @param consensusNow the consensus time at which the user transaction triggering this execution was processed - * @param creatorInfo the node info of the user transaction creator - * @param type the type of the user transaction triggering this execution + * @param consensusNow the consensus time at which the user transaction triggering this execution was processed + * @param creatorInfo the node info of the user transaction creator + * @param type the type of the user transaction triggering this execution */ private void executeAsManyScheduled( @NonNull final State state, @@ -481,9 +483,9 @@ private void executeAsManyScheduled( * Type inference helper to compute the base builder for a {@link UserTxn} derived from a * {@link ExecutableTxn}. * - * @param the type of the stream builder + * @param the type of the stream builder * @param executableTxn the executable transaction to compute the base builder for - * @param userTxn the user transaction derived from the executable transaction + * @param userTxn the user transaction derived from the executable transaction * @return the base builder for the user transaction */ private T baseBuilderFor( @@ -496,9 +498,10 @@ private T baseBuilderFor( * Purges all service state used for scheduling work that was expired by the last time the purge * was triggered; but is not expired at the current time. Returns true if the last purge time * should be set to the current time. + * * @param state the state to purge - * @param then the last time the purge was triggered - * @param now the current time + * @param then the last time the purge was triggered + * @param now the current time */ private void purgeScheduling(@NonNull final State state, final Instant then, final Instant now) { if (!Instant.EPOCH.equals(then) && then.getEpochSecond() < now.getEpochSecond()) { @@ -519,7 +522,8 @@ private void purgeScheduling(@NonNull final State state, final Instant then, fin * there is an internal error when executing the transaction, returns stream output of * just the transaction with a {@link ResponseCodeEnum#FAIL_INVALID} transaction result, * and no other side effects. - * @param userTxn the user transaction to execute + * + * @param userTxn the user transaction to execute * @param txnVersion the software version for the event containing the transaction * @return the stream output from executing the transaction */ @@ -615,7 +619,8 @@ private HandleOutput executeTopLevel(@NonNull final UserTxn userTxn, @NonNull fi * there is an internal error when executing the transaction, returns stream output of just the * scheduled transaction with a {@link ResponseCodeEnum#FAIL_INVALID} transaction result, and * no other side effects. - * @param state the state to execute the transaction against + * + * @param state the state to execute the transaction against * @param consensusNow the time to execute the transaction at * @return the stream output from executing the transaction */ @@ -649,7 +654,8 @@ private HandleOutput executeScheduled( /** * Manages time-based side effects for the given user transaction and dispatch. - * @param userTxn the user transaction to manage time for + * + * @param userTxn the user transaction to manage time for * @param dispatch the dispatch to manage time for */ private void advanceTimeFor(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { @@ -657,8 +663,10 @@ private void advanceTimeFor(@NonNull final UserTxn userTxn, @NonNull final Dispa // correctly detect stake period boundary, so the order of the following two lines is important processStakePeriodChanges(userTxn, dispatch); if (isNextSecond(userTxn.consensusNow(), blockStreamManager.lastHandleTime())) { - // Check if the tss encryption keys are present in the state and reached threshold - tssBaseService.manageTssStatus(userTxn.stack()); + // Check the tss status and manage it if necessary + final var isStakePeriodBoundary = processStakePeriodChanges(userTxn, dispatch); + tssBaseService.manageTssStatus( + userTxn.stack(), isStakePeriodBoundary, userTxn.consensusNow(), storeMetricsService); } blockStreamManager.setLastHandleTime(userTxn.consensusNow()); if (streamMode != BLOCKS) { @@ -670,9 +678,10 @@ private void advanceTimeFor(@NonNull final UserTxn userTxn, @NonNull final Dispa /** * Commits an action with side effects while capturing its key/value state changes and writing them to the * block stream. + * * @param writableStates the writable states to commit the action to - * @param now the consensus timestamp of the action - * @param action the action to commit + * @param now the consensus timestamp of the action + * @param action the action to commit */ private void doStreamingKVChanges( @NonNull final WritableStates writableStates, @NonNull final Instant now, @NonNull final Runnable action) { @@ -794,12 +803,13 @@ public static StreamBuilder initializeBuilderInfo( /** * Processes any side effects of crossing a stake period boundary. - * @param userTxn the user transaction that crossed the boundary + * + * @param userTxn the user transaction that crossed the boundary * @param dispatch the dispatch for the user transaction that crossed the boundary */ - private void processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { + private boolean processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNull final Dispatch dispatch) { try { - stakePeriodChanges.process( + return stakePeriodChanges.process( dispatch, userTxn.stack(), userTxn.tokenContextImpl(), @@ -812,6 +822,7 @@ private void processStakePeriodChanges(@NonNull final UserTxn userTxn, @NonNull // get back to user transactions logger.error("Failed to process stake period changes", e); } + return false; } private static void logPreDispatch(@NonNull final UserTxn userTxn) { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/StakePeriodChanges.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/StakePeriodChanges.java index 43249ff67cf6..150e932418ad 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/StakePeriodChanges.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/workflows/handle/steps/StakePeriodChanges.java @@ -99,7 +99,7 @@ public StakePeriodChanges( * @param isGenesis whether the current transaction is the genesis transaction * @param lastHandleTime the last instant at which a transaction was handled */ - public void process( + public boolean process( @NonNull final Dispatch dispatch, @NonNull final SavepointStackImpl stack, @NonNull final TokenContext tokenContext, @@ -111,7 +111,8 @@ public void process( requireNonNull(tokenContext); requireNonNull(streamMode); requireNonNull(lastHandleTime); - if (isGenesis || isStakingPeriodBoundary(streamMode, tokenContext, lastHandleTime)) { + final var isStakePeriodBoundary = isStakingPeriodBoundary(streamMode, tokenContext, lastHandleTime); + if (isGenesis || isStakePeriodBoundary) { try { exchangeRateManager.updateMidnightRates(stack); stack.commitSystemStateChanges(); @@ -141,6 +142,7 @@ public void process( startKeyingCandidateRoster(dispatch.handleContext(), newWritableRosterStore(stack, config)); } } + return !isGenesis && isStakePeriodBoundary; } private boolean isStakingPeriodBoundary( diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java index 9d427f642505..62a2a7527509 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssBaseServiceImplTest.java @@ -16,39 +16,106 @@ package com.hedera.node.app.tss; +import static com.hedera.node.app.tss.RosterToKey.CANDIDATE_ROSTER; +import static com.hedera.node.app.tss.TssKeyingStatus.KEYING_COMPLETE; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_ENCRYPTION_KEYS; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_THRESHOLD_TSS_MESSAGES; +import static com.hedera.node.app.tss.TssKeyingStatus.WAITING_FOR_THRESHOLD_TSS_VOTES; +import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; +import static com.hedera.node.app.workflows.handle.steps.PlatformStateUpdatesTest.ROSTER_STATE; import static com.hedera.node.app.workflows.standalone.TransactionExecutors.DEFAULT_NODE_INFO; +import static com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema.ROSTER_KEY; +import static com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema.ROSTER_STATES_KEY; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.mockito.internal.verification.VerificationModeFactory.times; +import com.hedera.cryptography.bls.BlsPrivateKey; +import com.hedera.cryptography.bls.BlsPublicKey; +import com.hedera.cryptography.tss.api.TssMessage; +import com.hedera.cryptography.tss.api.TssPrivateShare; +import com.hedera.cryptography.tss.api.TssPublicShare; +import com.hedera.hapi.node.state.primitives.ProtoBytes; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; +import com.hedera.hapi.node.state.roster.RosterState; +import com.hedera.hapi.node.state.roster.RoundRosterPair; +import com.hedera.hapi.node.state.tss.TssEncryptionKeys; +import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; +import com.hedera.node.app.fixtures.state.FakeState; +import com.hedera.node.app.roster.RosterService; import com.hedera.node.app.spi.AppContext; +import com.hedera.node.app.tss.api.FakeGroupElement; +import com.hedera.node.app.tss.api.TssLibrary; import com.hedera.node.app.tss.schemas.TssBaseTransplantSchema; import com.hedera.node.app.tss.schemas.V0560TssBaseSchema; import com.hedera.node.app.tss.schemas.V0580TssBaseSchema; +import com.hedera.node.app.tss.stores.ReadableTssStore; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.crypto.Signature; +import com.swirlds.common.crypto.SignatureType; +import com.swirlds.common.metrics.noop.NoOpMetrics; import com.swirlds.metrics.api.Metrics; +import com.swirlds.platform.state.service.ReadableRosterStore; +import com.swirlds.state.State; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; +import java.math.BigInteger; +import java.security.SecureRandom; import java.time.Instant; import java.time.InstantSource; import java.util.ArrayList; +import java.util.BitSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; +import java.util.stream.IntStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) class TssBaseServiceImplTest { + private static final Bytes SOURCE_HASH = Bytes.wrap("SOURCE"); + private static final Bytes TARGET_HASH = Bytes.wrap("TARGET"); + private static final Roster SOURCE_ROSTER = Roster.newBuilder() + .rosterEntries( + new RosterEntry(0L, 4L, Bytes.EMPTY, List.of()), + new RosterEntry(1L, 3L, Bytes.EMPTY, List.of()), + new RosterEntry(2L, 2L, Bytes.EMPTY, List.of())) + .build(); + private static final Roster TARGET_ROSTER = Roster.newBuilder() + .rosterEntries( + new RosterEntry(0L, 1L, Bytes.EMPTY, List.of()), + new RosterEntry(1L, 2L, Bytes.EMPTY, List.of()), + new RosterEntry(2L, 3L, Bytes.EMPTY, List.of()), + new RosterEntry(3L, 4L, Bytes.EMPTY, List.of())) + .build(); + final BlsPublicKey FAKE_PUBLIC_KEY = + new BlsPublicKey(new FakeGroupElement(BigInteger.valueOf(10L)), SIGNATURE_SCHEMA); + final BlsPrivateKey FAKE_PRIVATE_KEY = BlsPrivateKey.create(SIGNATURE_SCHEMA, new SecureRandom()); + private static final Signature FAKE_SIGNATURE = new Signature(SignatureType.RSA, new byte[384]); + private final TssMessage TSS_MESSAGE = () -> "test".getBytes(); + private CountDownLatch latch; private final List receivedMessageHashes = new ArrayList<>(); private final List receivedSignatures = new ArrayList<>(); @@ -69,22 +136,58 @@ class TssBaseServiceImplTest { @Mock(strictness = Mock.Strictness.LENIENT) private AppContext appContext; - @Mock - private Metrics metrics; + @Mock(strictness = Mock.Strictness.LENIENT) + private ReadableRosterStore rosterStore; + + @Mock(strictness = Mock.Strictness.LENIENT) + private ReadableTssStore tssStore; + + @Mock(strictness = Mock.Strictness.LENIENT) + private TssLibrary tssLibrary; + private Metrics metrics = new NoOpMetrics(); + private State state; private TssBaseServiceImpl subject; @BeforeEach void setUp() { + final ConcurrentHashMap rosters = new ConcurrentHashMap<>(); + final AtomicReference rosterStateBackingStore = new AtomicReference<>(ROSTER_STATE); + rosterStateBackingStore.set(RosterState.newBuilder() + .roundRosterPairs(List.of(RoundRosterPair.newBuilder() + .activeRosterHash(SOURCE_HASH) + .roundNumber(1) + .build())) + .candidateRosterHash(TARGET_HASH) + .build()); + rosters.put(ProtoBytes.newBuilder().value(SOURCE_HASH).build(), SOURCE_ROSTER); + rosters.put(ProtoBytes.newBuilder().value(TARGET_HASH).build(), TARGET_ROSTER); + state = new FakeState() + .addService( + RosterService.NAME, + Map.of( + ROSTER_STATES_KEY, rosterStateBackingStore, + ROSTER_KEY, rosters)) + .addService( + TssBaseService.NAME, + Map.of( + V0580TssBaseSchema.TSS_ENCRYPTION_KEYS_KEY, + new HashMap<>(), + V0560TssBaseSchema.TSS_MESSAGE_MAP_KEY, + new HashMap<>(), + V0560TssBaseSchema.TSS_VOTE_MAP_KEY, + new HashMap<>())); + given(appContext.gossip()).willReturn(gossip); given(appContext.instantSource()).willReturn(InstantSource.system()); given(appContext.configSupplier()).willReturn(HederaTestConfigBuilder::createConfig); given(appContext.selfNodeInfoSupplier()).willReturn(() -> DEFAULT_NODE_INFO); + subject = new TssBaseServiceImpl( appContext, ForkJoinPool.commonPool(), ForkJoinPool.commonPool(), - new TssLibraryImpl(appContext), + tssLibrary, ForkJoinPool.commonPool(), metrics); } @@ -124,4 +227,315 @@ void placeholderRegistersSchemas() { assertThat(schemas.getLast()).isInstanceOf(V0580TssBaseSchema.class); assertThat(schemas.getLast()).isInstanceOf(TssBaseTransplantSchema.class); } + + @Test + void managesTssStatusWhenRosterToKeyIsNone() { + final var oldStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, RosterToKey.NONE, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(WAITING_FOR_ENCRYPTION_KEYS, CANDIDATE_ROSTER, Bytes.EMPTY); + subject.setTssStatus(oldStatus); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + SOURCE_ROSTER, + SOURCE_HASH, + List.of(), + Optional.empty(), + List.of(), + null)); + assertEquals(expectedTssStatus, subject.getTssStatus()); + } + + @ParameterizedTest + @EnumSource( + value = RosterToKey.class, + names = {"ACTIVE_ROSTER", "CANDIDATE_ROSTER"}) + void managesTssStatusWhenMessagesReachedThreshold(RosterToKey rosterToKey) { + final var messages = IntStream.range(0, 8) + .mapToObj(i -> + new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, i * 2L + 1, Bytes.wrap("MESSAGE" + i))) + .toList(); + final var oldStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, rosterToKey, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_VOTES, rosterToKey, Bytes.EMPTY); + subject.setTssStatus(oldStatus); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + + subject.ensureParticipantDirectoryKnown(state); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + SOURCE_ROSTER, + SOURCE_HASH, + messages, + Optional.empty(), + List.of(), + null)); + assertEquals(expectedTssStatus, subject.getTssStatus()); + } + + @ParameterizedTest + @EnumSource( + value = RosterToKey.class, + names = {"ACTIVE_ROSTER", "CANDIDATE_ROSTER"}) + void submitsTssMessageIfSelfHasNotSubmitted(RosterToKey rosterToKey) { + final var messages = IntStream.range(0, 4) + .mapToObj(i -> + new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, i * 2L + 1, Bytes.wrap("MESSAGE" + i))) + .toList(); + final var oldStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, rosterToKey, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, rosterToKey, Bytes.EMPTY); + subject.setTssStatus(oldStatus); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(tssLibrary.generateTssMessage(any())).willReturn(TSS_MESSAGE); + given(tssLibrary.generateTssMessage(any(), any())).willReturn(TSS_MESSAGE); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.decryptPrivateShares(any(), any())) + .willReturn(List.of(new TssPrivateShare(1, FAKE_PRIVATE_KEY))); + assertFalse(subject.haveSentMessageForTargetRoster()); + given(tssStore.getMessagesForTarget(any())).willReturn(messages); + + subject.ensureParticipantDirectoryKnown(state); + subject.getTssKeysAccessor().generateKeyMaterialForActiveRoster(state); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + SOURCE_ROSTER, + SOURCE_HASH, + messages, + Optional.empty(), + List.of(), + null)); + if (rosterToKey == RosterToKey.ACTIVE_ROSTER) { + verify(tssLibrary).generateTssMessage(any()); + } else { + verify(tssLibrary).generateTssMessage(any(), any()); + } + assertEquals(expectedTssStatus, subject.getTssStatus()); + assertTrue(subject.haveSentMessageForTargetRoster()); + } + + @ParameterizedTest + @EnumSource( + value = RosterToKey.class, + names = {"ACTIVE_ROSTER", "CANDIDATE_ROSTER"}) + void managesTssStatusWhenKeyingComplete(RosterToKey rosterToKey) { + final var oldStatus = new TssStatus(KEYING_COMPLETE, rosterToKey, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(KEYING_COMPLETE, RosterToKey.NONE, Bytes.EMPTY); + subject.setTssStatus(oldStatus); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getCandidateRoster()).willReturn(TARGET_ROSTER); + + subject.ensureParticipantDirectoryKnown(state); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + SOURCE_ROSTER, + SOURCE_HASH, + List.of(), + Optional.empty(), + List.of(), + null)); + assertEquals(expectedTssStatus, subject.getTssStatus()); + } + + @ParameterizedTest + @EnumSource( + value = RosterToKey.class, + names = {"ACTIVE_ROSTER", "CANDIDATE_ROSTER"}) + void managesTssStatusWhenVotesReachedThreshold(RosterToKey rosterToKey) { + final var oldStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_VOTES, rosterToKey, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(KEYING_COMPLETE, rosterToKey, Bytes.wrap("ledger")); + subject.setTssStatus(oldStatus); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getCandidateRoster()).willReturn(TARGET_ROSTER); + + subject.ensureParticipantDirectoryKnown(state); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + TARGET_ROSTER, + TARGET_HASH, + List.of(), + Optional.of(TssVoteTransactionBody.newBuilder() + .ledgerId(Bytes.wrap("ledger")) + .tssVote(Bytes.wrap( + BitSet.valueOf(new long[] {1L, 2L}).toByteArray())) + .build()), + List.of(), + null)); + assertEquals(expectedTssStatus, subject.getTssStatus()); + } + + @ParameterizedTest + @EnumSource( + value = RosterToKey.class, + names = {"ACTIVE_ROSTER", "CANDIDATE_ROSTER"}) + void submitsVoteWhenSelfHasNotSubmittedVotes(RosterToKey rosterToKey) { + final var messages = IntStream.range(0, 8) + .mapToObj(i -> + new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, i * 2L + 1, Bytes.wrap("MESSAGE" + i))) + .toList(); + + final var oldStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_VOTES, rosterToKey, Bytes.EMPTY); + final var expectedTssStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_VOTES, rosterToKey, Bytes.EMPTY); + subject.setTssStatus(oldStatus); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getCandidateRoster()).willReturn(TARGET_ROSTER); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(gossip.sign(any())).willReturn(FAKE_SIGNATURE); + + subject.ensureParticipantDirectoryKnown(state); + subject.updateTssStatus( + true, + Instant.ofEpochSecond(1_234_567L), + new TssBaseServiceImpl.RosterAndTssInfo( + SOURCE_ROSTER, + SOURCE_HASH, + TARGET_ROSTER, + TARGET_HASH, + messages, + Optional.empty(), + List.of(), + null)); + assertEquals(expectedTssStatus, subject.getTssStatus()); + assertTrue(subject.haveSentVoteForTargetRoster()); + } + + @Test + void managesTssStatusOnIntializationWaitingForkeys() { + final var expectedTssStatus = + new TssStatus(WAITING_FOR_ENCRYPTION_KEYS, RosterToKey.ACTIVE_ROSTER, Bytes.EMPTY); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getActiveRoster()).willReturn(SOURCE_ROSTER); + given(rosterStore.getCandidateRoster()).willReturn(null); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + + subject.ensureParticipantDirectoryKnown(state); + final var tssStatus = subject.computeInitialTssStatus(tssStore, rosterStore); + assertEquals(expectedTssStatus, tssStatus); + } + + @Test + void managesTssStatusOnIntializationWaitingForMessages() { + final var expectedTssStatus = + new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, RosterToKey.ACTIVE_ROSTER, Bytes.EMPTY); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getActiveRoster()).willReturn(SOURCE_ROSTER); + given(rosterStore.getCandidateRoster()).willReturn(null); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(tssStore.getTssEncryptionKeys(anyLong())) + .willReturn(TssEncryptionKeys.newBuilder() + .currentEncryptionKey(Bytes.wrap("test")) + .build()); + + subject.ensureParticipantDirectoryKnown(state); + final var tssStatus = subject.computeInitialTssStatus(tssStore, rosterStore); + assertEquals(expectedTssStatus, tssStatus); + } + + @Test + void managesTssStatusOnIntializationWaitingForVotes() { + final var messages = IntStream.range(0, 8) + .mapToObj(i -> + new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, i * 2L + 1, Bytes.wrap("MESSAGE" + i))) + .toList(); + + final var expectedTssStatus = + new TssStatus(WAITING_FOR_THRESHOLD_TSS_VOTES, RosterToKey.ACTIVE_ROSTER, Bytes.EMPTY); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getActiveRoster()).willReturn(SOURCE_ROSTER); + given(rosterStore.getCandidateRoster()).willReturn(null); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(tssStore.getTssEncryptionKeys(anyLong())) + .willReturn(TssEncryptionKeys.newBuilder() + .currentEncryptionKey(Bytes.wrap("test")) + .build()); + given(tssStore.getMessagesForTarget(any())).willReturn(messages); + + subject.ensureParticipantDirectoryKnown(state); + final var tssStatus = subject.computeInitialTssStatus(tssStore, rosterStore); + assertEquals(expectedTssStatus, tssStatus); + } + + @Test + void managesTssStatusOnIntializationWaitingForMessagesCandidateRoster() { + final var expectedTssStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, CANDIDATE_ROSTER, Bytes.EMPTY); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getActiveRoster()).willReturn(SOURCE_ROSTER); + given(rosterStore.getCandidateRoster()).willReturn(TARGET_ROSTER); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(tssStore.getTssEncryptionKeys(anyLong())) + .willReturn(TssEncryptionKeys.newBuilder() + .currentEncryptionKey(Bytes.wrap("test")) + .build()); + given(tssStore.getMessage(any())) + .willReturn(new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, 1L, Bytes.EMPTY)); + given(tssStore.anyWinningVoteFor(SOURCE_HASH, rosterStore)) + .willReturn(Optional.of(TssVoteTransactionBody.DEFAULT)); + + subject.ensureParticipantDirectoryKnown(state); + + final var tssStatus = subject.computeInitialTssStatus(tssStore, rosterStore); + assertEquals(expectedTssStatus, tssStatus); + } + + @Test + void managesTssStatusOnIntializationWaitingForVotesCandidateRoster() { + final var expectedTssStatus = new TssStatus(WAITING_FOR_THRESHOLD_TSS_MESSAGES, CANDIDATE_ROSTER, Bytes.EMPTY); + + given(rosterStore.getCurrentRosterHash()).willReturn(SOURCE_HASH); + given(rosterStore.getActiveRoster()).willReturn(SOURCE_ROSTER); + given(rosterStore.getCandidateRoster()).willReturn(TARGET_ROSTER); + given(tssLibrary.computePublicShares(any(), any())).willReturn(List.of(new TssPublicShare(1, FAKE_PUBLIC_KEY))); + given(tssLibrary.aggregatePublicShares(any())).willReturn(FAKE_PUBLIC_KEY); + given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); + given(tssStore.getTssEncryptionKeys(anyLong())) + .willReturn(TssEncryptionKeys.newBuilder() + .currentEncryptionKey(Bytes.wrap("test")) + .build()); + given(tssStore.getMessage(any())) + .willReturn(new TssMessageTransactionBody(SOURCE_HASH, TARGET_HASH, 1L, Bytes.EMPTY)); + given(tssStore.anyWinningVoteFor(SOURCE_HASH, rosterStore)) + .willReturn(Optional.of(TssVoteTransactionBody.DEFAULT)); + + subject.ensureParticipantDirectoryKnown(state); + + final var tssStatus = subject.computeInitialTssStatus(tssStore, rosterStore); + assertEquals(expectedTssStatus, tssStatus); + } } 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 index 17f039d7b3a0..49ed562f373c 100644 --- 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 @@ -34,6 +34,7 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; +import com.hedera.cryptography.tss.api.TssMessage; import com.hedera.cryptography.tss.api.TssParticipantDirectory; import com.hedera.cryptography.tss.api.TssPrivateShare; import com.hedera.cryptography.tss.api.TssPublicShare; @@ -90,8 +91,7 @@ public class TssBaseServiceTest { @Mock(strictness = Mock.Strictness.LENIENT) private NetworkInfo networkInfo; - @Mock - private com.hedera.cryptography.tss.api.TssMessage tssMessage; + private final TssMessage TSS_MESSAGE = "test"::getBytes; @Mock private Executor executor; @@ -145,6 +145,7 @@ void doesntSetSameCandidateRoster() { // Simulate CURRENT_CANDIDATE_ROSTER and ACTIVE_ROSTER mockWritableRosterStore(); given(tssLibrary.decryptPrivateShares(any(), any())).willReturn(List.of()); + given(tssLibrary.generateTssMessage(any(), any())).willReturn(TSS_MESSAGE); // Attempt to set the same candidate roster subject.setCandidateRoster(CURRENT_CANDIDATE_ROSTER, handleContext); @@ -161,7 +162,7 @@ void doesntSetActiveRosterAsCandidateRoster() { given(handleContext.storeFactory()).willReturn(storeFactory); given(storeFactory.writableStore(WritableRosterStore.class)).willReturn(rosterStore); given(tssLibrary.decryptPrivateShares(any(), any())).willReturn(List.of()); - given(tssLibrary.generateTssMessage(any(), any())).willReturn(tssMessage); + given(tssLibrary.generateTssMessage(any(), any())).willReturn(TSS_MESSAGE); given(handleContext.consensusNow()).willReturn(Instant.ofEpochSecond(1_234_567L)); subject.setCandidateRoster(ACTIVE_ROSTER, handleContext); @@ -180,7 +181,7 @@ void setsCandidateRoster() { // Simulate the _current_ candidate roster and active roster final var rosterStore = mockWritableRosterStore(); given(tssLibrary.decryptPrivateShares(any(), any())).willReturn(List.of()); - given(tssLibrary.generateTssMessage(any(), any())).willReturn(tssMessage); + given(tssLibrary.generateTssMessage(any(), any())).willReturn(TSS_MESSAGE); given(handleContext.consensusNow()).willReturn(Instant.ofEpochSecond(1_234_567L)); final var inputRoster = Roster.newBuilder() .rosterEntries(List.of(ROSTER_NODE_1, ROSTER_NODE_2, ROSTER_NODE_3)) diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssCryptographyManagerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssCryptographyManagerTest.java index 7c5b78987583..b08e3ce9361f 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssCryptographyManagerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/TssCryptographyManagerTest.java @@ -38,7 +38,6 @@ import com.hedera.node.app.spi.store.StoreFactory; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.tss.api.TssLibrary; -import com.hedera.node.app.tss.stores.WritableTssStore; import com.hedera.pbj.runtime.io.buffer.Bytes; import com.swirlds.common.crypto.Signature; import com.swirlds.state.lifecycle.info.NetworkInfo; @@ -74,9 +73,6 @@ public class TssCryptographyManagerTest { @Mock private StoreFactory storeFactory; - @Mock - private WritableTssStore tssStore; - @Mock private TssMetrics tssMetrics; @@ -96,10 +92,9 @@ void setUp() { void testWhenVoteAlreadySubmitted() { final var body = getTssBody(); when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - when(tssStore.getVote(any())).thenReturn(mock(TssVoteTransactionBody.class)); // Simulate vote already submitted - - final var result = subject.getVoteFuture(body.targetRosterHash(), tssParticipantDirectory, handleContext); + // Simulate vote already submitted + final var result = + subject.getVoteFuture(tssParticipantDirectory, List.of(body), (mock(TssVoteTransactionBody.class))); assertNull(result.join()); } @@ -108,10 +103,7 @@ void testWhenVoteAlreadySubmitted() { void testWhenVoteNoVoteSubmittedAndThresholdNotMet() { final var body = getTssBody(); when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - when(tssStore.getVote(any())).thenReturn(null); - - final var result = subject.getVoteFuture(body.targetRosterHash(), tssParticipantDirectory, handleContext); + final var result = subject.getVoteFuture(tssParticipantDirectory, List.of(body), null); assertNull(result.join()); } @@ -124,17 +116,13 @@ void testWhenVoteNoVoteSubmittedAndThresholdMet() { final var body = getTssBody(); when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - when(tssStore.getVote(any())).thenReturn(null); - when(tssStore.getMessagesForTarget(any())).thenReturn(List.of(body)); when(tssLibrary.verifyTssMessage(any(), any())).thenReturn(true); when(tssLibrary.computePublicShares(any(), any())).thenReturn(mockPublicShares); when(tssLibrary.aggregatePublicShares(any())).thenReturn(ledgerId); when(gossip.sign(any())).thenReturn(mockSignature); - final var result = subject.getVoteFuture(body.targetRosterHash(), tssParticipantDirectory, handleContext); - + final var result = subject.getVoteFuture(tssParticipantDirectory, List.of(body), null); assertNotNull(result.join()); verify(gossip).sign(ledgerId.toBytes()); } @@ -143,14 +131,10 @@ void testWhenVoteNoVoteSubmittedAndThresholdMet() { void testWhenMetException() { final var body = getTssBody(); when(handleContext.storeFactory()).thenReturn(storeFactory); - when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - when(tssStore.getVote(any())).thenReturn(null); - when(tssStore.getMessagesForTarget(any())).thenReturn(List.of(body)); when(tssLibrary.verifyTssMessage(any(), any())).thenReturn(true); when(tssLibrary.computePublicShares(any(), any())).thenThrow(new RuntimeException()); - - final var result = subject.getVoteFuture(body.targetRosterHash(), tssParticipantDirectory, handleContext); + final var result = subject.getVoteFuture(tssParticipantDirectory, List.of(body), null); assertNull(result.join()); verify(gossip, never()).sign(any()); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java index b655b8f8638d..f5c09afaba6b 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssMessageHandlerTest.java @@ -31,6 +31,7 @@ import com.hedera.hapi.node.base.AccountID; import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.hapi.services.auxiliary.tss.TssMessageTransactionBody; +import com.hedera.hapi.services.auxiliary.tss.TssVoteTransactionBody; import com.hedera.node.app.spi.store.StoreFactory; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.PreHandleContext; @@ -131,10 +132,7 @@ void submitsVoteOnHandlingMessageWhenThresholdMet() { when(handleContext.storeFactory()).thenReturn(storeFactory); when(storeFactory.writableStore(WritableTssStore.class)).thenReturn(tssStore); - given(tssCryptographyManager.getVoteFuture( - eq(getTssBody().tssMessageOrThrow().targetRosterHash()), - any(TssParticipantDirectory.class), - eq(handleContext))) + given(tssCryptographyManager.getVoteFuture(any(TssParticipantDirectory.class), any(), any())) .willReturn(CompletableFuture.completedFuture(vote)); given(signature.getBytes()).willReturn(Bytes.wrap("test")); given(directoryAccessor.activeParticipantDirectoryOrThrow()).willReturn(TSS_KEYS.activeParticipantDirectory()); @@ -147,12 +145,12 @@ void submitsVoteOnHandlingMessageWhenThresholdMet() { @Test public void testHandleException() { when(handleContext.body()).thenReturn(getTssBody()); - when(tssCryptographyManager.getVoteFuture(any(), any(), any())) + when(tssCryptographyManager.getVoteFuture(any(TssParticipantDirectory.class), any(), any())) .thenThrow(new RuntimeException("Simulated error")); // Execute the handler and ensure no vote is submitted assertThrows(RuntimeException.class, () -> subject.handle(handleContext)); - verify(submissionManager, never()).submitTssVote(any(), any()); + verify(submissionManager, never()).submitTssVote(any(TssVoteTransactionBody.class), any(HandleContext.class)); } public static TransactionBody getTssBody() { diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssSubmissionsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssSubmissionsTest.java index e2ca0ee2f8ad..d5da697204e8 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssSubmissionsTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssSubmissionsTest.java @@ -90,10 +90,9 @@ void setUp() { given(appContext.gossip()).willReturn(gossip); subject = new TssSubmissions(appContext, ForkJoinPool.commonPool()); given(context.consensusNow()).willReturn(CONSENSUS_NOW); - given(context.networkInfo()).willReturn(networkInfo); - given(networkInfo.selfNodeInfo()).willReturn(nodeInfo); given(nodeInfo.accountId()).willReturn(NODE_ACCOUNT_ID); - given(context.configuration()).willReturn(TEST_CONFIG); + given(appContext.configSupplier()).willReturn(() -> TEST_CONFIG); + given(appContext.selfNodeInfoSupplier()).willReturn(() -> nodeInfo); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java index 9f6ebd7b89fb..2bd23e77a4ec 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/handlers/TssUtilsTest.java @@ -18,7 +18,7 @@ import static com.hedera.node.app.tss.handlers.TssMessageHandlerTest.getTssBody; import static com.hedera.node.app.tss.handlers.TssUtils.SIGNATURE_SCHEMA; -import static com.hedera.node.app.tss.handlers.TssUtils.validateTssMessages; +import static com.hedera.node.app.tss.handlers.TssUtils.voteForValidMessages; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -59,29 +59,32 @@ public void testComputeParticipantDirectory() { } @Test - public void testValidateTssMessages() { + public void testVoteForValidMessages() { final var body = getTssBody(); final var tssLibrary = mock(TssLibrary.class); final var tssParticipantDirectory = mock(TssParticipantDirectory.class); given(tssLibrary.verifyTssMessage(any(), any())).willReturn(true); - final var validMessages = - validateTssMessages(List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary); + final var validMessages = voteForValidMessages( + List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary) + .get() + .validTssMessages(); assertEquals(1, validMessages.size()); } @Test - public void testValidateTssMessagesFails() { + public void testVoteForValidMessagesFails() { final var body = getTssBody(); final var tssLibrary = mock(TssLibrary.class); final var tssParticipantDirectory = mock(TssParticipantDirectory.class); given(tssLibrary.verifyTssMessage(any(), any())).willReturn(false); + given(tssParticipantDirectory.getShareIds()).willReturn(List.of(1, 2, 3, 4)); - final var validMessages = - validateTssMessages(List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary); + final var validMessagesForVote = + voteForValidMessages(List.of(body.tssMessageOrThrow()), tssParticipantDirectory, tssLibrary); - assertEquals(0, validMessages.size()); + assertTrue(validMessagesForVote.isEmpty()); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java index 8e19b1611424..1bd0898c44b1 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/tss/stores/ReadableTssStoreTest.java @@ -22,6 +22,7 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -211,6 +212,18 @@ void testExistsVote() { assertTrue(tssStore.exists(key)); } + @Test + void testGetsAllVotes() { + TssVoteTransactionBody vote = TssVoteTransactionBody.DEFAULT; + when(readableTssVoteState.keys()) + .thenReturn(singletonList(TssVoteMapKey.DEFAULT).iterator()); + when(readableTssVoteState.get(TssVoteMapKey.DEFAULT)).thenReturn(vote); + + final var result = tssStore.allVotes(); + assertEquals(1, result.size()); + assertEquals(vote, result.get(0)); + } + @Test void testGetMessagesForTarget() { Bytes rosterHash = Bytes.wrap("targetHash".getBytes()); @@ -251,4 +264,42 @@ void testAnyWinningVoteFrom() { tssStore.anyWinningVoteFrom(sourceRosterHash, targetRosterHash, 10L, weightFn); assertTrue(result.isPresent()); } + + @Test + void testAnyWinningVoteForWhenNoVote() { + Bytes sourceRosterHash = Bytes.wrap("sourceHash".getBytes()); + Bytes targetRosterHash = Bytes.wrap("targetHash".getBytes()); + TssVoteMapKey key = mock(TssVoteMapKey.class); + TssVoteTransactionBody vote = TssVoteTransactionBody.newBuilder() + .sourceRosterHash(sourceRosterHash) + .tssVote(Bytes.wrap("vote".getBytes())) + .targetRosterHash(targetRosterHash) + .build(); + when(readableTssVoteState.keys()).thenReturn(singletonList(key).iterator()); + when(readableTssVoteState.get(key)).thenReturn(vote); + when(rosterStore.get(sourceRosterHash)).thenReturn(SOURCE_ROSTER); + + Optional result = tssStore.anyWinningVoteFor(targetRosterHash, rosterStore); + assertFalse(result.isPresent()); + } + + @Test + void testAnyWinningVoteFor() { + doCallRealMethod().when(subject).anyWinningVoteFor(any(), any()); + Bytes sourceRosterHash = Bytes.wrap("sourceHash".getBytes()); + Bytes targetRosterHash = Bytes.wrap("targetHash".getBytes()); + TssVoteMapKey key = + TssVoteMapKey.newBuilder().rosterHash(targetRosterHash).build(); + TssVoteTransactionBody vote = TssVoteTransactionBody.newBuilder() + .sourceRosterHash(sourceRosterHash) + .tssVote(Bytes.wrap("vote".getBytes())) + .targetRosterHash(targetRosterHash) + .build(); + when(subject.allVotes()).thenReturn(List.of(vote)); + when(subject.anyWinningVoteFrom(any(), any(), any())).thenReturn(Optional.of(vote)); + + Optional result = subject.anyWinningVoteFor(targetRosterHash, rosterStore); + + assertTrue(result.isPresent()); + } } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java index 96a7f076720d..b9e2c67cc19a 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/NodeStakeUpdatesTest.java @@ -164,6 +164,11 @@ void processUpdateCalledForGenesisTxn() { given(context.configuration()).willReturn(DEFAULT_CONFIG); given(stack.getWritableStates(AddressBookService.NAME)).willReturn(writableStates); given(writableStates.get(NODES_KEY)).willReturn(nodesState); + given(blockStore.getLastBlockInfo()) + .willReturn(BlockInfo.newBuilder() + .consTimeOfLastHandledTxn(Timestamp.newBuilder().seconds(1_234_567L)) + .build()); + given(context.consensusTime()).willReturn(CONSENSUS_TIME_1234567); subject.process(dispatch, stack, context, RECORDS, true, Instant.EPOCH); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java index fd5a1df85693..7d0c026f048f 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/workflows/handle/steps/PlatformStateUpdatesTest.java @@ -63,8 +63,8 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class PlatformStateUpdatesTest implements TransactionFactory { - private static final RosterState ROSTER_STATE = RosterState.newBuilder() +public class PlatformStateUpdatesTest implements TransactionFactory { + public static final RosterState ROSTER_STATE = RosterState.newBuilder() .roundRosterPairs(List.of(RoundRosterPair.DEFAULT)) .build(); private FakeState state; 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 index 68d7226613f5..6edda8d6752c 100644 --- 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 @@ -24,6 +24,7 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.services.ServiceMigrator; import com.hedera.node.app.spi.AppContext; +import com.hedera.node.app.spi.metrics.StoreMetricsService; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.tss.TssBaseService; import com.hedera.node.app.tss.TssBaseServiceImpl; @@ -271,7 +272,11 @@ public TssMessage getTssMessageFromBytes(Bytes wrap, TssParticipantDirectory dir } @Override - public void manageTssStatus(final State state) { - delegate.manageTssStatus(state); + public void manageTssStatus( + final State state, + final boolean isStakePeriodBoundary, + final Instant lastUsedConsensusNow, + final StoreMetricsService storeMetricsService) { + delegate.manageTssStatus(state, isStakePeriodBoundary, lastUsedConsensusNow, storeMetricsService); } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermCreateTests.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermCreateTests.java index 8be48714ad16..d2c80a5bb9ef 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermCreateTests.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip423/ScheduleLongTermCreateTests.java @@ -60,15 +60,4 @@ final Stream scheduleCreateDefaultsToMaxValueFromConfig() { .via("createTxn"), getScheduleInfo("one").hasRelativeExpiry("createTxn", MAX_SCHEDULE_EXPIRY_TIME)); } - - @HapiTest - final Stream scheduleCreateMinimumTime() { - return hapiTest( - cryptoCreate(RECEIVER).balance(0L), - scheduleCreate("one", cryptoTransfer(tinyBarsFromTo(DEFAULT_PAYER, RECEIVER, 1L))) - .waitForExpiry(false) - .expiringIn(1) - .via("createTxn"), - getScheduleInfo("one").isExecuted().hasRelativeExpiry("createTxn", 1)); - } } From ccca1f7bd7661ae937a9456cf3f698222fd44156 Mon Sep 17 00:00:00 2001 From: Neeharika Sompalli <52669918+Neeharika-Sompalli@users.noreply.github.com> Date: Mon, 16 Dec 2024 15:03:17 -0600 Subject: [PATCH 09/39] fix: Export only active nodes in `NodeStakeUpdates` (#17077) Signed-off-by: Neeharika-Sompalli --- .../staking/EndOfStakingPeriodUpdater.java | 31 ++++++++++--------- .../handlers/staking/StakeInfoHelper.java | 8 +++-- .../EndOfStakingPeriodUpdaterTest.java | 22 ++++++++++++- .../handlers/staking/StakeInfoHelperTest.java | 25 +++++++++++++-- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java index 6fe624637125..d369e14341d8 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/EndOfStakingPeriodUpdater.java @@ -90,7 +90,7 @@ public EndOfStakingPeriodUpdater( * Updates all (relevant) staking-related values for all nodes, as well as any network reward information, * at the end of a staking period. This method must be invoked during handling of a transaction * - * @param context the context of the transaction used to end the staking period + * @param context the context of the transaction used to end the staking period * @param exchangeRates the active exchange rate set * @param weightUpdates the callback to use to propagate weight changes */ @@ -209,9 +209,11 @@ public EndOfStakingPeriodUpdater( // We rescale the weight range [0, sumOfConsensusWeights] back to [minStake, maxStake] before // externalizing the node stake metadata to stream consumers like mirror nodes final var rescaledWeight = rescaleWeight(newWeight, nodeInfo.minStake(), maxStake, totalStake, totalWeight); - nodeStakes.add(EndOfStakingPeriodUtils.fromStakingInfo( - nodeRewardRates.get(nodeId), - nodeInfo.copyBuilder().stake(rescaledWeight).build())); + if (!nodeInfo.deleted()) { + nodeStakes.add(EndOfStakingPeriodUtils.fromStakingInfo( + nodeRewardRates.get(nodeId), + nodeInfo.copyBuilder().stake(rescaledWeight).build())); + } // Persist the updated staking info stakingInfoStore.put(nodeId, newNodeInfo); }); @@ -253,10 +255,10 @@ public EndOfStakingPeriodUpdater( * Scales up the weight of the node to the range [minStake, maxStakeOfAllNodes] * from the consensus weight range [0, sumOfConsensusWeights]. * - * @param weight weight of the node - * @param newMinStake min stake of the node - * @param newMaxStake real max stake of all nodes computed by taking max(stakeOfNode1, stakeOfNode2, ...) - * @param totalStakeOfAllNodes total stake of all nodes at the start of new period + * @param weight weight of the node + * @param newMinStake min stake of the node + * @param newMaxStake real max stake of all nodes computed by taking max(stakeOfNode1, stakeOfNode2, ...) + * @param totalStakeOfAllNodes total stake of all nodes at the start of new period * @param sumOfConsensusWeights sum of consensus weights of all nodes * @return scaled weight of the node */ @@ -308,8 +310,9 @@ public static long rescaleWeight( * The result are normalized weights whose sum will be approximately the given total weight. That is, any node * with a non-zero amount of stake will have a weight of at least {@code 1}; any node with a stake of at least one * out of every 250 whole hbars staked will have weight at least {@code 2}; and so on. - * @param nodeStake the stake of a single node, both rewarded and non-rewarded - * @param totalStake the total stake of all nodes + * + * @param nodeStake the stake of a single node, both rewarded and non-rewarded + * @param totalStake the total stake of all nodes * @param totalWeight the desired approximate total weight of all nodes * @return the scaled consensus weight for the node */ @@ -356,7 +359,7 @@ private long rewardRateGiven( * threshold, from 0 for empty, up to 1 at the threshold. * * @param unreservedBalance the balance in {@code 0.0.800} minus the pending rewards - * @param thresholdBalance the threshold balance setting + * @param thresholdBalance the threshold balance setting * @return the ratio of the balance to the threshold, from 0 for empty, up to 1 at the threshold */ private BigDecimal ratioOf(final long unreservedBalance, final long thresholdBalance) { @@ -371,9 +374,9 @@ private BigDecimal ratioOf(final long unreservedBalance, final long thresholdBal * start of the period that is now ending, and the maximum amount of tinybars to pay as staking rewards in the * period, returns the effective per-hbar reward rate for the period. * - * @param balanceRatio the ratio of the {@code 0.0.800} balance to the threshold - * @param stakedToReward the amount of hbars staked to reward at the start of the ending period - * @param maxRewardRate the maximum amount of tinybars to pay per hbar reward + * @param balanceRatio the ratio of the {@code 0.0.800} balance to the threshold + * @param stakedToReward the amount of hbars staked to reward at the start of the ending period + * @param maxRewardRate the maximum amount of tinybars to pay per hbar reward * @param maxStakeRewarded the maximum amount of stake that can be rewarded * @return the effective per-hbar reward rate for the period */ diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java index c48dbd412546..e4380997c5fa 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/staking/StakeInfoHelper.java @@ -231,9 +231,11 @@ public StreamBuilder adjustPostUpgradeStakes( final var nodeStakes = new ArrayList(); postUpgradeNodeIds.stream().sorted().forEach(nodeId -> { final var stakingInfo = requireNonNull(infoStore.getForModify(nodeId)); - final var history = stakingInfo.rewardSumHistory(); - final var rewardRate = stakingInfo.deleted() ? 0 : history.getFirst() - history.get(1); - nodeStakes.add(fromStakingInfo(rewardRate, stakingInfo)); + if (!stakingInfo.deleted()) { + final var history = stakingInfo.rewardSumHistory(); + final var rewardRate = history.getFirst() - history.get(1); + nodeStakes.add(fromStakingInfo(rewardRate, stakingInfo)); + } }); final var stakingConfig = config.getConfigData(StakingConfig.class); final var syntheticNodeStakeUpdateTxn = newNodeStakeUpdateBuilder( diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java index 924e5da68ce1..f9a66722d8d0 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/EndOfStakingPeriodUpdaterTest.java @@ -27,14 +27,18 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import com.hedera.hapi.node.base.Timestamp; +import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.token.Account; import com.hedera.hapi.node.state.token.NetworkStakingRewards; import com.hedera.hapi.node.state.token.StakingNodeInfo; import com.hedera.hapi.node.transaction.ExchangeRateSet; +import com.hedera.hapi.node.transaction.SignedTransaction; +import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.token.ReadableAccountStore; import com.hedera.node.app.service.token.impl.WritableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; @@ -51,6 +55,7 @@ import com.hedera.node.config.ConfigProvider; import com.hedera.node.config.data.StakingConfig; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.ParseException; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableSingletonStateBase; @@ -67,6 +72,7 @@ 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; @@ -201,7 +207,8 @@ void scalesBackWeightToStake() { } @Test - void deletedNodesGetsZeroPendingRewards() { + void deletedNodesGetsZeroPendingRewards() throws ParseException { + final var captor = ArgumentCaptor.forClass(Transaction.class); commonSetup( 1_000_000_000L, STAKING_INFO_1.copyBuilder().deleted(true).build(), @@ -218,6 +225,7 @@ void deletedNodesGetsZeroPendingRewards() { given(nodeStakeUpdateRecordBuilder.memo(any())).willReturn(nodeStakeUpdateRecordBuilder); given(nodeStakeUpdateRecordBuilder.exchangeRate(ExchangeRateSet.DEFAULT)) .willReturn(nodeStakeUpdateRecordBuilder); + given(nodeStakeUpdateRecordBuilder.transaction(captor.capture())).willReturn(nodeStakeUpdateRecordBuilder); subject.updateNodes(context, ExchangeRateSet.DEFAULT, weightUpdates); @@ -248,6 +256,18 @@ void deletedNodesGetsZeroPendingRewards() { assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 1 is now [6, 6, 5]"); assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 2 is now [101, 1, 1]"); assertThat(logCaptor.infoLogs()).contains("Non-zero reward sum history for node number 3 is now [3, 3, 1]"); + + // Doesn't export deleted nodes nodeStakeUpdates + verify(context).addPrecedingChildRecordBuilder(NodeStakeUpdateStreamBuilder.class, NODE_STAKE_UPDATE); + final var transaction = captor.getValue(); + final var nodeStakeUpdate = TransactionBody.PROTOBUF + .parse(SignedTransaction.PROTOBUF + .parse(transaction.signedTransactionBytes()) + .bodyBytes()) + .nodeStakeUpdate(); + final var nodeStakes = nodeStakeUpdate.nodeStake(); + assertThat(nodeStakes).hasSize(1); + assertThat(nodeStakes.get(0).nodeId()).isEqualTo(NODE_NUM_2.number()); } @Test diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java index 2740101fb88f..9fa8e815cb4d 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/staking/StakeInfoHelperTest.java @@ -31,9 +31,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import com.hedera.hapi.node.base.Transaction; import com.hedera.hapi.node.state.common.EntityNumber; import com.hedera.hapi.node.state.token.StakingNodeInfo; +import com.hedera.hapi.node.transaction.SignedTransaction; +import com.hedera.hapi.node.transaction.TransactionBody; import com.hedera.node.app.service.token.impl.WritableNetworkStakingRewardsStore; import com.hedera.node.app.service.token.impl.WritableStakingInfoStore; import com.hedera.node.app.service.token.impl.handlers.staking.StakeInfoHelper; @@ -42,6 +46,7 @@ import com.hedera.node.app.service.token.records.TokenContext; import com.hedera.node.app.spi.fixtures.info.FakeNetworkInfo; import com.hedera.node.config.testfixtures.HederaTestConfigBuilder; +import com.hedera.pbj.runtime.ParseException; import com.swirlds.config.api.Configuration; import com.swirlds.state.spi.WritableSingletonStateBase; import com.swirlds.state.test.fixtures.MapWritableKVState; @@ -53,6 +58,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -104,7 +110,8 @@ void increaseUnclaimedStartToLargerThanCurrentStakeReward(int amount, int expect } @Test - void marksNonExistingNodesToDeletedInStateAndAddsNewNodesToState() { + void marksNonExistingNodesToDeletedInStateAndAddsNewNodesToState() throws ParseException { + final var captor = ArgumentCaptor.forClass(Transaction.class); // State has nodeIds 1, 2, 3 final var stakingInfosState = new MapWritableKVState.Builder(STAKING_INFO_KEY) .value(NODE_NUM_1, STAKING_INFO_1) @@ -118,7 +125,7 @@ void marksNonExistingNodesToDeletedInStateAndAddsNewNodesToState() { given(tokenContext.consensusTime()).willReturn(Instant.EPOCH); given(tokenContext.addPrecedingChildRecordBuilder(NodeStakeUpdateStreamBuilder.class, NODE_STAKE_UPDATE)) .willReturn(streamBuilder); - given(streamBuilder.transaction(any())).willReturn(streamBuilder); + given(streamBuilder.transaction(captor.capture())).willReturn(streamBuilder); given(streamBuilder.memo(any())).willReturn(streamBuilder); // Should update the state to mark node 1 and 3 as deleted @@ -138,6 +145,20 @@ void marksNonExistingNodesToDeletedInStateAndAddsNewNodesToState() { assertThat(((StakingNodeInfo) updatedStates.get(NODE_NUM_8)).weight()).isZero(); assertThat(((StakingNodeInfo) updatedStates.get(NODE_NUM_8)).minStake()).isZero(); assertThat(((StakingNodeInfo) updatedStates.get(NODE_NUM_8)).maxStake()).isEqualTo(1666666666666666666L); + + // Doesn't export deleted nodes nodeStakeUpdates + verify(tokenContext).addPrecedingChildRecordBuilder(NodeStakeUpdateStreamBuilder.class, NODE_STAKE_UPDATE); + final var transaction = captor.getValue(); + final var nodeStakeUpdate = TransactionBody.PROTOBUF + .parse(SignedTransaction.PROTOBUF + .parse(transaction.signedTransactionBytes()) + .bodyBytes()) + .nodeStakeUpdate(); + final var nodeStakes = nodeStakeUpdate.nodeStake(); + assertThat(nodeStakes).hasSize(3); + assertThat(nodeStakes.get(0).nodeId()).isEqualTo(NODE_NUM_2.number()); + assertThat(nodeStakes.get(1).nodeId()).isEqualTo(NODE_NUM_4.number()); + assertThat(nodeStakes.get(2).nodeId()).isEqualTo(NODE_NUM_8.number()); } private MapWritableStates newStatesInstance(final MapWritableKVState stakingInfo) { From 391df460bc8a9f347276c917aeeeb90af5a914c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:16:45 -0600 Subject: [PATCH 10/39] build(deps): bump docker/setup-buildx-action from 3.7.1 to 3.8.0 (#17079) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/node-zxc-build-release-artifact.yaml | 2 +- .github/workflows/zxc-publish-production-image.yaml | 2 +- .github/workflows/zxc-verify-docker-build-determinism.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/node-zxc-build-release-artifact.yaml b/.github/workflows/node-zxc-build-release-artifact.yaml index 797181f6df28..93ce1387d98f 100644 --- a/.github/workflows/node-zxc-build-release-artifact.yaml +++ b/.github/workflows/node-zxc-build-release-artifact.yaml @@ -420,7 +420,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Setup Docker Buildx Support - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 with: version: v0.16.2 driver-opts: network=host diff --git a/.github/workflows/zxc-publish-production-image.yaml b/.github/workflows/zxc-publish-production-image.yaml index 86c105218075..fa6e29f7a114 100644 --- a/.github/workflows/zxc-publish-production-image.yaml +++ b/.github/workflows/zxc-publish-production-image.yaml @@ -155,7 +155,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Setup Docker Buildx Support - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 with: version: v0.16.2 driver-opts: network=host diff --git a/.github/workflows/zxc-verify-docker-build-determinism.yaml b/.github/workflows/zxc-verify-docker-build-determinism.yaml index 95d1a461de0c..ea7475d4808c 100644 --- a/.github/workflows/zxc-verify-docker-build-determinism.yaml +++ b/.github/workflows/zxc-verify-docker-build-determinism.yaml @@ -197,7 +197,7 @@ jobs: if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} - name: Setup Docker Buildx Support - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 if: ${{ steps.baseline.outputs.exists == 'false' && !failure() && !cancelled() }} with: version: v0.16.2 @@ -426,7 +426,7 @@ jobs: uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0 - name: Setup Docker Buildx Support - uses: docker/setup-buildx-action@c47758b77c9736f4b2ef4073d4d51994fabfe349 # v3.7.1 + uses: docker/setup-buildx-action@6524bf65af31da8d45b59e8c27de4bd072b392f5 # v3.8.0 with: version: v0.16.2 driver-opts: network=host From ff365ab718fb84f9d46d0b3f2890844b9a0ad1b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 21:04:22 -0600 Subject: [PATCH 11/39] build(deps): bump com.autonomousapps:dependency-analysis-gradle-plugin from 2.5.0 to 2.6.1 in /gradle/plugins (#17080) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gradle/plugins/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts index 70d4000e5813..fc93ac5a4da1 100644 --- a/gradle/plugins/build.gradle.kts +++ b/gradle/plugins/build.gradle.kts @@ -24,7 +24,7 @@ repositories { gradlePluginPortal() } dependencies { implementation("com.adarshr:gradle-test-logger-plugin:4.0.0") - implementation("com.autonomousapps:dependency-analysis-gradle-plugin:2.5.0") + implementation("com.autonomousapps:dependency-analysis-gradle-plugin:2.6.1") implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0") implementation("com.github.johnrengelman:shadow:8.1.1") implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.4") From 753d1be0907a558dbc2e00e2609f3d8930eda15b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 22:10:04 -0600 Subject: [PATCH 12/39] build(deps): bump jfrog/setup-jfrog-cli from 4.5.1 to 4.5.2 (#17078) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/node-zxc-build-release-artifact.yaml | 2 +- .github/workflows/zxc-publish-production-image.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/node-zxc-build-release-artifact.yaml b/.github/workflows/node-zxc-build-release-artifact.yaml index 93ce1387d98f..1534a2fb3e84 100644 --- a/.github/workflows/node-zxc-build-release-artifact.yaml +++ b/.github/workflows/node-zxc-build-release-artifact.yaml @@ -575,7 +575,7 @@ jobs: service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com" - name: Setup JFrog CLI - uses: jfrog/setup-jfrog-cli@96153976e4e81b3701e9cc0a5b9597e80614af81 # v4.5.1 + uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2 env: JF_URL: ${{ secrets.jf-url }} JF_ACCESS_TOKEN: ${{ secrets.jf-access-token }} diff --git a/.github/workflows/zxc-publish-production-image.yaml b/.github/workflows/zxc-publish-production-image.yaml index fa6e29f7a114..3fe6d8e0b53e 100644 --- a/.github/workflows/zxc-publish-production-image.yaml +++ b/.github/workflows/zxc-publish-production-image.yaml @@ -112,7 +112,7 @@ jobs: service_account: "swirlds-automation@hedera-registry.iam.gserviceaccount.com" - name: Setup JFrog CLI - uses: jfrog/setup-jfrog-cli@96153976e4e81b3701e9cc0a5b9597e80614af81 # v4.5.1 + uses: jfrog/setup-jfrog-cli@dff217c085c17666e8849ebdbf29c8fe5e3995e6 # v4.5.2 if: ${{ inputs.dry-run-enabled != true && inputs.registry-name == 'jfrog' && !cancelled() && !failure() }} env: JF_URL: ${{ secrets.jf-url }} From 8b7c003d0c2b99184d457e92e3d92af1ed8c49a2 Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Tue, 17 Dec 2024 16:14:00 +0200 Subject: [PATCH 13/39] feat: adapt PlatformWiring to expect system transactions as a return parameter from TransactionPrehandler and StateHasher (#16899) Signed-off-by: Ivan Kavaldzhiev Co-authored-by: Mustafa Uzun --- .../DefaultTransactionHandler.java | 14 ++- .../DefaultTransactionPrehandler.java | 8 +- .../eventhandling/TransactionPrehandler.java | 6 +- .../platform/state/SwirldStateManager.java | 7 +- .../state/iss/DefaultIssDetector.java | 12 +-- .../platform/wiring/PlatformCoordinator.java | 10 +- .../platform/wiring/PlatformWiring.java | 37 +++---- .../wiring/components/StateAndRound.java | 11 ++- .../platform/StateFileManagerTests.java | 3 +- .../state/signed/DefaultStateHasherTests.java | 4 +- .../signed/StateGarbageCollectorTests.java | 5 +- .../StateHashedNotificationTest.java | 9 +- .../platform/test/state/IssDetectorTests.java | 97 ++++++++++++++++--- 13 files changed, 159 insertions(+), 64 deletions(-) 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 c1c2104c3673..12e36f6ae397 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 @@ -27,10 +27,12 @@ import static com.swirlds.platform.eventhandling.TransactionHandlerPhase.UPDATING_PLATFORM_STATE_RUNNING_HASH; import static com.swirlds.platform.eventhandling.TransactionHandlerPhase.WAITING_FOR_PREHANDLE; +import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.stream.RunningEventHashOverride; import com.swirlds.common.wiring.schedulers.builders.TaskSchedulerType; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.event.PlatformEvent; @@ -48,6 +50,7 @@ import com.swirlds.platform.wiring.components.StateAndRound; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.List; import java.util.Objects; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -198,12 +201,12 @@ public StateAndRound handleConsensusRound(@NonNull final ConsensusRound consensu } handlerMetrics.setPhase(HANDLING_CONSENSUS_ROUND); - swirldStateManager.handleConsensusRound(consensusRound); + final var systemTransactions = swirldStateManager.handleConsensusRound(consensusRound); handlerMetrics.setPhase(UPDATING_PLATFORM_STATE_RUNNING_HASH); updateRunningEventHash(consensusRound); - return createSignedState(consensusRound); + return createSignedState(consensusRound, systemTransactions); } catch (final InterruptedException e) { logger.error(EXCEPTION.getMarker(), "handleConsensusRound interrupted"); Thread.currentThread().interrupt(); @@ -268,7 +271,10 @@ private void updateRunningEventHash(@NonNull final ConsensusRound round) throws * @throws InterruptedException if this thread is interrupted */ @NonNull - private StateAndRound createSignedState(@NonNull final ConsensusRound consensusRound) throws InterruptedException { + private StateAndRound createSignedState( + @NonNull final ConsensusRound consensusRound, + @NonNull final List> systemTransactions) + throws InterruptedException { if (freezeRoundReceived) { // Let the swirld state manager know we are about to write the saved state for the freeze period swirldStateManager.savedStateInFreezePeriod(); @@ -289,6 +295,6 @@ private StateAndRound createSignedState(@NonNull final ConsensusRound consensusR consensusRound.isPcesRound()); final ReservedSignedState reservedSignedState = signedState.reserve("transaction handler output"); - return new StateAndRound(reservedSignedState, consensusRound); + return new StateAndRound(reservedSignedState, consensusRound, systemTransactions); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionPrehandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionPrehandler.java index fa6d80c2c591..6ddb39c0aade 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionPrehandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/DefaultTransactionPrehandler.java @@ -18,6 +18,7 @@ import static com.swirlds.logging.legacy.LogMarker.EXCEPTION; import static com.swirlds.metrics.api.Metrics.INTERNAL_CATEGORY; +import static com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils.extractFromEvent; import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.base.time.Time; @@ -81,7 +82,8 @@ public DefaultTransactionPrehandler( * {@inheritDoc} */ @Override - public void prehandleApplicationTransactions(@NonNull final PlatformEvent event) { + public List> prehandleApplicationTransactions( + @NonNull final PlatformEvent event) { final long startTime = time.nanoTime(); ReservedSignedState latestImmutableState = null; @@ -102,5 +104,9 @@ public void prehandleApplicationTransactions(@NonNull final PlatformEvent event) preHandleTime.update(startTime, time.nanoTime()); } + + // TODO adapt this logic to read transactions directly from the callback passed in SwirldState.preHandle() when + // implemented + return extractFromEvent(event, StateSignatureTransaction.class); } } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPrehandler.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPrehandler.java index f6a58c527b01..de53d1258ab5 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPrehandler.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/eventhandling/TransactionPrehandler.java @@ -16,9 +16,12 @@ package com.swirlds.platform.eventhandling; +import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.common.wiring.component.InputWireLabel; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.event.PlatformEvent; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; /** * Performs the prehandling of transactions @@ -30,5 +33,6 @@ public interface TransactionPrehandler { * @param event the event to prehandle */ @InputWireLabel("PlatformEvent") - void prehandleApplicationTransactions(@NonNull PlatformEvent event); + List> prehandleApplicationTransactions( + @NonNull PlatformEvent event); } 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 e527f08d8444..ce99739dc690 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 @@ -16,6 +16,7 @@ package com.swirlds.platform.state; +import static com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils.extractFromRound; import static com.swirlds.platform.state.SwirldStateManagerUtils.fastCopy; import com.hedera.hapi.node.state.roster.Roster; @@ -126,11 +127,15 @@ public void setInitialState(@NonNull final MerkleRoot state) { * * @param round the round to handle */ - public void handleConsensusRound(final ConsensusRound round) { + public List> handleConsensusRound(final ConsensusRound round) { final MerkleRoot state = stateRef.get(); uptimeTracker.handleRound(round); transactionHandler.handleRound(round, state); + + // TODO update this logic to return the transactions from the callback consumer passed in + // state.getSwirldState().handleConsensusRound, when it is implemented + return extractFromRound(round, StateSignatureTransaction.class); } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java index 150ea4c9b18d..08f0cd191dc7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/iss/DefaultIssDetector.java @@ -28,10 +28,8 @@ import com.swirlds.common.utility.throttle.RateLimiter; import com.swirlds.logging.legacy.payload.IssPayload; import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.consensus.ConsensusConfig; -import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.metrics.IssMetrics; import com.swirlds.platform.sequence.map.ConcurrentSequenceMap; import com.swirlds.platform.sequence.map.SequenceMap; @@ -247,7 +245,7 @@ public List handleStateAndRound(@NonNull final StateAndRound st issNotifications.add(selfHashCheckResult); } - issNotifications.addAll(handlePostconsensusSignatures(stateAndRound.round())); + issNotifications.addAll(handlePostconsensusSignatures(stateAndRound.systemTransactions())); return issNotifications.isEmpty() ? null : issNotifications; } @@ -292,14 +290,12 @@ private IssNotification handleRemovedRound(@NonNull final RoundHashValidator rou /** * Handle postconsensus state signatures. * - * @param round the round that may contain state signatures + * @param stateSignatureTransactions the transactions containining state signatures * @return a list of ISS notifications, which may be empty, but will not contain null */ @NonNull - private List handlePostconsensusSignatures(@NonNull final ConsensusRound round) { - final List> stateSignatureTransactions = - SystemTransactionExtractionUtils.extractFromRound(round, StateSignatureTransaction.class); - + private List handlePostconsensusSignatures( + final List> stateSignatureTransactions) { if (stateSignatureTransactions == null) { return List.of(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java index b8c28c8338cd..c424d01ee5be 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformCoordinator.java @@ -16,9 +16,11 @@ package com.swirlds.platform.wiring; +import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.common.wiring.component.ComponentWiring; import com.swirlds.common.wiring.transformers.RoutableData; import com.swirlds.platform.components.consensus.ConsensusEngine; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.event.PlatformEvent; import com.swirlds.platform.event.branching.BranchDetector; import com.swirlds.platform.event.branching.BranchReporter; @@ -65,7 +67,8 @@ public class PlatformCoordinator { private final GossipWiring gossipWiring; private final ComponentWiring> consensusEngineWiring; private final ComponentWiring eventCreationManagerWiring; - private final ComponentWiring applicationTransactionPrehandlerWiring; + private final ComponentWiring>> + applicationTransactionPrehandlerWiring; private final ComponentWiring> stateSignatureCollectorWiring; private final ComponentWiring transactionHandlerWiring; private final ComponentWiring> roundDurabilityBufferWiring; @@ -110,7 +113,10 @@ public PlatformCoordinator( @NonNull final GossipWiring gossipWiring, @NonNull final ComponentWiring> consensusEngineWiring, @NonNull final ComponentWiring eventCreationManagerWiring, - @NonNull final ComponentWiring applicationTransactionPrehandlerWiring, + @NonNull + final ComponentWiring< + TransactionPrehandler, List>> + applicationTransactionPrehandlerWiring, @NonNull final ComponentWiring> stateSignatureCollectorWiring, diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index 5c5d23cd5a4e..a93ec07ed782 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -45,7 +45,6 @@ import com.swirlds.platform.components.appcomm.LatestCompleteStateNotifier; import com.swirlds.platform.components.consensus.ConsensusEngine; import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; -import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.consensus.EventWindow; import com.swirlds.platform.event.AncientMode; @@ -136,7 +135,8 @@ public class PlatformWiring { private final ComponentWiring pcesInlineWriterWiring; private final ComponentWiring> roundDurabilityBufferWiring; private final ComponentWiring pcesSequencerWiring; - private final ComponentWiring applicationTransactionPrehandlerWiring; + private final ComponentWiring>> + applicationTransactionPrehandlerWiring; private final ComponentWiring> stateSignatureCollectorWiring; private final GossipWiring gossipWiring; private final ComponentWiring eventWindowManagerWiring; @@ -493,16 +493,7 @@ private void wire() { splitOrphanBufferOutput.solderTo(applicationTransactionPrehandlerWiring.getInputWire( TransactionPrehandler::prehandleApplicationTransactions)); - // From the orphan buffer, extract signatures from preconsensus events for input to the StateSignatureCollector. - final WireTransformer>> - preConsensusTransformer = new WireTransformer<>( - model, - "extractPreconsensusSignatureTransactions", - "preconsensus signatures", - event -> SystemTransactionExtractionUtils.extractFromEvent( - event, StateSignatureTransaction.class)); - splitOrphanBufferOutput.solderTo(preConsensusTransformer.getInputWire()); - preConsensusTransformer + applicationTransactionPrehandlerWiring .getOutputWire() .solderTo(stateSignatureCollectorWiring.getInputWire( StateSignatureCollector::handlePreconsensusSignatures)); @@ -629,6 +620,15 @@ private void wire() { .getOutputWire() .buildTransformer("postHasher_getConsensusRound", "stateAndRound", StateAndRound::round); + transactionHandlerWiring + .getOutputWire() + .buildTransformer( + "getSystemTransactions", + "stateAndRound with system transactions", + StateAndRound::systemTransactions) + .solderTo(stateSignatureCollectorWiring.getInputWire( + StateSignatureCollector::handlePostconsensusSignatures)); + hashedStateOutputWire.solderTo(hashLoggerWiring.getInputWire(HashLogger::logHashes)); hashedStateOutputWire.solderTo(stateSignerWiring.getInputWire(StateSigner::signState)); hashedStateAndRoundOutputWire.solderTo(issDetectorWiring.getInputWire(IssDetector::handleStateAndRound)); @@ -643,19 +643,6 @@ private void wire() { // FUTURE WORK: combine the signedStateHasherWiring State and Round outputs into a single StateAndRound output. // FUTURE WORK: Split the single StateAndRound output into separate State and Round wires. - // Extract signatures from post-consensus events for input to the StateSignatureCollector. - final WireTransformer>> - postConsensusTransformer = new WireTransformer<>( - model, - "extractConsensusSignatureTransactions", - "consensus events", - round -> SystemTransactionExtractionUtils.extractFromRound( - round, StateSignatureTransaction.class)); - hashedConsensusRoundOutput.solderTo(postConsensusTransformer.getInputWire()); - postConsensusTransformer - .getOutputWire() - .solderTo(stateSignatureCollectorWiring.getInputWire( - StateSignatureCollector::handlePostconsensusSignatures)); // Solder the state output as input to the state signature collector. hashedStateOutputWire.solderTo( stateSignatureCollectorWiring.getInputWire(StateSignatureCollector::addReservedState)); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateAndRound.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateAndRound.java index c929d3c9303e..202ee453b91c 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateAndRound.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/components/StateAndRound.java @@ -16,17 +16,24 @@ package com.swirlds.platform.wiring.components; +import com.hedera.hapi.platform.event.StateSignatureTransaction; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.state.signed.ReservedSignedState; import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; /** * Contains a reserved signed state, and the consensus round which caused the state to be created * * @param reservedSignedState the state * @param round the round that caused the state to be created + * @param systemTransactions the system transactions that were included in the round */ -public record StateAndRound(@NonNull ReservedSignedState reservedSignedState, @NonNull ConsensusRound round) { +public record StateAndRound( + @NonNull ReservedSignedState reservedSignedState, + @NonNull ConsensusRound round, + @NonNull List> systemTransactions) { /** * Make an additional reservation on the reserved signed state * @@ -35,6 +42,6 @@ public record StateAndRound(@NonNull ReservedSignedState reservedSignedState, @N */ @NonNull public StateAndRound makeAdditionalReservation(@NonNull final String reservationReason) { - return new StateAndRound(reservedSignedState.getAndReserve(reservationReason), round); + return new StateAndRound(reservedSignedState.getAndReserve(reservationReason), round, systemTransactions); } } diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index 0ecbc9ec5626..a32832d3eb1c 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -318,7 +318,8 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { .build(); final ReservedSignedState reservedSignedState = signedState.reserve("initialTestReservation"); - controller.markSavedState(new StateAndRound(reservedSignedState, mock(ConsensusRound.class))); + controller.markSavedState( + new StateAndRound(reservedSignedState, mock(ConsensusRound.class), mock(ArrayList.class))); makeImmutable(reservedSignedState.get()); if (signedState.isStateToSave()) { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java index e7b84d2526b6..57be3249ed87 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/DefaultStateHasherTests.java @@ -26,6 +26,7 @@ import com.swirlds.platform.state.hasher.DefaultStateHasher; import com.swirlds.platform.state.hasher.StateHasher; import com.swirlds.platform.wiring.components.StateAndRound; +import java.util.ArrayList; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -47,7 +48,8 @@ void normalOperation() { final ReservedSignedState reservedSignedState = mock(ReservedSignedState.class); when(reservedSignedState.get()).thenReturn(signedState); - final StateAndRound stateAndRound = new StateAndRound(reservedSignedState, mock(ConsensusRound.class)); + final StateAndRound stateAndRound = + new StateAndRound(reservedSignedState, mock(ConsensusRound.class), mock(ArrayList.class)); // do the test final StateAndRound result = hasher.hashState(stateAndRound); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StateGarbageCollectorTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StateGarbageCollectorTests.java index 44f7db7fc1fd..15ce5a5e52ef 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StateGarbageCollectorTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StateGarbageCollectorTests.java @@ -27,6 +27,7 @@ import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import com.swirlds.platform.wiring.components.StateAndRound; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -69,7 +70,9 @@ void standardBehaviorTest() { .build(); unreleasedStates.add(signedState.reserve("hold local copy of state")); garbageCollector.registerState(new StateAndRound( - signedState.reserve("send state to garbage collector"), mock(ConsensusRound.class))); + signedState.reserve("send state to garbage collector"), + mock(ConsensusRound.class), + mock(ArrayList.class))); } // Randomly release some of the states. diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/state/notifications/StateHashedNotificationTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/state/notifications/StateHashedNotificationTest.java index 8be4e94bcd39..3f6c32024fc7 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/state/notifications/StateHashedNotificationTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/system/state/notifications/StateHashedNotificationTest.java @@ -19,12 +19,15 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.BDDMockito.given; +import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.common.crypto.Hash; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.wiring.components.StateAndRound; +import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -47,6 +50,9 @@ class StateHashedNotificationTest { @Mock private ReservedSignedState reservedSignedState; + @Mock + private List> systemTransactions; + @Test void factoryWorksAsExpected() { given(round.getRoundNum()).willReturn(ROUND); @@ -54,7 +60,8 @@ void factoryWorksAsExpected() { given(signedState.getState()).willReturn(merkleRoot); given(merkleRoot.getHash()).willReturn(HASH); - final var notification = StateHashedNotification.from(new StateAndRound(reservedSignedState, round)); + final var notification = + StateHashedNotification.from(new StateAndRound(reservedSignedState, round, systemTransactions)); assertEquals(ROUND, notification.round()); assertEquals(HASH, notification.hash()); diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java index 0accc487f637..61527bacf9df 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java @@ -37,6 +37,7 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.Randotron; +import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; @@ -202,14 +203,16 @@ void noIss() { final List eventsToInclude = selectRandomEvents(random, signatureEvents); final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, eventsToInclude); + final var systemTransactions = getScopedSystemTransactions(currentRound, roundHash); issDetectorTestHelper.handleStateAndRound( - new StateAndRound(mockState(currentRound, roundHash), consensusRound)); + new StateAndRound(mockState(currentRound, roundHash), consensusRound, systemTransactions)); } // Add all remaining unsubmitted signature events final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, signatureEvents); + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); issDetectorTestHelper.handleStateAndRound( - new StateAndRound(mockState(currentRound, randomHash(random)), consensusRound)); + new StateAndRound(mockState(currentRound, randomHash(random)), consensusRound, systemTransactions)); assertEquals(0, issDetectorTestHelper.getSelfIssCount(), "there should be no ISS notifications"); assertEquals( @@ -224,6 +227,22 @@ void noIss() { assertMarkerFile(IssType.OTHER_ISS.toString(), false); } + private List> getScopedSystemTransactions( + long currentRound, Hash roundHash) { + final var semanticVersion = new SemanticVersion(1, 0, 0, null, null); + final var stateSignatureTransaction = StateSignatureTransaction.newBuilder() + .round(currentRound) + .signature(Bytes.EMPTY) + .hash(roundHash.getBytes()) + .build(); + + final var scopedSystemTransaction = + new ScopedSystemTransaction(NodeId.of(1), semanticVersion, stateSignatureTransaction); + final var systemTransactions = new ArrayList>(); + systemTransactions.add(scopedSystemTransaction); + return systemTransactions; + } + /** * This test goes through a series of rounds, some of which experience ISSes. The test verifies that the expected * number of ISSes are registered by the ISS detector. @@ -321,14 +340,18 @@ void mixedOrderTest() { final List eventsToInclude = selectRandomEvents(random, signatureEvents); final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, eventsToInclude); - issDetectorTestHelper.handleStateAndRound( - new StateAndRound(mockState(currentRound, selfHashes.get((int) currentRound)), consensusRound)); + final var systemTransactions = currentRound % 2 == 1 + ? getScopedSystemTransactions(currentRound, randomHash(random)) + : new ArrayList>(); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( + mockState(currentRound, selfHashes.get((int) currentRound)), consensusRound, systemTransactions)); } // Add all remaining signature events final ConsensusRound consensusRound = createRoundWithSignatureEvents(roundsNonAncient, signatureEvents); + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); issDetectorTestHelper.handleStateAndRound( - new StateAndRound(mockState(roundsNonAncient, randomHash(random)), consensusRound)); + new StateAndRound(mockState(roundsNonAncient, randomHash(random)), consensusRound, systemTransactions)); assertEquals( expectedSelfIssCount, @@ -407,15 +430,22 @@ void decideForCatastrophicIss() { generateEventsContainingSignatures(random, currentRound, catastrophicHashData); // handle the catastrophic round, but don't submit any signatures yet, so it won't be detected + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, List.of()))); + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactions)); // handle some more rounds on top of the catastrophic round for (currentRound++; currentRound < 10; currentRound++) { // don't include any signatures + final var systemTransactionsForNonCatastrophicRound = currentRound % 2 == 1 + ? getScopedSystemTransactions(currentRound, randomHash(random)) + : new ArrayList>(); issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), createRoundWithSignatureEvents(currentRound, List.of()))); + mockState(currentRound, randomHash()), + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactionsForNonCatastrophicRound)); } // submit signatures on the ISS round that represent a minority of the weight @@ -432,9 +462,13 @@ void decideForCatastrophicIss() { signaturesToSubmit.add(signatureEvent); } + final var moreSystemTransactions = currentRound % 2 == 1 + ? getScopedSystemTransactions(currentRound, randomHash(random)) + : new ArrayList>(); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit))); + createRoundWithSignatureEvents(currentRound, signaturesToSubmit), + moreSystemTransactions)); assertEquals( 0, issDetectorTestHelper.getIssNotificationList().size(), @@ -443,9 +477,12 @@ void decideForCatastrophicIss() { currentRound++; // submit the remaining signatures in the next round + final var remainingSystemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound))); + createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound), + remainingSystemTransactions)); assertEquals( 1, issDetectorTestHelper.getCatastrophicIssCount(), "the catastrophic round should have caused an ISS"); @@ -540,14 +577,24 @@ void catastrophicShiftBeforeCompleteTest() { } // handle the catastrophic round, but it won't be decided yet, since there aren't enough signatures + final var systemTransactionsForCatastrophicRound = + getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit))); + createRoundWithSignatureEvents(currentRound, signaturesToSubmit), + systemTransactionsForCatastrophicRound)); // shift through until the catastrophic round is almost ready to be cleaned up for (currentRound++; currentRound < roundsNonAncient; currentRound++) { + final var systemTransactions = currentRound % 2 == 1 + ? getScopedSystemTransactions(currentRound, randomHash(random)) + : new ArrayList>(); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), createRoundWithSignatureEvents(currentRound, List.of()))); + mockState(currentRound, randomHash()), + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactions)); } assertEquals( @@ -557,8 +604,12 @@ void catastrophicShiftBeforeCompleteTest() { // Shift the window. Even though we have not added enough data for a decision, we will have added enough to lead // to a catastrophic ISS when the timeout is triggered. + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), createRoundWithSignatureEvents(currentRound, List.of()))); + mockState(currentRound, randomHash()), + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactions)); assertEquals(1, issDetectorTestHelper.getIssNotificationList().size(), "shifting should have caused an ISS"); assertEquals( @@ -613,9 +664,12 @@ void bigShiftTest() { random, currentRound, new RoundHashValidatorTests.HashGenerationData(catastrophicData, null)); // handle the catastrophic round, but don't submit any signatures yet, so it won't be detected + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, List.of()))); + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactions)); long submittedWeight = 0; final List signaturesToSubmit = new ArrayList<>(); @@ -634,9 +688,13 @@ void bigShiftTest() { currentRound++; // submit the supermajority of signatures + final var systemTransactionsForRoundWithSupermajorityOfSignatures = + getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit))); + createRoundWithSignatureEvents(currentRound, signaturesToSubmit), + systemTransactionsForRoundWithSupermajorityOfSignatures)); // Shifting the window a great distance should not trigger the ISS. issDetectorTestHelper.overridingState(mockState(roundsNonAncient + 100L, randomHash(random))); @@ -685,14 +743,21 @@ void ignoredRoundTest() { // handle the round and all signatures. // The round has a catastrophic ISS, but should be ignored + final var systemTransactionsForISSRound = getScopedSystemTransactions(currentRound, randomHash(random)); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound))); + createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound), + systemTransactionsForISSRound)); // shift through some rounds, to make sure nothing unexpected happens + final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + for (currentRound++; currentRound <= roundsNonAncient; currentRound++) { issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), createRoundWithSignatureEvents(currentRound, List.of()))); + mockState(currentRound, randomHash()), + createRoundWithSignatureEvents(currentRound, List.of()), + systemTransactions)); } assertEquals(0, issDetectorTestHelper.getIssNotificationList().size(), "ISS should have been ignored"); From 836d26204eaac2459d40bdab13d63082b2de2db7 Mon Sep 17 00:00:00 2001 From: Ivan Malygin Date: Tue, 17 Dec 2024 10:52:46 -0500 Subject: [PATCH 14/39] refactor: 17023 Dropped support of state file of v1. (#17030) Signed-off-by: Ivan Malygin --- .../migration/MigrationTestingToolMain.java | 4 +- .../migration/MigrationTestingToolState.java | 31 +- .../MigrationTestingToolStateRoot.java | 267 ------------------ .../platform/reconnect/ReconnectLearner.java | 2 +- .../platform/state/signed}/SigSet.java | 2 +- .../platform/state/signed/SignedState.java | 1 - .../state/signed/SignedStateInfo.java | 1 - .../state/snapshot/SignedStateFileReader.java | 30 +- .../state/snapshot/SignedStateFileWriter.java | 2 +- .../platform/SavedStateMetadataTests.java | 2 +- .../SignedStateFileReadWriteTest.java | 52 ---- .../swirlds/platform/state/SigSetTests.java | 2 +- .../platform/state/StateSigningTests.java | 2 +- .../merkle/MerkleTreeSnapshotReader.java | 44 +-- 14 files changed, 50 insertions(+), 392 deletions(-) delete mode 100644 platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolStateRoot.java rename platform-sdk/{swirlds-state-impl/src/main/java/com/swirlds/state/merkle => swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed}/SigSet.java (99%) diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java index 0baf09ed65f9..4c87694f44a4 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolMain.java @@ -59,8 +59,6 @@ public class MigrationTestingToolMain implements SwirldMain { FAKE_MERKLE_STATE_LIFECYCLES, version -> new BasicSoftwareVersion(version.major())); return migrationTestingToolState; })); - constructableRegistry.registerConstructable( - new ClassConstructorPair(MigrationTestingToolStateRoot.class, MigrationTestingToolStateRoot::new)); registerMerkleStateRootClassIds(); logger.info(STARTUP.getMarker(), "MigrationTestingToolState is registered with ConstructableRegistry"); } catch (ConstructableRegistryException e) { @@ -83,7 +81,7 @@ public class MigrationTestingToolMain implements SwirldMain { private double toCreate = 0; private long lastEventTime = System.nanoTime(); - public static final int SOFTWARE_VERSION = 6; + public static final int SOFTWARE_VERSION = 7; public static final BasicSoftwareVersion PREVIOUS_SOFTWARE_VERSION = new BasicSoftwareVersion(SOFTWARE_VERSION - 1); private final BasicSoftwareVersion softwareVersion = new BasicSoftwareVersion(SOFTWARE_VERSION); diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java index f6cdde58d952..504679ccff2e 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolState.java @@ -18,6 +18,7 @@ import static com.swirlds.demo.migration.MigrationTestingToolMain.PREVIOUS_SOFTWARE_VERSION; import static com.swirlds.logging.legacy.LogMarker.STARTUP; +import static com.swirlds.platform.test.fixtures.state.FakeMerkleStateLifecycles.FAKE_MERKLE_STATE_LIFECYCLES; import com.hedera.hapi.node.base.SemanticVersion; import com.hedera.hapi.platform.event.StateSignatureTransaction; @@ -84,6 +85,10 @@ private static class ClassVersion { * Add a virtual map and remove all blobs. */ public static final int VIRTUAL_MAP = 4; + /** + * Added ROSTERS and ROSTER_STATE + */ + public static final int ROSTERS = 5; } private static final long CLASS_ID = 0x1a0daec64a09f6a4L; @@ -93,13 +98,16 @@ private static class ClassVersion { */ private static class ChildIndices { public static final int UNUSED_PLATFORM_STATE = 0; - public static final int UNUSED_ROSTERS = 1; - public static final int UNUSED_ROSTER_STATE = 2; + public static final int MERKLE_MAP = 1; + public static final int VIRTUAL_MAP = 2; - public static final int MERKLE_MAP = 3; - public static final int VIRTUAL_MAP = 4; + public static final int UNUSED_ROSTERS = 3; + public static final int UNUSED_ROSTER_STATE = 4; public static final int CHILD_COUNT = 5; + + // these constants are to migrate from v4 to v5 + public static final int OLD_CHILD_COUNT = 3; } public NodeId selfId; @@ -108,6 +116,7 @@ public MigrationTestingToolState( @NonNull final MerkleStateLifecycles lifecycles, @NonNull final Function versionFactory) { super(lifecycles, versionFactory); + allocateSpaceForChild(ChildIndices.CHILD_COUNT); } private MigrationTestingToolState(final MigrationTestingToolState that) { @@ -122,7 +131,7 @@ private MigrationTestingToolState(final MigrationTestingToolState that) { */ @Override public int getMinimumChildCount() { - return ChildIndices.CHILD_COUNT; + return ChildIndices.OLD_CHILD_COUNT; } /** @@ -153,6 +162,16 @@ public boolean childHasExpectedType(final int index, final long childClassId) { } } + @Override + public MerkleNode migrate(int version) { + if (version == ClassVersion.VIRTUAL_MAP) { + FAKE_MERKLE_STATE_LIFECYCLES.initRosterState(this); + return this; + } + + return super.migrate(version); + } + /** * {@inheritDoc} */ @@ -308,7 +327,7 @@ public long getClassId() { */ @Override public int getVersion() { - return ClassVersion.VIRTUAL_MAP; + return ClassVersion.ROSTERS; } /** diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolStateRoot.java b/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolStateRoot.java deleted file mode 100644 index 194cbcd7d4a0..000000000000 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/src/main/java/com/swirlds/demo/migration/MigrationTestingToolStateRoot.java +++ /dev/null @@ -1,267 +0,0 @@ -/* - * 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.demo.migration; - -import static com.swirlds.platform.state.MerkleStateUtils.createInfoString; - -import com.swirlds.base.utility.ToStringBuilder; -import com.swirlds.common.merkle.MerkleNode; -import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; -import com.swirlds.common.merkle.route.MerkleRouteFactory; -import com.swirlds.common.utility.RuntimeObjectRecord; -import com.swirlds.common.utility.RuntimeObjectRegistry; -import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.PlatformState; -import com.swirlds.platform.state.PlatformStateAccessor; -import com.swirlds.platform.state.PlatformStateModifier; -import com.swirlds.platform.system.SwirldState; -import com.swirlds.state.merkle.MerkleTreeSnapshotWriter; -import edu.umd.cs.findbugs.annotations.NonNull; -import java.nio.file.Path; -import java.util.Objects; - -/** - * This is a temporary resurrection of {@code com.swirlds.platform.state.State} from the oblivion. This class is necessary - * to load existing state which still has {@code com.swirlds.platform.state.State} as a root of the tree. - * Without it the state cannot be deserialized. - * - * @deprecated This class should be removed after 0.57 release and once the source migration state is updated to 0.57. - */ -@Deprecated(forRemoval = true) -public class MigrationTestingToolStateRoot extends PartialNaryMerkleInternal implements MerkleRoot { - - private static final long CLASS_ID = 0x2971b4ba7dd84402L; - - public static class ClassVersion { - public static final int ORIGINAL = 1; - public static final int REMOVE_DUAL_STATE = 6; - public static final int MIGRATE_PLATFORM_STATE = 7; - } - - private static class ChildIndices { - /** - * The state written and used by the application. It is the state resulting from all transactions in consensus - * order from all events with received rounds up through the round this State represents. - */ - public static final int SWIRLD_STATE = 0; - /** - * The state written and used by the platform. - */ - public static final int PLATFORM_STATE = 1; - } - - /** - * Used to track the lifespan of this state. - */ - private final RuntimeObjectRecord registryRecord; - - public MigrationTestingToolStateRoot() { - registryRecord = RuntimeObjectRegistry.createRecord(getClass()); - updatePlatformState(new PlatformState()); - } - - private MigrationTestingToolStateRoot(final MigrationTestingToolStateRoot that) { - super(that); - - registryRecord = RuntimeObjectRegistry.createRecord(getClass()); - - if (that.getSwirldState() != null) { - this.setSwirldState(that.getSwirldState().copy()); - } - if (that.getWritablePlatformState() != null) { - this.updatePlatformState(that.getWritablePlatformState().copy()); - } - } - - /** - * {@inheritDoc} - */ - @Override - public MerkleNode migrate(final int version) { - if (version < ClassVersion.REMOVE_DUAL_STATE) { - throw new UnsupportedOperationException("State migration from version " + version + " is not supported." - + " The minimum supported version is " + getMinimumSupportedVersion()); - } - - if (version < ClassVersion.MIGRATE_PLATFORM_STATE - && getSwirldState() instanceof MigrationTestingToolState merkleStateRoot) { - PlatformState platformState = getWritablePlatformState().copy(); - setChild(ChildIndices.PLATFORM_STATE, null); - merkleStateRoot.updatePlatformState(platformState); - merkleStateRoot.setRoute(MerkleRouteFactory.getEmptyRoute()); - return merkleStateRoot.copy(); - } - - return this; - } - - /** - * {@inheritDoc} - */ - @Override - public int getMinimumSupportedVersion() { - return ClassVersion.REMOVE_DUAL_STATE; - } - - /** - * Get the application state. - * - * @return the application state - */ - @Override - @NonNull - public SwirldState getSwirldState() { - return getChild(ChildIndices.SWIRLD_STATE); - } - - /** - * Set the application state. - * - * @param state the application state - */ - 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 getWritablePlatformState() { - return getChild(ChildIndices.PLATFORM_STATE); - } - - /** - * Updates the platform state. - * - * @param modifier the platform state - */ - @Override - public void updatePlatformState(@NonNull final PlatformStateModifier modifier) { - if (modifier instanceof PlatformState platformState) { - setChild(ChildIndices.PLATFORM_STATE, platformState); - } else { - throw new UnsupportedOperationException("%s implementation of %s is not supported" - .formatted(modifier.getClass().getSimpleName(), PlatformStateModifier.class.getSimpleName())); - } - } - - /** - * {@inheritDoc} - */ - @Override - public long getClassId() { - return CLASS_ID; - } - - /** - * {@inheritDoc} - */ - @Override - public int getVersion() { - return ClassVersion.MIGRATE_PLATFORM_STATE; - } - - /** - * {@inheritDoc} - */ - @NonNull - @Override - public MerkleRoot copy() { - throwIfImmutable(); - throwIfDestroyed(); - setImmutable(true); - return new MigrationTestingToolStateRoot(this); - } - - /** - * {@inheritDoc} - */ - @Override - protected void destroyNode() { - registryRecord.release(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean equals(final Object other) { - if (this == other) { - return true; - } - if (other == null || getClass() != other.getClass()) { - return false; - } - final MerkleRoot state = (MerkleRoot) other; - return Objects.equals(getReadablePlatformState(), state.getReadablePlatformState()) - && Objects.equals(getSwirldState(), state.getSwirldState()); - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Objects.hash(getReadablePlatformState(), getSwirldState()); - } - - /** - * Generate a string that describes this state. - * - * @param hashDepth the depth of the tree to visit and print - */ - @NonNull - @Override - public String getInfoString(final int hashDepth) { - final PlatformStateAccessor platformState = getReadablePlatformState(); - return createInfoString(hashDepth, platformState, getHash(), this); - } - - /** - * {@inheritDoc} - */ - @Override - public void createSnapshot(@NonNull final Path targetPath) { - throwIfMutable(); - throwIfDestroyed(); - MerkleTreeSnapshotWriter.createSnapshot( - this, targetPath, getReadablePlatformState().getRound()); - } - - /** - * {@inheritDoc} - */ - @Override - public String toString() { - return new ToStringBuilder(this) - .append("platformState", getReadablePlatformState()) - .append("swirldState", getSwirldState()) - .toString(); - } -} 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 436ea59d991b..c92a02a0f808 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 @@ -32,12 +32,12 @@ import com.swirlds.platform.network.Connection; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.signed.ReservedSignedState; +import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateInvalidException; import com.swirlds.platform.state.signed.SignedStateValidationData; import com.swirlds.platform.state.signed.SignedStateValidator; import com.swirlds.platform.state.snapshot.SignedStateFileReader; -import com.swirlds.state.merkle.SigSet; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.net.SocketException; diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/SigSet.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SigSet.java similarity index 99% rename from platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/SigSet.java rename to platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SigSet.java index bea9e23707d0..1ddc733990ce 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/SigSet.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SigSet.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.swirlds.state.merkle; +package com.swirlds.platform.state.signed; import com.swirlds.common.FastCopyable; import com.swirlds.common.crypto.Signature; 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 30ef324cf41a..2058742e2baa 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 @@ -45,7 +45,6 @@ import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.merkle.MerkleStateRoot; -import com.swirlds.state.merkle.SigSet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.security.cert.X509Certificate; diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java index 46fdfcdf0969..33f4aa024ae7 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java @@ -17,7 +17,6 @@ package com.swirlds.platform.state.signed; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.state.merkle.SigSet; /** * Contains information about a signed state. A SignedStateInfo object is still ok to read after the parent SignedState diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java index 899964b20678..322e36c5a94a 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/SignedStateFileReader.java @@ -33,13 +33,13 @@ import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema; +import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.state.State; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; import com.swirlds.state.merkle.MerkleStateRoot; import com.swirlds.state.merkle.MerkleTreeSnapshotReader; -import com.swirlds.state.merkle.SigSet; import com.swirlds.state.merkle.StateMetadata; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.BufferedInputStream; @@ -95,26 +95,18 @@ public static List getSavedStateFiles( final DeserializedSignedState returnState; final MerkleTreeSnapshotReader.StateFileData data = MerkleTreeSnapshotReader.readStateFileData(stateFile); - - final MerkleTreeSnapshotReader.StateFileData normalizedData; - if (data.sigSet() == null) { - final File sigSetFile = - stateFile.getParent().resolve(SIGNATURE_SET_FILE_NAME).toFile(); - normalizedData = deserializeAndDebugOnFailure( - () -> new BufferedInputStream(new FileInputStream(sigSetFile)), - (final MerkleDataInputStream in) -> { - readAndCheckSigSetFileVersion(in); - final SigSet sigSet = in.readSerializable(); - return new MerkleTreeSnapshotReader.StateFileData(data.stateRoot(), data.hash(), sigSet); - }); - } else { - normalizedData = data; - } + final File sigSetFile = + stateFile.getParent().resolve(SIGNATURE_SET_FILE_NAME).toFile(); + final SigSet sigSet = deserializeAndDebugOnFailure( + () -> new BufferedInputStream(new FileInputStream(sigSetFile)), (final MerkleDataInputStream in) -> { + readAndCheckSigSetFileVersion(in); + return in.readSerializable(); + }); final SignedState newSignedState = new SignedState( configuration, CryptoStatic::verifySignature, - (MerkleRoot) normalizedData.stateRoot(), + (MerkleRoot) data.stateRoot(), "SignedStateFileReader.readStateFile()", false, false, @@ -122,10 +114,10 @@ public static List getSavedStateFiles( registerServiceStates(newSignedState); - newSignedState.setSigSet(normalizedData.sigSet()); + newSignedState.setSigSet(sigSet); returnState = new DeserializedSignedState( - newSignedState.reserve("SignedStateFileReader.readStateFile()"), normalizedData.hash()); + newSignedState.reserve("SignedStateFileReader.readStateFile()"), data.hash()); return returnState; } 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 1f1d6f5f1858..5be11a4f7a65 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 @@ -35,10 +35,10 @@ import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; import com.swirlds.platform.state.MerkleRoot; +import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.merkle.MerkleStateRoot; -import com.swirlds.state.merkle.SigSet; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.BufferedWriter; 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 4acc642a810b..3ca6cb49546b 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 @@ -44,13 +44,13 @@ import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.snapshot.SavedStateMetadata; import com.swirlds.platform.state.snapshot.SavedStateMetadataField; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.state.merkle.SigSet; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java index f1fcec1d7c26..78af965fc10d 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java @@ -37,7 +37,6 @@ import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.context.PlatformContext; -import com.swirlds.common.io.streams.MerkleDataOutputStream; import com.swirlds.common.io.utility.LegacyTemporaryFileBuilder; import com.swirlds.common.merkle.crypto.MerkleCryptoFactory; import com.swirlds.common.merkle.utility.MerkleTreeVisualizer; @@ -60,11 +59,7 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteBuffer; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -166,53 +161,6 @@ void writeThenReadStateFileTest() throws IOException { assertNotSame(signedState, deserializedSignedState.reservedSignedState(), "state should be a different object"); } - @Test - @DisplayName("Write Then Read State File (protocol v1) Test") - void writeThenReadStateFileTest_v1() throws IOException { - final SignedState signedState = new RandomSignedStateGenerator().build(); - final Path stateFile = testDirectory.resolve(SIGNED_STATE_FILE_NAME); - final Path signatureSetFile = testDirectory.resolve(SIGNATURE_SET_FILE_NAME); - - assertFalse(exists(stateFile), "signed state file should not yet exist"); - assertFalse(exists(signatureSetFile), "signature set file should not yet exist"); - - State state = (State) signedState.getState(); - state.copy(); - state.createSnapshot(testDirectory); - - // now we need to emulate v1 by modifying the protocol version and appending signatures to the state file - final byte[] fileContent = Files.readAllBytes(stateFile); - final int fileVersionOffset = 1; - ByteBuffer buffer = ByteBuffer.wrap(fileContent); - buffer.position(fileVersionOffset); - // set the protocol version to v1 - buffer.putInt(1); - try (OutputStream out = Files.newOutputStream( - stateFile, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE); - MerkleDataOutputStream merkleOut = new MerkleDataOutputStream(out)) { - // Write the modified content back to the file - out.write(fileContent); - // And append the signature set - merkleOut.writeSerializable(signedState.getSigSet(), true); - } - - assertTrue(exists(stateFile), "signed state file should be present"); - - final DeserializedSignedState deserializedSignedState = - readStateFile(TestPlatformContextBuilder.create().build().getConfiguration(), stateFile); - MerkleCryptoFactory.getInstance() - .digestTreeSync( - deserializedSignedState.reservedSignedState().get().getState()); - - assertNotNull(deserializedSignedState.originalHash(), "hash should not be null"); - assertEquals(signedState.getState().getHash(), deserializedSignedState.originalHash(), "hash should match"); - assertEquals( - signedState.getState().getHash(), - deserializedSignedState.reservedSignedState().get().getState().getHash(), - "hash should match"); - assertNotSame(signedState, deserializedSignedState.reservedSignedState(), "state should be a different object"); - } - @Test @DisplayName("writeSavedStateToDisk() Test") void writeSavedStateToDiskTest() throws IOException { diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SigSetTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SigSetTests.java index 4548b29489c3..a04728aafc34 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SigSetTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/SigSetTests.java @@ -30,7 +30,7 @@ import com.swirlds.common.io.streams.SerializableDataInputStream; import com.swirlds.common.io.streams.SerializableDataOutputStream; import com.swirlds.common.platform.NodeId; -import com.swirlds.state.merkle.SigSet; +import com.swirlds.platform.state.signed.SigSet; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; 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 bbacd4fc94a0..c622fa801c07 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 @@ -36,6 +36,7 @@ import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; import com.swirlds.merkledb.MerkleDb; +import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateInvalidException; import com.swirlds.platform.system.address.Address; @@ -43,7 +44,6 @@ import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.crypto.PreGeneratedX509Certs; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; -import com.swirlds.state.merkle.SigSet; import edu.umd.cs.findbugs.annotations.NonNull; import java.security.cert.X509Certificate; import java.util.ArrayList; diff --git a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleTreeSnapshotReader.java b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleTreeSnapshotReader.java index be3c9c86d145..82b7c310026a 100644 --- a/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleTreeSnapshotReader.java +++ b/platform-sdk/swirlds-state-impl/src/main/java/com/swirlds/state/merkle/MerkleTreeSnapshotReader.java @@ -22,7 +22,6 @@ import com.swirlds.common.io.streams.MerkleDataInputStream; import com.swirlds.common.merkle.impl.PartialNaryMerkleInternal; import edu.umd.cs.findbugs.annotations.NonNull; -import edu.umd.cs.findbugs.annotations.Nullable; import java.io.BufferedInputStream; import java.io.FileInputStream; import java.io.IOException; @@ -34,10 +33,6 @@ */ public class MerkleTreeSnapshotReader { - /** - * The previous version of the signed state file - */ - public static final int INIT_STATE_FILE_VERSION = 1; /** * The current version of the signed state file. A file of this version no longer contains the signature set, * instead the signature set is stored in a separate file. @@ -46,8 +41,7 @@ public class MerkleTreeSnapshotReader { /** * The supported versions of the signed state file */ - public static final Set SUPPORTED_STATE_FILE_VERSIONS = - Set.of(INIT_STATE_FILE_VERSION, SIG_SET_SEPARATE_STATE_FILE_VERSION); + public static final Set SUPPORTED_STATE_FILE_VERSIONS = Set.of(SIG_SET_SEPARATE_STATE_FILE_VERSION); /** * Prior to v1, the signed state file was not versioned. This byte was introduced in v1 to mark a versioned file. */ @@ -64,10 +58,8 @@ public class MerkleTreeSnapshotReader { * This is a helper class to hold the data read from a state file. * @param stateRoot the root of Merkle tree state * @param hash the hash of the state - * @param sigSet the signature set */ - public record StateFileData( - @NonNull PartialNaryMerkleInternal stateRoot, @NonNull Hash hash, @Nullable SigSet sigSet) {} + public record StateFileData(@NonNull PartialNaryMerkleInternal stateRoot, @NonNull Hash hash) {} /** * Reads a state file from disk @@ -83,10 +75,8 @@ public static StateFileData readStateFileData(@NonNull final Path stateFile) thr final int fileVersion = readAndCheckStateFileVersion(in); final Path directory = stateFile.getParent(); - if (fileVersion == INIT_STATE_FILE_VERSION) { - return readStateFileDataV1(stateFile, in, directory); - } else if (fileVersion == SIG_SET_SEPARATE_STATE_FILE_VERSION) { - return readStateFileDataV2(stateFile, in, directory); + if (fileVersion == SIG_SET_SEPARATE_STATE_FILE_VERSION) { + return readStateFileData(stateFile, in, directory); } else { throw new IOException("Unsupported state file version: " + fileVersion); } @@ -94,36 +84,16 @@ public static StateFileData readStateFileData(@NonNull final Path stateFile) thr } /** - * This method reads the state file data from a version 1 state file. This version of the state file contains - * signature set data. - */ - @NonNull - private static StateFileData readStateFileDataV1( - @NonNull final Path stateFile, @NonNull final MerkleDataInputStream in, @NonNull final Path directory) - throws IOException { - try { - final PartialNaryMerkleInternal state = in.readMerkleTree(directory, MAX_MERKLE_NODES_IN_STATE); - final Hash hash = in.readSerializable(); - final SigSet sigSet = in.readSerializable(); - return new StateFileData(state, hash, sigSet); - } catch (final IOException e) { - throw new IOException("Failed to read snapshot file " + stateFile.toFile(), e); - } - } - - /** - * This method reads the state file data from a version 2 state file. This version of the state file - * doesn't contain signature set data. Instead, the signature set data is stored in a separate file, - * and the resulting object doesn't have {@link SigSet} field initialized. + * This method reads the state file data from state file. */ @NonNull - private static StateFileData readStateFileDataV2( + private static StateFileData readStateFileData( @NonNull final Path stateFile, @NonNull final MerkleDataInputStream in, @NonNull final Path directory) throws IOException { try { final MerkleStateRoot state = in.readMerkleTree(directory, MAX_MERKLE_NODES_IN_STATE); final Hash hash = in.readSerializable(); - return new StateFileData(state, hash, null); + return new StateFileData(state, hash); } catch (final IOException e) { throw new IOException("Failed to read snapshot file " + stateFile.toFile(), e); From 720d1e0bc3f53d9867e4c1c250ebdeadbcc268e5 Mon Sep 17 00:00:00 2001 From: Kim Rader Date: Tue, 17 Dec 2024 09:47:42 -0800 Subject: [PATCH 15/39] feat: consolidate fee tests (#16846) Signed-off-by: Lev Povolotsky Signed-off-by: Kim Rader Co-authored-by: Lev Povolotsky Co-authored-by: Michael Tinker --- .../suites/consensus/SubmitMessageSuite.java | 18 - .../suites/consensus/TopicCreateSuite.java | 19 - .../suites/consensus/TopicUpdateSuite.java | 18 - .../crypto/CryptoApproveAllowanceSuite.java | 119 --- .../bdd/suites/crypto/CryptoCreateSuite.java | 92 -- .../suites/crypto/CryptoTransferSuite.java | 85 -- .../bdd/suites/fees/AllBaseOpFeesSuite.java | 271 ------ .../fees/ConsensusServiceFeesSuite.java | 113 +++ .../suites/fees/CryptoServiceFeesSuite.java | 518 ++++++++++ .../bdd/suites/fees/FileServiceFeesSuite.java | 147 +++ .../suites/fees/MiscellaneousFeesSuite.java | 105 ++ .../suites/fees/ScheduleServiceFeesSuite.java | 119 +++ .../suites/fees/TokenServiceFeesSuite.java | 911 ++++++++++++++++++ .../bdd/suites/file/FileAppendSuite.java | 40 - .../bdd/suites/hip904/TokenAirdropBase.java | 2 +- .../suites/hip904/TokenCancelAirdropTest.java | 73 -- .../suites/hip904/TokenClaimAirdropTest.java | 35 - .../schedule/FutureSchedulableOpsTest.java | 60 -- .../bdd/suites/schedule/ScheduleUtils.java | 8 +- .../bdd/suites/token/TokenCreateSpecs.java | 90 -- .../token/TokenFeeScheduleUpdateSpecs.java | 29 - .../bdd/suites/token/TokenPauseSpecs.java | 28 - .../suites/token/TokenUpdateNftsSuite.java | 77 -- .../bdd/suites/util/UtilPrngSuite.java | 30 - 24 files changed, 1918 insertions(+), 1089 deletions(-) create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ConsensusServiceFeesSuite.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CryptoServiceFeesSuite.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FileServiceFeesSuite.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/MiscellaneousFeesSuite.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ScheduleServiceFeesSuite.java create mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/TokenServiceFeesSuite.java diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java index d868ebddda83..d86c56538deb 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/SubmitMessageSuite.java @@ -36,7 +36,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.suites.HapiSuite.asOpArray; import static com.hedera.services.bdd.suites.HapiSuite.flattened; @@ -176,23 +175,6 @@ final Stream messageSubmissionOverSize() { .hasKnownStatus(MESSAGE_SIZE_TOO_LARGE)); } - @HapiTest - final Stream feeAsExpected() { - final byte[] messageBytes = new byte[100]; // 4k - Arrays.fill(messageBytes, (byte) 0b1); - return hapiTest( - cryptoCreate("payer").hasRetryPrecheckFrom(BUSY), - createTopic("testTopic").submitKeyName("payer").hasRetryPrecheckFrom(BUSY), - submitMessageTo("testTopic") - .blankMemo() - .payingWith("payer") - .message(new String(messageBytes)) - .hasRetryPrecheckFrom(BUSY) - .via("submitMessage"), - sleepFor(1000), - validateChargedUsd("submitMessage", 0.0001)); - } - @HapiTest final Stream messageSubmissionCorrectlyUpdatesRunningHash() { String topic = "testTopic"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java index 161a03a61d23..65480a71c732 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicCreateSuite.java @@ -32,12 +32,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; import static com.hedera.services.bdd.suites.HapiSuite.NONSENSE_KEY; -import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hedera.services.bdd.suites.contract.hapi.ContractCallSuite.PAY_RECEIVABLE_CONTRACT; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_ACCOUNT_NOT_ALLOWED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; @@ -213,23 +211,6 @@ final Stream allFieldsSetHappyCase() { .autoRenewAccountId("autoRenewAccount")); } - @HapiTest - final Stream feeAsExpected() { - return hapiTest( - newKeyNamed("adminKey"), - newKeyNamed("submitKey"), - cryptoCreate("autoRenewAccount"), - cryptoCreate("payer").balance(ONE_HUNDRED_HBARS), - createTopic("testTopic") - .topicMemo("testmemo") - .adminKeyName("adminKey") - .submitKeyName("submitKey") - .autoRenewAccountId("autoRenewAccount") - .payingWith("payer") - .via("topicCreate"), - validateChargedUsd("topicCreate", 0.0226)); - } - @HapiTest final Stream getInfoIdVariantsTreatedAsExpected() { return hapiTest( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java index 1392d8a8a25a..e0ebff715c66 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/consensus/TopicUpdateSuite.java @@ -26,11 +26,9 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.specOps; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.suites.HapiSuite.EMPTY_KEY; import static com.hedera.services.bdd.suites.HapiSuite.NONSENSE_KEY; -import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.HapiSuite.ZERO_BYTE_MEMO; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_ACCOUNT_NOT_ALLOWED; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; @@ -259,20 +257,4 @@ final Stream updateSubmitKeyOnTopicWithNoAdminKeyFails() { createTopic("testTopic"), updateTopic("testTopic").submitKey("submitKey").hasKnownStatus(UNAUTHORIZED)); } - - @HapiTest - final Stream feeAsExpected() { - return hapiTest( - cryptoCreate("autoRenewAccount"), - cryptoCreate("payer"), - createTopic("testTopic") - .autoRenewAccountId("autoRenewAccount") - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS - 1) - .adminKeyName("payer"), - updateTopic("testTopic") - .payingWith("payer") - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .via("updateTopic"), - validateChargedUsdWithin("updateTopic", 0.00022, 3.0)); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java index 12f13e063377..6429600df4db 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoApproveAllowanceSuite.java @@ -871,125 +871,6 @@ final Stream canHaveMultipleOwners() { .cryptoAllowancesContaining(SPENDER, 2 * ONE_HBAR))); } - @HapiTest - final Stream feesAsExpected() { - return hapiTest( - newKeyNamed(SUPPLY_KEY), - cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), - cryptoCreate(SPENDER).balance(ONE_HUNDRED_HBARS), - cryptoCreate(ANOTHER_SPENDER).balance(ONE_HUNDRED_HBARS), - cryptoCreate(SECOND_SPENDER).balance(ONE_HUNDRED_HBARS), - cryptoCreate(TOKEN_TREASURY).balance(100 * ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), - tokenCreate(FUNGIBLE_TOKEN) - .tokenType(TokenType.FUNGIBLE_COMMON) - .supplyType(TokenSupplyType.FINITE) - .supplyKey(SUPPLY_KEY) - .maxSupply(1000L) - .initialSupply(10L) - .treasury(TOKEN_TREASURY), - tokenCreate(NON_FUNGIBLE_TOKEN) - .maxSupply(10L) - .initialSupply(0) - .supplyType(TokenSupplyType.FINITE) - .tokenType(NON_FUNGIBLE_UNIQUE) - .supplyKey(SUPPLY_KEY) - .treasury(TOKEN_TREASURY), - tokenAssociate(OWNER, FUNGIBLE_TOKEN), - tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), - mintToken( - NON_FUNGIBLE_TOKEN, - List.of( - ByteString.copyFromUtf8("a"), - ByteString.copyFromUtf8("b"), - ByteString.copyFromUtf8("c"))) - .via(NFT_TOKEN_MINT_TXN), - mintToken(FUNGIBLE_TOKEN, 500L).via(FUNGIBLE_TOKEN_MINT_TXN), - cryptoTransfer(movingUnique(NON_FUNGIBLE_TOKEN, 1L, 2L, 3L).between(TOKEN_TREASURY, OWNER)), - cryptoApproveAllowance() - .payingWith(OWNER) - .addCryptoAllowance(OWNER, SPENDER, 100L) - .via("approve") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approve", 0.05, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) - .via("approveTokenTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveTokenTxn", 0.05012, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L)) - .via("approveNftTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveNftTxn", 0.050101, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, ANOTHER_SPENDER, true, List.of()) - .via("approveForAllNftTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveForAllNftTxn", 0.05, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addCryptoAllowance(OWNER, SECOND_SPENDER, 100L) - .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SECOND_SPENDER, 100L) - .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SECOND_SPENDER, false, List.of(1L)) - .via(APPROVE_TXN) - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin(APPROVE_TXN, 0.05238, 0.01), - getAccountDetails(OWNER) - .payingWith(GENESIS) - .has(accountDetailsWith() - .cryptoAllowancesCount(2) - .nftApprovedForAllAllowancesCount(1) - .tokenAllowancesCount(2) - .cryptoAllowancesContaining(SECOND_SPENDER, 100L) - .tokenAllowancesContaining(FUNGIBLE_TOKEN, SECOND_SPENDER, 100L)), - /* edit existing allowances */ - cryptoApproveAllowance() - .payingWith(OWNER) - .addCryptoAllowance(OWNER, SECOND_SPENDER, 200L) - .via("approveModifyCryptoTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveModifyCryptoTxn", 0.049375, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SECOND_SPENDER, 200L) - .via("approveModifyTokenTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveModifyTokenTxn", 0.04943, 0.01), - cryptoApproveAllowance() - .payingWith(OWNER) - .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, ANOTHER_SPENDER, false, List.of()) - .via("approveModifyNftTxn") - .fee(ONE_HBAR) - .blankMemo() - .logged(), - validateChargedUsdWithin("approveModifyNftTxn", 0.049375, 0.01), - getAccountDetails(OWNER) - .payingWith(GENESIS) - .has(accountDetailsWith() - .cryptoAllowancesCount(2) - .nftApprovedForAllAllowancesCount(0) - .tokenAllowancesCount(2) - .cryptoAllowancesContaining(SECOND_SPENDER, 200L) - .tokenAllowancesContaining(FUNGIBLE_TOKEN, SECOND_SPENDER, 200L))); - } - @HapiTest final Stream serialsInAscendingOrder() { return hapiTest( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java index bd6be0e2b2b4..7b12f52ef32b 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoCreateSuite.java @@ -63,7 +63,6 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTORENEW_DURATION_NOT_IN_RANGE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BAD_ENCODING; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_GAS; -import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ACCOUNT_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ADMIN_KEY; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ALIAS_KEY; @@ -220,97 +219,6 @@ final Stream cannotCreateAnAccountWithLongZeroKeyButCanUseEvmAddres sourcing(() -> getTxnRecord(creation).logged())); } - @HapiTest - final Stream usdFeeAsExpected() { - double expectedPriceUsd = 0.05; - final var noAutoAssocSlots = "noAutoAssocSlots"; - final var oneAutoAssocSlot = "oneAutoAssocSlot"; - final var tenAutoAssocSlots = "tenAutoAssocSlots"; - final var negativeAutoAssocSlots = "negativeAutoAssocSlots"; - final var positiveOverflowAutoAssocSlots = "positiveOverflowAutoAssocSlots"; - final var unlimitedAutoAssocSlots = "unlimitedAutoAssocSlots"; - final var token = "token"; - return hapiTest( - cryptoCreate(CIVILIAN).balance(5 * ONE_HUNDRED_HBARS), - getAccountBalance(CIVILIAN).hasTinyBars(5 * ONE_HUNDRED_HBARS), - tokenCreate(token).autoRenewPeriod(THREE_MONTHS_IN_SECONDS), - cryptoCreate("neverToBe") - .balance(0L) - .memo("") - .entityMemo("") - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .payingWith(CIVILIAN) - .feeUsd(0.01) - .hasPrecheck(INSUFFICIENT_TX_FEE), - getAccountBalance(CIVILIAN).hasTinyBars(5 * ONE_HUNDRED_HBARS), - cryptoCreate(noAutoAssocSlots) - .key(CIVILIAN) - .balance(0L) - .via(noAutoAssocSlots) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN), - cryptoCreate(oneAutoAssocSlot) - .key(CIVILIAN) - .balance(0L) - .maxAutomaticTokenAssociations(1) - .via(oneAutoAssocSlot) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN), - cryptoCreate(tenAutoAssocSlots) - .key(CIVILIAN) - .balance(0L) - .maxAutomaticTokenAssociations(10) - .via(tenAutoAssocSlots) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN), - cryptoCreate(negativeAutoAssocSlots) - .key(CIVILIAN) - .balance(0L) - .maxAutomaticTokenAssociations(-2) - .via(negativeAutoAssocSlots) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN) - .logged() - .hasPrecheck(INVALID_MAX_AUTO_ASSOCIATIONS), - cryptoCreate(positiveOverflowAutoAssocSlots) - .key(CIVILIAN) - .balance(0L) - .maxAutomaticTokenAssociations(5001) - .via(positiveOverflowAutoAssocSlots) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN) - .logged() - .hasKnownStatus(INVALID_MAX_AUTO_ASSOCIATIONS), - cryptoCreate(unlimitedAutoAssocSlots) - .key(CIVILIAN) - .balance(0L) - .maxAutomaticTokenAssociations(-1) - .via(unlimitedAutoAssocSlots) - .blankMemo() - .autoRenewSecs(THREE_MONTHS_IN_SECONDS) - .signedBy(CIVILIAN) - .payingWith(CIVILIAN), - getTxnRecord(tenAutoAssocSlots).logged(), - validateChargedUsd(noAutoAssocSlots, expectedPriceUsd), - getAccountInfo(noAutoAssocSlots).hasMaxAutomaticAssociations(0), - validateChargedUsd(oneAutoAssocSlot, expectedPriceUsd), - getAccountInfo(oneAutoAssocSlot).hasMaxAutomaticAssociations(1), - validateChargedUsd(tenAutoAssocSlots, expectedPriceUsd), - getAccountInfo(tenAutoAssocSlots).hasMaxAutomaticAssociations(10), - validateChargedUsd(unlimitedAutoAssocSlots, expectedPriceUsd), - getAccountInfo(unlimitedAutoAssocSlots).hasMaxAutomaticAssociations(-1)); - } - @LeakyHapiTest(overrides = {"entities.unlimitedAutoAssociationsEnabled"}) final Stream createFailsIfMaxAutoAssocIsNegativeAndUnlimitedFlagDisabled() { return hapiTest( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java index 5dfa85b0d66d..b0afa2fa8cb9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/crypto/CryptoTransferSuite.java @@ -95,7 +95,6 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.usableTxnIdNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withTargetLedgerId; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; @@ -1355,90 +1354,6 @@ final Stream autoAssociationRequiresOpenSlots() { cryptoTransfer(moving(1, tokenB).between(TREASURY, firstUser))); } - @HapiTest - final Stream baseCryptoTransferFeeChargedAsExpected() { - final var expectedHbarXferPriceUsd = 0.0001; - final var expectedHtsXferPriceUsd = 0.001; - final var expectedNftXferPriceUsd = 0.001; - final var expectedHtsXferWithCustomFeePriceUsd = 0.002; - final var expectedNftXferWithCustomFeePriceUsd = 0.002; - final var transferAmount = 1L; - final var customFeeCollector = "customFeeCollector"; - final var nonTreasurySender = "nonTreasurySender"; - final var hbarXferTxn = "hbarXferTxn"; - final var fungibleToken = "fungibleToken"; - final var fungibleTokenWithCustomFee = "fungibleTokenWithCustomFee"; - final var htsXferTxn = "htsXferTxn"; - final var htsXferTxnWithCustomFee = "htsXferTxnWithCustomFee"; - final var nonFungibleToken = "nonFungibleToken"; - final var nonFungibleTokenWithCustomFee = "nonFungibleTokenWithCustomFee"; - final var nftXferTxn = "nftXferTxn"; - final var nftXferTxnWithCustomFee = "nftXferTxnWithCustomFee"; - - return hapiTest( - cryptoCreate(nonTreasurySender).balance(ONE_HUNDRED_HBARS), - cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), - cryptoCreate(RECEIVER), - cryptoCreate(customFeeCollector), - tokenCreate(fungibleToken) - .treasury(SENDER) - .tokenType(FUNGIBLE_COMMON) - .initialSupply(100L), - tokenCreate(fungibleTokenWithCustomFee) - .treasury(SENDER) - .tokenType(FUNGIBLE_COMMON) - .withCustom(fixedHbarFee(transferAmount, customFeeCollector)) - .initialSupply(100L), - tokenAssociate(RECEIVER, fungibleToken, fungibleTokenWithCustomFee), - newKeyNamed(SUPPLY_KEY), - tokenCreate(nonFungibleToken) - .initialSupply(0) - .supplyKey(SUPPLY_KEY) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(SENDER), - tokenCreate(nonFungibleTokenWithCustomFee) - .initialSupply(0) - .supplyKey(SUPPLY_KEY) - .tokenType(NON_FUNGIBLE_UNIQUE) - .withCustom(fixedHbarFee(transferAmount, customFeeCollector)) - .treasury(SENDER), - tokenAssociate(nonTreasurySender, List.of(fungibleTokenWithCustomFee, nonFungibleTokenWithCustomFee)), - mintToken(nonFungibleToken, List.of(copyFromUtf8("memo1"))), - mintToken(nonFungibleTokenWithCustomFee, List.of(copyFromUtf8("memo2"))), - tokenAssociate(RECEIVER, nonFungibleToken, nonFungibleTokenWithCustomFee), - cryptoTransfer(movingUnique(nonFungibleTokenWithCustomFee, 1).between(SENDER, nonTreasurySender)) - .payingWith(SENDER), - cryptoTransfer(moving(1, fungibleTokenWithCustomFee).between(SENDER, nonTreasurySender)) - .payingWith(SENDER), - cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 100L)) - .payingWith(SENDER) - .blankMemo() - .via(hbarXferTxn), - cryptoTransfer(moving(1, fungibleToken).between(SENDER, RECEIVER)) - .blankMemo() - .payingWith(SENDER) - .via(htsXferTxn), - cryptoTransfer(movingUnique(nonFungibleToken, 1).between(SENDER, RECEIVER)) - .blankMemo() - .payingWith(SENDER) - .via(nftXferTxn), - cryptoTransfer(moving(1, fungibleTokenWithCustomFee).between(nonTreasurySender, RECEIVER)) - .blankMemo() - .fee(ONE_HBAR) - .payingWith(nonTreasurySender) - .via(htsXferTxnWithCustomFee), - cryptoTransfer(movingUnique(nonFungibleTokenWithCustomFee, 1).between(nonTreasurySender, RECEIVER)) - .blankMemo() - .fee(ONE_HBAR) - .payingWith(nonTreasurySender) - .via(nftXferTxnWithCustomFee), - validateChargedUsdWithin(hbarXferTxn, expectedHbarXferPriceUsd, 0.01), - validateChargedUsdWithin(htsXferTxn, expectedHtsXferPriceUsd, 0.01), - validateChargedUsdWithin(nftXferTxn, expectedNftXferPriceUsd, 0.01), - validateChargedUsdWithin(htsXferTxnWithCustomFee, expectedHtsXferWithCustomFeePriceUsd, 0.1), - validateChargedUsdWithin(nftXferTxnWithCustomFee, expectedNftXferWithCustomFeePriceUsd, 0.3)); - } - @HapiTest final Stream okToSetInvalidPaymentHeaderForCostAnswer() { return hapiTest( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java index e25425892e07..c26660901e02 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/AllBaseOpFeesSuite.java @@ -26,35 +26,22 @@ import static com.hedera.services.bdd.spec.keys.SigControl.ON; import static com.hedera.services.bdd.spec.keys.SigControl.threshSigs; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenReject; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.wipeTokenAccount; import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; -import static com.hedera.services.bdd.spec.transactions.token.HapiTokenReject.rejectingNFT; -import static com.hedera.services.bdd.spec.transactions.token.HapiTokenReject.rejectingToken; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; -import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.assertionsHold; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; 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.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.THREE_MONTHS_IN_SECONDS; -import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; -import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INSUFFICIENT_TX_FEE; import static com.hederahashgraph.api.proto.java.TokenType.NON_FUNGIBLE_UNIQUE; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -67,8 +54,6 @@ import com.hedera.services.bdd.spec.transactions.TxnUtils; import com.hederahashgraph.api.proto.java.AccountAmount; import com.hederahashgraph.api.proto.java.AccountID; -import com.hederahashgraph.api.proto.java.TokenSupplyType; -import com.hederahashgraph.api.proto.java.TokenType; import com.hederahashgraph.api.proto.java.TransactionRecord; import java.time.Instant; import java.util.List; @@ -80,94 +65,16 @@ public class AllBaseOpFeesSuite { private static final String PAYER = "payer"; private static final double ALLOWED_DIFFERENCE_PERCENTAGE = 0.01; - private static final double ALLOWED_DIFFERENCE = 1; - private static final String TREASURE_KEY = "treasureKey"; - private static final String FUNGIBLE_COMMON_TOKEN = "fungibleCommonToken"; - - private static final String ADMIN_KEY = "adminKey"; - private static final String MULTI_KEY = "multiKey"; private static final String SUPPLY_KEY = "supplyKey"; - private static final String FREEZE_KEY = "freezeKey"; - private static final String WIPE_KEY = "wipeKey"; - private static final String KYC_KEY = "kycKey"; private static final String CIVILIAN_ACCT = "civilian"; - private static final String ALICE = "alice"; private static final String UNIQUE_TOKEN = "nftType"; private static final String BASE_TXN = "baseTxn"; - private static final String UNFREEZE = "unfreeze"; - - private static final double EXPECTED_FUNGIBLE_REJECT_PRICE_USD = 0.001; - private static final double EXPECTED_NFT_REJECT_PRICE_USD = 0.00100245; - private static final double EXPECTED_MIX_REJECT_PRICE_USD = 0.00375498; - private static final double EXPECTED_UNFREEZE_PRICE_USD = 0.001; - private static final double EXPECTED_FREEZE_PRICE_USD = 0.001; private static final double EXPECTED_NFT_MINT_PRICE_USD = 0.02; - private static final double EXPECTED_NFT_BURN_PRICE_USD = 0.001; - private static final double EXPECTED_NFT_WIPE_PRICE_USD = 0.001; - - @HapiTest - final Stream baseNftMintOperationIsChargedExpectedFee() { - final var standard100ByteMetadata = ByteString.copyFromUtf8( - "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); - - return defaultHapiSpec("BaseUniqueMintOperationIsChargedExpectedFee") - .given( - newKeyNamed(SUPPLY_KEY), - cryptoCreate(CIVILIAN_ACCT).balance(ONE_MILLION_HBARS).key(SUPPLY_KEY), - tokenCreate(UNIQUE_TOKEN) - .initialSupply(0L) - .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) - .supplyKey(SUPPLY_KEY) - .tokenType(NON_FUNGIBLE_UNIQUE)) - .when(mintToken(UNIQUE_TOKEN, List.of(standard100ByteMetadata)) - .payingWith(CIVILIAN_ACCT) - .signedBy(SUPPLY_KEY) - .blankMemo() - .fee(ONE_HUNDRED_HBARS) - .via(BASE_TXN)) - .then(validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_MINT_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); - } - - @HapiTest - final Stream NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers() { - final var expectedFee = 10 * EXPECTED_NFT_MINT_PRICE_USD; - final var standard100ByteMetadata = ByteString.copyFromUtf8( - "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); - - return defaultHapiSpec("NftMintsScaleLinearlyBasedOnNumberOfSerialNumbers") - .given( - newKeyNamed(SUPPLY_KEY), - cryptoCreate(CIVILIAN_ACCT).balance(ONE_MILLION_HBARS).key(SUPPLY_KEY), - tokenCreate(UNIQUE_TOKEN) - .initialSupply(0L) - .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) - .supplyKey(SUPPLY_KEY) - .tokenType(NON_FUNGIBLE_UNIQUE)) - .when(mintToken( - UNIQUE_TOKEN, - List.of( - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata, - standard100ByteMetadata)) - .payingWith(CIVILIAN_ACCT) - .signedBy(SUPPLY_KEY) - .blankMemo() - .fee(ONE_HUNDRED_HBARS) - .via(BASE_TXN)) - .then(validateChargedUsdWithin(BASE_TXN, expectedFee, ALLOWED_DIFFERENCE_PERCENTAGE)); - } @HapiTest final Stream NftMintsScaleLinearlyBasedOnNumberOfSignatures() { @@ -195,184 +102,6 @@ final Stream NftMintsScaleLinearlyBasedOnNumberOfSignatures() { .then(validateChargedUsdWithin("moreSigsTxn", expectedFee, ALLOWED_DIFFERENCE_PERCENTAGE)); } - @HapiTest - final Stream baseNftWipeOperationIsChargedExpectedFee() { - return defaultHapiSpec("BaseUniqueWipeOperationIsChargedExpectedFee") - .given( - newKeyNamed(SUPPLY_KEY), - newKeyNamed(WIPE_KEY), - cryptoCreate(CIVILIAN_ACCT).key(WIPE_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(WIPE_KEY), - tokenCreate(UNIQUE_TOKEN) - .tokenType(NON_FUNGIBLE_UNIQUE) - .supplyType(TokenSupplyType.INFINITE) - .initialSupply(0L) - .supplyKey(SUPPLY_KEY) - .wipeKey(WIPE_KEY) - .treasury(TOKEN_TREASURY), - tokenAssociate(CIVILIAN_ACCT, UNIQUE_TOKEN), - mintToken(UNIQUE_TOKEN, List.of(ByteString.copyFromUtf8("token_to_wipe"))), - cryptoTransfer(movingUnique(UNIQUE_TOKEN, 1L).between(TOKEN_TREASURY, CIVILIAN_ACCT))) - .when(wipeTokenAccount(UNIQUE_TOKEN, CIVILIAN_ACCT, List.of(1L)) - .payingWith(TOKEN_TREASURY) - .fee(ONE_HBAR) - .blankMemo() - .via(BASE_TXN)) - .then(validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_WIPE_PRICE_USD, 0.01)); - } - - @HapiTest - final Stream baseNftBurnOperationIsChargedExpectedFee() { - return defaultHapiSpec("BaseUniqueBurnOperationIsChargedExpectedFee") - .given( - newKeyNamed(SUPPLY_KEY), - cryptoCreate(CIVILIAN_ACCT).key(SUPPLY_KEY), - cryptoCreate(TOKEN_TREASURY), - tokenCreate(UNIQUE_TOKEN) - .initialSupply(0) - .supplyKey(SUPPLY_KEY) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY), - mintToken(UNIQUE_TOKEN, List.of(metadata("memo")))) - .when(burnToken(UNIQUE_TOKEN, List.of(1L)) - .fee(ONE_HBAR) - .payingWith(CIVILIAN_ACCT) - .blankMemo() - .via(BASE_TXN)) - .then(validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_BURN_PRICE_USD, 0.01)); - } - - @HapiTest - final Stream baseCommonTokenRejectChargedAsExpected() { - return defaultHapiSpec("baseCommonTokenRejectChargedAsExpected") - .given( - newKeyNamed(MULTI_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), - cryptoCreate(ALICE).balance(ONE_HUNDRED_HBARS), - tokenCreate(FUNGIBLE_COMMON_TOKEN) - .initialSupply(1000L) - .adminKey(MULTI_KEY) - .supplyKey(MULTI_KEY) - .treasury(TOKEN_TREASURY), - tokenCreate(UNIQUE_TOKEN) - .initialSupply(0) - .adminKey(MULTI_KEY) - .supplyKey(MULTI_KEY) - .treasury(TOKEN_TREASURY) - .tokenType(TokenType.NON_FUNGIBLE_UNIQUE), - mintToken( - UNIQUE_TOKEN, - List.of( - metadata("nemo the fish"), - metadata("garfield the cat"), - metadata("snoopy the dog"))), - tokenAssociate(ALICE, FUNGIBLE_COMMON_TOKEN, UNIQUE_TOKEN), - cryptoTransfer(movingUnique(UNIQUE_TOKEN, 1L).between(TOKEN_TREASURY, ALICE)) - .payingWith(TOKEN_TREASURY) - .via("nftTransfer"), - cryptoTransfer(moving(100, FUNGIBLE_COMMON_TOKEN).between(TOKEN_TREASURY, ALICE)) - .payingWith(TOKEN_TREASURY) - .via("fungibleTransfer")) - .when( - tokenReject(rejectingToken(FUNGIBLE_COMMON_TOKEN)) - .payingWith(ALICE) - .via("rejectFungible"), - tokenReject(rejectingNFT(UNIQUE_TOKEN, 1)) - .payingWith(ALICE) - .via("rejectNft"), - cryptoTransfer( - movingUnique(UNIQUE_TOKEN, 1L).between(TOKEN_TREASURY, ALICE), - moving(100, FUNGIBLE_COMMON_TOKEN).between(TOKEN_TREASURY, ALICE)) - .payingWith(ALICE) - .via("transferMix"), - tokenReject(ALICE, rejectingNFT(UNIQUE_TOKEN, 1), rejectingToken(FUNGIBLE_COMMON_TOKEN)) - .payingWith(TOKEN_TREASURY) - .via("rejectMix")) - .then( - validateChargedUsdWithin( - "fungibleTransfer", EXPECTED_FUNGIBLE_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), - validateChargedUsdWithin("nftTransfer", EXPECTED_NFT_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), - validateChargedUsdWithin("transferMix", EXPECTED_MIX_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), - validateChargedUsdWithin( - "rejectFungible", EXPECTED_FUNGIBLE_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), - validateChargedUsdWithin("rejectNft", EXPECTED_NFT_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), - validateChargedUsdWithin("rejectMix", EXPECTED_MIX_REJECT_PRICE_USD, ALLOWED_DIFFERENCE)); - } - - @HapiTest - final Stream baseNftFreezeUnfreezeChargedAsExpected() { - return defaultHapiSpec("baseNftFreezeUnfreezeChargedAsExpected") - .given( - newKeyNamed(TREASURE_KEY), - newKeyNamed(ADMIN_KEY), - newKeyNamed(KYC_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(TREASURE_KEY), - cryptoCreate(CIVILIAN_ACCT), - tokenCreate(UNIQUE_TOKEN) - .tokenType(NON_FUNGIBLE_UNIQUE) - .supplyType(TokenSupplyType.INFINITE) - .initialSupply(0L) - .adminKey(ADMIN_KEY) - .freezeKey(TOKEN_TREASURY) - .kycKey(KYC_KEY) - .freezeDefault(false) - .treasury(TOKEN_TREASURY) - .payingWith(TOKEN_TREASURY) - .supplyKey(ADMIN_KEY) - .via(BASE_TXN), - tokenAssociate(CIVILIAN_ACCT, UNIQUE_TOKEN)) - .when( - tokenFreeze(UNIQUE_TOKEN, CIVILIAN_ACCT) - .blankMemo() - .signedBy(TOKEN_TREASURY) - .payingWith(TOKEN_TREASURY) - .via("freeze"), - tokenUnfreeze(UNIQUE_TOKEN, CIVILIAN_ACCT) - .blankMemo() - .payingWith(TOKEN_TREASURY) - .signedBy(TOKEN_TREASURY) - .via(UNFREEZE)) - .then( - validateChargedUsdWithin("freeze", EXPECTED_FREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE), - validateChargedUsdWithin(UNFREEZE, EXPECTED_UNFREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); - } - - @HapiTest - final Stream baseCommonFreezeUnfreezeChargedAsExpected() { - return defaultHapiSpec("baseCommonFreezeUnfreezeChargedAsExpected") - .given( - newKeyNamed(TREASURE_KEY), - newKeyNamed(ADMIN_KEY), - newKeyNamed(FREEZE_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(WIPE_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(TREASURE_KEY), - cryptoCreate(CIVILIAN_ACCT), - tokenCreate(FUNGIBLE_COMMON_TOKEN) - .adminKey(ADMIN_KEY) - .freezeKey(TOKEN_TREASURY) - .wipeKey(WIPE_KEY) - .supplyKey(SUPPLY_KEY) - .freezeDefault(false) - .treasury(TOKEN_TREASURY) - .payingWith(TOKEN_TREASURY), - tokenAssociate(CIVILIAN_ACCT, FUNGIBLE_COMMON_TOKEN)) - .when( - tokenFreeze(FUNGIBLE_COMMON_TOKEN, CIVILIAN_ACCT) - .blankMemo() - .signedBy(TOKEN_TREASURY) - .payingWith(TOKEN_TREASURY) - .via("freeze"), - tokenUnfreeze(FUNGIBLE_COMMON_TOKEN, CIVILIAN_ACCT) - .blankMemo() - .payingWith(TOKEN_TREASURY) - .signedBy(TOKEN_TREASURY) - .via(UNFREEZE)) - .then( - validateChargedUsdWithin("freeze", EXPECTED_FREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE), - validateChargedUsdWithin(UNFREEZE, EXPECTED_UNFREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); - } - @HapiTest final Stream feeCalcUsesNumPayerKeys() { SigControl SHAPE = threshSigs(2, threshSigs(2, ANY, ANY, ANY), threshSigs(2, ANY, ANY, ANY)); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ConsensusServiceFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ConsensusServiceFeesSuite.java new file mode 100644 index 000000000000..98cd0deb2ffb --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ConsensusServiceFeesSuite.java @@ -0,0 +1,113 @@ +/* + * 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.fees; + +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTopicInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.createTopic; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.deleteTopic; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.submitMessageTo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.updateTopic; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.BUSY; + +import com.hedera.services.bdd.junit.HapiTest; +import java.util.Arrays; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; + +public class ConsensusServiceFeesSuite { + private static final double BASE_FEE_TOPIC_CREATE = 0.01; + private static final double BASE_FEE_TOPIC_UPDATE = 0.00022; + private static final double BASE_FEE_TOPIC_DELETE = 0.005; + private static final double BASE_FEE_TOPIC_SUBMIT_MESSAGE = 0.0001; + private static final double BASE_FEE_TOPIC_GET_INFO = 0.0001; + + private static final String PAYER = "payer"; + private static final String TOPIC_NAME = "testTopic"; + + @HapiTest + @DisplayName("Topic create base USD fee as expected") + final Stream topicCreateBaseUSDFee() { + return hapiTest( + cryptoCreate(PAYER).balance(ONE_HUNDRED_HBARS), + createTopic(TOPIC_NAME).blankMemo().payingWith(PAYER).via("topicCreate"), + validateChargedUsd("topicCreate", BASE_FEE_TOPIC_CREATE)); + } + + @HapiTest + @DisplayName("Topic update base USD fee as expected") + final Stream topicUpdateBaseUSDFee() { + return hapiTest( + cryptoCreate("autoRenewAccount"), + cryptoCreate(PAYER), + createTopic(TOPIC_NAME) + .autoRenewAccountId("autoRenewAccount") + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS - 1) + .adminKeyName(PAYER), + updateTopic(TOPIC_NAME) + .payingWith(PAYER) + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) + .via("updateTopic"), + validateChargedUsdWithin("updateTopic", BASE_FEE_TOPIC_UPDATE, 3.0)); + } + + @HapiTest + @DisplayName("Topic delete base USD fee as expected") + final Stream topicDeleteBaseUSDFee() { + return hapiTest( + cryptoCreate(PAYER), + createTopic(TOPIC_NAME).adminKeyName(PAYER), + deleteTopic(TOPIC_NAME).blankMemo().payingWith(PAYER).via("topicDelete"), + validateChargedUsd("topicDelete", BASE_FEE_TOPIC_DELETE)); + } + + @HapiTest + @DisplayName("Topic submit message base USD fee as expected") + final Stream topicSubmitMessageBaseUSDFee() { + final byte[] messageBytes = new byte[100]; // 4k + Arrays.fill(messageBytes, (byte) 0b1); + return hapiTest( + cryptoCreate(PAYER).hasRetryPrecheckFrom(BUSY), + createTopic(TOPIC_NAME).submitKeyName(PAYER).hasRetryPrecheckFrom(BUSY), + submitMessageTo(TOPIC_NAME) + .blankMemo() + .payingWith(PAYER) + .message(new String(messageBytes)) + .hasRetryPrecheckFrom(BUSY) + .via("submitMessage"), + sleepFor(1000), + validateChargedUsd("submitMessage", BASE_FEE_TOPIC_SUBMIT_MESSAGE)); + } + + @HapiTest + @DisplayName("Topic get info base USD fee as expected") + final Stream tokenGetTopicInfoBaseUSDFee() { + return hapiTest( + cryptoCreate(PAYER), + createTopic(TOPIC_NAME).adminKeyName(PAYER), + getTopicInfo(TOPIC_NAME).payingWith(PAYER).via("getTopic"), + sleepFor(1000), + validateChargedUsd("getTopic", BASE_FEE_TOPIC_GET_INFO)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CryptoServiceFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CryptoServiceFeesSuite.java new file mode 100644 index 000000000000..d376cc9c748d --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/CryptoServiceFeesSuite.java @@ -0,0 +1,518 @@ +/* + * 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.fees; + +import static com.google.protobuf.ByteString.copyFromUtf8; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.assertions.AccountDetailsAsserts.accountDetailsWith; +import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountDetails; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountRecords; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoApproveAllowance; +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.cryptoDeleteAllowance; +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.mintToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCreate; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoApproveAllowance.MISSING_OWNER; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingTwo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; +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; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.TOKEN_TREASURY; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_MAX_AUTO_ASSOCIATIONS; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT; +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; +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.junit.support.TestLifecycle; +import com.hederahashgraph.api.proto.java.TokenSupplyType; +import com.hederahashgraph.api.proto.java.TokenType; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; + +@HapiTestLifecycle +public class CryptoServiceFeesSuite { + private static final double BASE_FEE_CRYPTO_GET_ACCOUNT_INFO = 0.0001; + private static final double BASE_FEE_CRYPTO_CREATE = 0.05; + private static final double BASE_FEE_CRYPTO_DELETE = 0.005; + private static final double BASE_FEE_CRYPTO_DELETE_ALLOWANCE = 0.05; + private static final double BASE_FEE_CRYPTO_UPDATE = 0.000214; + private static final double BASE_FEE_WITH_EXPIRY_CRYPTO_UPDATE = 0.00022; + private static final double BASE_FEE_HBAR_CRYPTO_TRANSFER = 0.0001; + private static final double BASE_FEE_HTS_CRYPTO_TRANSFER = 0.001; + private static final double BASE_FEE_NFT_CRYPTO_TRANSFER = 0.001; + private static final double BASE_FEE_CRYPTO_GET_ACCOUNT_RECORDS = 0.0001; + + private static final String CIVILIAN = "civilian"; + private static final String FEES_ACCOUNT = "feesAccount"; + private static final String OWNER = "owner"; + private static final String SPENDER = "spender"; + private static final String SECOND_SPENDER = "spender2"; + private static final String SENDER = "sender"; + private static final String RECEIVER = "receiver"; + + @BeforeAll + static void beforeAll(@NonNull final TestLifecycle testLifecycle) { + testLifecycle.doAdhoc( + cryptoCreate(FEES_ACCOUNT).balance(5 * ONE_HUNDRED_HBARS), + cryptoCreate(CIVILIAN).balance(5 * ONE_HUNDRED_HBARS).key(FEES_ACCOUNT)); + } + + @HapiTest + @DisplayName("CryptoCreate transaction has expected base fee") + final Stream cryptoCreateBaseUSDFee() { + final var cryptoCreate = "cryptoCreate"; + return hapiTest( + cryptoCreate(cryptoCreate) + .key(CIVILIAN) + .via(cryptoCreate) + .blankMemo() + .signedBy(CIVILIAN) + .payingWith(CIVILIAN), + validateChargedUsd(cryptoCreate, BASE_FEE_CRYPTO_CREATE)); + } + + @HapiTest + @DisplayName("CryptoDelete transaction has expected base fee") + final Stream cryptoDeleteBaseUSDFee() { + final var cryptoCreate = "cryptoCreate"; + final var cryptoDelete = "cryptoDelete"; + return hapiTest( + cryptoCreate(cryptoCreate).balance(5 * ONE_HUNDRED_HBARS).key(CIVILIAN), + cryptoDelete(cryptoCreate) + .via(cryptoDelete) + .payingWith(CIVILIAN) + .signedBy(CIVILIAN), + validateChargedUsd(cryptoDelete, BASE_FEE_CRYPTO_DELETE)); + } + + @HapiTest + @DisplayName("CryptoDeleteAllowance transaction has expected base fee") + final Stream cryptoDeleteAllowanceBaseUSDFee() { + final String token = "token"; + final String nft = "nft"; + final String supplyKey = "supplyKey"; + final String baseDeleteNft = "baseDeleteNft"; + return hapiTest( + newKeyNamed(supplyKey), + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), + cryptoCreate(SPENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY).balance(100 * ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), + tokenCreate(token) + .tokenType(TokenType.FUNGIBLE_COMMON) + .supplyType(TokenSupplyType.FINITE) + .supplyKey(supplyKey) + .initialSupply(10L) + .maxSupply(1000L) + .treasury(TOKEN_TREASURY), + tokenCreate(nft) + .tokenType(NON_FUNGIBLE_UNIQUE) + .supplyType(TokenSupplyType.FINITE) + .supplyKey(supplyKey) + .initialSupply(0) + .maxSupply(10L) + .treasury(TOKEN_TREASURY), + tokenAssociate(OWNER, token), + tokenAssociate(OWNER, nft), + mintToken( + nft, + List.of( + ByteString.copyFromUtf8("a"), + ByteString.copyFromUtf8("b"), + ByteString.copyFromUtf8("c"))) + .via("nftTokenMint"), + mintToken(token, 500L).via("tokenMint"), + cryptoTransfer(movingUnique(nft, 1L, 2L, 3L).between(TOKEN_TREASURY, OWNER)), + cryptoApproveAllowance() + .payingWith(OWNER) + .addCryptoAllowance(OWNER, SPENDER, 100L) + .addTokenAllowance(OWNER, token, SPENDER, 100L) + .addNftAllowance(OWNER, nft, SPENDER, false, List.of(1L, 2L, 3L)), + /* without specifying owner */ + cryptoDeleteAllowance() + .payingWith(OWNER) + .blankMemo() + .addNftDeleteAllowance(MISSING_OWNER, nft, List.of(1L)) + .via(baseDeleteNft), + validateChargedUsdWithin(baseDeleteNft, BASE_FEE_CRYPTO_DELETE_ALLOWANCE, 0.01), + cryptoApproveAllowance().payingWith(OWNER).addNftAllowance(OWNER, nft, SPENDER, false, List.of(1L)), + /* with specifying owner */ + cryptoDeleteAllowance() + .payingWith(OWNER) + .blankMemo() + .addNftDeleteAllowance(OWNER, nft, List.of(1L)) + .via(baseDeleteNft), + validateChargedUsdWithin(baseDeleteNft, BASE_FEE_CRYPTO_DELETE_ALLOWANCE, 0.01)); + } + + @HapiTest + @DisplayName("CryptoApproveAllowance transaction has expected base fee") + final Stream cryptoApproveAllowanceBaseUSDFee() { + final String SUPPLY_KEY = "supplyKeyApproveAllowance"; + final String APPROVE_TXN = "approveTxn"; + final String ANOTHER_SPENDER = "spender1"; + final String FUNGIBLE_TOKEN = "fungible"; + final String NON_FUNGIBLE_TOKEN = "nonFungible"; + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), + cryptoCreate(SPENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(ANOTHER_SPENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(SECOND_SPENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY).balance(100 * ONE_HUNDRED_HBARS).maxAutomaticTokenAssociations(10), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(TokenType.FUNGIBLE_COMMON) + .supplyType(TokenSupplyType.FINITE) + .supplyKey(SUPPLY_KEY) + .maxSupply(1000L) + .initialSupply(10L) + .treasury(TOKEN_TREASURY), + tokenCreate(NON_FUNGIBLE_TOKEN) + .maxSupply(10L) + .initialSupply(0) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .supplyKey(SUPPLY_KEY) + .treasury(TOKEN_TREASURY), + tokenAssociate(OWNER, FUNGIBLE_TOKEN), + tokenAssociate(OWNER, NON_FUNGIBLE_TOKEN), + mintToken( + NON_FUNGIBLE_TOKEN, + List.of( + ByteString.copyFromUtf8("a"), + ByteString.copyFromUtf8("b"), + ByteString.copyFromUtf8("c"))), + mintToken(FUNGIBLE_TOKEN, 500L), + cryptoTransfer(movingUnique(NON_FUNGIBLE_TOKEN, 1L, 2L, 3L).between(TOKEN_TREASURY, OWNER)), + cryptoApproveAllowance() + .payingWith(OWNER) + .addCryptoAllowance(OWNER, SPENDER, 100L) + .via("approve") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approve", 0.05, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SPENDER, 100L) + .via("approveTokenTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveTokenTxn", 0.05012, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SPENDER, false, List.of(1L)) + .via("approveNftTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveNftTxn", 0.050101, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, ANOTHER_SPENDER, true, List.of()) + .via("approveForAllNftTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveForAllNftTxn", 0.05, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addCryptoAllowance(OWNER, SECOND_SPENDER, 100L) + .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SECOND_SPENDER, 100L) + .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, SECOND_SPENDER, false, List.of(1L)) + .via(APPROVE_TXN) + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin(APPROVE_TXN, 0.05238, 0.01), + getAccountDetails(OWNER) + .payingWith(GENESIS) + .has(accountDetailsWith() + .cryptoAllowancesCount(2) + .nftApprovedForAllAllowancesCount(1) + .tokenAllowancesCount(2) + .cryptoAllowancesContaining(SECOND_SPENDER, 100L) + .tokenAllowancesContaining(FUNGIBLE_TOKEN, SECOND_SPENDER, 100L)), + /* edit existing allowances */ + cryptoApproveAllowance() + .payingWith(OWNER) + .addCryptoAllowance(OWNER, SECOND_SPENDER, 200L) + .via("approveModifyCryptoTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveModifyCryptoTxn", 0.049375, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addTokenAllowance(OWNER, FUNGIBLE_TOKEN, SECOND_SPENDER, 200L) + .via("approveModifyTokenTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveModifyTokenTxn", 0.04943, 0.01), + cryptoApproveAllowance() + .payingWith(OWNER) + .addNftAllowance(OWNER, NON_FUNGIBLE_TOKEN, ANOTHER_SPENDER, false, List.of()) + .via("approveModifyNftTxn") + .fee(ONE_HBAR) + .blankMemo() + .logged(), + validateChargedUsdWithin("approveModifyNftTxn", 0.049375, 0.01), + getAccountDetails(OWNER) + .payingWith(GENESIS) + .has(accountDetailsWith() + .cryptoAllowancesCount(2) + .nftApprovedForAllAllowancesCount(0) + .tokenAllowancesCount(2) + .cryptoAllowancesContaining(SECOND_SPENDER, 200L) + .tokenAllowancesContaining(FUNGIBLE_TOKEN, SECOND_SPENDER, 200L))); + } + + @LeakyHapiTest(overrides = {"entities.maxLifetime", "ledger.maxAutoAssociations"}) + @DisplayName("CryptoUpdate transaction has expected base fee") + final Stream cryptoUpdateBaseUSDFee() { + + final var baseTxn = "baseTxn"; + final var plusOneTxn = "plusOneTxn"; + final var plusTenTxn = "plusTenTxn"; + final var plusFiveKTxn = "plusFiveKTxn"; + final var plusFiveKAndOneTxn = "plusFiveKAndOneTxn"; + final var invalidNegativeTxn = "invalidNegativeTxn"; + final var validNegativeTxn = "validNegativeTxn"; + final var allowedPercentDiff = 1.5; + final var canonicalAccount = "canonicalAccount"; + final var payer = "payer"; + final var autoAssocTarget = "autoAssocTarget"; + + AtomicLong expiration = new AtomicLong(); + return hapiTest( + overridingTwo( + "ledger.maxAutoAssociations", "5000", + "entities.maxLifetime", "3153600000"), + newKeyNamed("key").shape(SIMPLE), + cryptoCreate(payer).key("key").balance(1_000 * ONE_HBAR), + cryptoCreate(canonicalAccount) + .key("key") + .balance(100 * ONE_HBAR) + .autoRenewSecs(THREE_MONTHS_IN_SECONDS) + .blankMemo() + .payingWith(payer), + cryptoCreate(autoAssocTarget) + .key("key") + .balance(100 * ONE_HBAR) + .autoRenewSecs(THREE_MONTHS_IN_SECONDS) + .blankMemo() + .payingWith(payer), + getAccountInfo(canonicalAccount).exposingExpiry(expiration::set), + sourcing(() -> cryptoUpdate(canonicalAccount) + .payingWith(canonicalAccount) + .expiring(expiration.get() + THREE_MONTHS_IN_SECONDS) + .blankMemo() + .via(baseTxn)), + getAccountInfo(canonicalAccount).hasMaxAutomaticAssociations(0).logged(), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(1) + .via(plusOneTxn), + getAccountInfo(autoAssocTarget).hasMaxAutomaticAssociations(1).logged(), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(11) + .via(plusTenTxn), + getAccountInfo(autoAssocTarget).hasMaxAutomaticAssociations(11).logged(), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(5000) + .via(plusFiveKTxn), + getAccountInfo(autoAssocTarget) + .hasMaxAutomaticAssociations(5000) + .logged(), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(-1000) + .via(invalidNegativeTxn) + .hasKnownStatus(INVALID_MAX_AUTO_ASSOCIATIONS), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(5001) + .via(plusFiveKAndOneTxn) + .hasKnownStatus(REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT), + cryptoUpdate(autoAssocTarget) + .payingWith(autoAssocTarget) + .blankMemo() + .maxAutomaticAssociations(-1) + .via(validNegativeTxn), + getAccountInfo(autoAssocTarget).hasMaxAutomaticAssociations(-1).logged(), + validateChargedUsd(baseTxn, BASE_FEE_WITH_EXPIRY_CRYPTO_UPDATE, allowedPercentDiff), + validateChargedUsd(plusOneTxn, BASE_FEE_CRYPTO_UPDATE, allowedPercentDiff), + validateChargedUsd(plusTenTxn, BASE_FEE_CRYPTO_UPDATE, allowedPercentDiff), + validateChargedUsd(plusFiveKTxn, BASE_FEE_CRYPTO_UPDATE, allowedPercentDiff), + validateChargedUsd(validNegativeTxn, BASE_FEE_CRYPTO_UPDATE, allowedPercentDiff)); + } + + @HapiTest + @DisplayName("CryptoTransfer transaction has expected base fee") + final Stream cryptoTransferBaseUSDFee() { + final String SUPPLY_KEY = "supplyKeyCryptoTransfer"; + final var expectedHtsXferWithCustomFeePriceUsd = 0.002; + final var expectedNftXferWithCustomFeePriceUsd = 0.002; + final var transferAmount = 1L; + final var customFeeCollector = "customFeeCollector"; + final var nonTreasurySender = "nonTreasurySender"; + final var hbarXferTxn = "hbarXferTxn"; + final var fungibleToken = "fungibleToken"; + final var fungibleTokenWithCustomFee = "fungibleTokenWithCustomFee"; + final var htsXferTxn = "htsXferTxn"; + final var htsXferTxnWithCustomFee = "htsXferTxnWithCustomFee"; + final var nonFungibleToken = "nonFungibleToken"; + final var nonFungibleTokenWithCustomFee = "nonFungibleTokenWithCustomFee"; + final var nftXferTxn = "nftXferTxn"; + final var nftXferTxnWithCustomFee = "nftXferTxnWithCustomFee"; + + return hapiTest( + cryptoCreate(nonTreasurySender).balance(ONE_HUNDRED_HBARS), + cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(RECEIVER), + cryptoCreate(customFeeCollector), + tokenCreate(fungibleToken) + .treasury(SENDER) + .tokenType(FUNGIBLE_COMMON) + .initialSupply(100L), + tokenCreate(fungibleTokenWithCustomFee) + .treasury(SENDER) + .tokenType(FUNGIBLE_COMMON) + .withCustom(fixedHbarFee(transferAmount, customFeeCollector)) + .initialSupply(100L), + tokenAssociate(RECEIVER, fungibleToken, fungibleTokenWithCustomFee), + newKeyNamed(SUPPLY_KEY), + tokenCreate(nonFungibleToken) + .initialSupply(0) + .supplyKey(SUPPLY_KEY) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(SENDER), + tokenCreate(nonFungibleTokenWithCustomFee) + .initialSupply(0) + .supplyKey(SUPPLY_KEY) + .tokenType(NON_FUNGIBLE_UNIQUE) + .withCustom(fixedHbarFee(transferAmount, customFeeCollector)) + .treasury(SENDER), + tokenAssociate(nonTreasurySender, List.of(fungibleTokenWithCustomFee, nonFungibleTokenWithCustomFee)), + mintToken(nonFungibleToken, List.of(copyFromUtf8("memo1"))), + mintToken(nonFungibleTokenWithCustomFee, List.of(copyFromUtf8("memo2"))), + tokenAssociate(RECEIVER, nonFungibleToken, nonFungibleTokenWithCustomFee), + cryptoTransfer(movingUnique(nonFungibleTokenWithCustomFee, 1).between(SENDER, nonTreasurySender)) + .payingWith(SENDER), + cryptoTransfer(moving(1, fungibleTokenWithCustomFee).between(SENDER, nonTreasurySender)) + .payingWith(SENDER), + cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 100L)) + .payingWith(SENDER) + .blankMemo() + .via(hbarXferTxn), + cryptoTransfer(moving(1, fungibleToken).between(SENDER, RECEIVER)) + .blankMemo() + .payingWith(SENDER) + .via(htsXferTxn), + cryptoTransfer(movingUnique(nonFungibleToken, 1).between(SENDER, RECEIVER)) + .blankMemo() + .payingWith(SENDER) + .via(nftXferTxn), + cryptoTransfer(moving(1, fungibleTokenWithCustomFee).between(nonTreasurySender, RECEIVER)) + .blankMemo() + .fee(ONE_HBAR) + .payingWith(nonTreasurySender) + .via(htsXferTxnWithCustomFee), + cryptoTransfer(movingUnique(nonFungibleTokenWithCustomFee, 1).between(nonTreasurySender, RECEIVER)) + .blankMemo() + .fee(ONE_HBAR) + .payingWith(nonTreasurySender) + .via(nftXferTxnWithCustomFee), + validateChargedUsdWithin(hbarXferTxn, BASE_FEE_HBAR_CRYPTO_TRANSFER, 0.01), + validateChargedUsdWithin(htsXferTxn, BASE_FEE_HTS_CRYPTO_TRANSFER, 0.01), + validateChargedUsdWithin(nftXferTxn, BASE_FEE_NFT_CRYPTO_TRANSFER, 0.01), + validateChargedUsdWithin(htsXferTxnWithCustomFee, expectedHtsXferWithCustomFeePriceUsd, 0.1), + validateChargedUsdWithin(nftXferTxnWithCustomFee, expectedNftXferWithCustomFeePriceUsd, 0.3)); + } + + @HapiTest + @DisplayName("CryptoGetAccountRecords query has expected base fee") + final Stream cryptoCryptoGetAccountRecordsBaseUSDFee() { + final var nonTreasurySender = "nonTreasurySender"; + + return hapiTest( + cryptoCreate(nonTreasurySender).balance(ONE_HUNDRED_HBARS), + cryptoCreate("GetAccountRecordsTest").key(nonTreasurySender).payingWith(nonTreasurySender), + getAccountRecords("GetAccountRecordsTest") + .payingWith(nonTreasurySender) + .via("baseGetAccountRecord"), + sleepFor(2000), + validateChargedUsd("baseGetAccountRecord", BASE_FEE_CRYPTO_GET_ACCOUNT_RECORDS)); + } + + @HapiTest + @DisplayName("CryptoGetAccountBalance query has expected base fee of zero") + final Stream cryptoGetAccountBalanceBaseUSDFee() { + final String getAccountBalanceTestFeesAccount = "GetAccountBalanceTestFeesAccount"; + return hapiTest( + cryptoCreate(getAccountBalanceTestFeesAccount).balance(ONE_HBAR), + getAccountBalance(getAccountBalanceTestFeesAccount) + .hasTinyBars(ONE_HBAR) + .signedBy(getAccountBalanceTestFeesAccount) + .payingWith(getAccountBalanceTestFeesAccount), + getAccountBalance(getAccountBalanceTestFeesAccount).hasTinyBars(ONE_HBAR)); + } + + @HapiTest + @DisplayName("CryptoGetAccountInfo query has expected base fee") + final Stream cryptoGetAccountInfoBaseUSDFee() { + return hapiTest( + getAccountInfo(CIVILIAN).via("basicGetInfo").payingWith(FEES_ACCOUNT), + sleepFor(1000), + validateChargedUsd("basicGetInfo", BASE_FEE_CRYPTO_GET_ACCOUNT_INFO)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FileServiceFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FileServiceFeesSuite.java new file mode 100644 index 000000000000..997679f5a7ef --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/FileServiceFeesSuite.java @@ -0,0 +1,147 @@ +/* + * 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.fees; + +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.*; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.*; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; + +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.spec.keys.KeyShape; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; + +public class FileServiceFeesSuite { + private static final String MEMO = "Really quite something!"; + private static final String CIVILIAN = "civilian"; + private static final String KEY = "key"; + private static final double BASE_FEE_FILE_CREATE = 0.05; + private static final double BASE_FEE_FILE_UPDATE = 0.05; + private static final double BASE_FEE_FILE_DELETE = 0.007; + private static final double BASE_FEE_FILE_APPEND = 0.05; + private static final double BASE_FEE_FILE_GET_CONTENT = 0.0001; + private static final double BASE_FEE_FILE_GET_FILE = 0.0001; + + @HapiTest + @DisplayName("USD base fee as expected for file create transaction") + final Stream fileCreateBaseUSDFee() { + // 90 days considered for base fee + var contents = "0".repeat(1000).getBytes(); + + return hapiTest( + newKeyNamed(KEY).shape(KeyShape.SIMPLE), + cryptoCreate(CIVILIAN).key(KEY).balance(ONE_HUNDRED_HBARS), + newKeyListNamed("WACL", List.of(CIVILIAN)), + fileCreate("test") + .memo(MEMO) + .key("WACL") + .contents(contents) + .payingWith(CIVILIAN) + .via("fileCreateBasic"), + validateChargedUsd("fileCreateBasic", BASE_FEE_FILE_CREATE)); + } + + @HapiTest + @DisplayName("USD base fee as expected for file update transaction") + final Stream fileUpdateBaseUSDFee() { + var contents = "0".repeat(1000).getBytes(); + + return hapiTest( + newKeyNamed("key").shape(KeyShape.SIMPLE), + cryptoCreate(CIVILIAN).key("key").balance(ONE_HUNDRED_HBARS), + newKeyListNamed("key", List.of(CIVILIAN)), + fileCreate("test").key("key").contents("ABC"), + fileUpdate("test") + .contents(contents) + .memo(MEMO) + .payingWith(CIVILIAN) + .via("fileUpdateBasic"), + validateChargedUsd("fileUpdateBasic", BASE_FEE_FILE_UPDATE)); + } + + @HapiTest + @DisplayName("USD base fee as expected for file delete transaction") + final Stream fileDeleteBaseUSDFee() { + String memo = "Really quite something!"; + return hapiTest( + newKeyNamed("key").shape(KeyShape.SIMPLE), + cryptoCreate(CIVILIAN).key("key").balance(ONE_HUNDRED_HBARS), + newKeyListNamed("WACL", List.of(CIVILIAN)), + fileCreate("test").memo(MEMO).key("WACL").contents("ABC"), + fileDelete("test").blankMemo().payingWith(CIVILIAN).via("fileDeleteBasic"), + validateChargedUsd("fileDeleteBasic", BASE_FEE_FILE_DELETE)); + } + + @HapiTest + @DisplayName("USD base fee as expected for file append transaction") + final Stream fileAppendBaseUSDFee() { + final var civilian = "NonExemptPayer"; + + final var baseAppend = "baseAppend"; + final var targetFile = "targetFile"; + final var contentBuilder = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + contentBuilder.append("A"); + } + final var magicKey = "magicKey"; + final var magicWacl = "magicWacl"; + + return hapiTest( + newKeyNamed(magicKey), + newKeyListNamed(magicWacl, List.of(magicKey)), + cryptoCreate(civilian).balance(ONE_HUNDRED_HBARS).key(magicKey), + fileCreate(targetFile) + .key(magicWacl) + .lifetime(THREE_MONTHS_IN_SECONDS) + .contents("Nothing much!"), + fileAppend(targetFile) + .signedBy(magicKey) + .blankMemo() + .content(contentBuilder.toString()) + .payingWith(civilian) + .via(baseAppend), + validateChargedUsd(baseAppend, BASE_FEE_FILE_APPEND)); + } + + @HapiTest + @DisplayName("USD base fee as expected for file get content transaction") + final Stream fileGetContentBaseUSDFee() { + return hapiTest( + cryptoCreate(CIVILIAN).balance(5 * ONE_HUNDRED_HBARS), + fileCreate("ntb").key(CIVILIAN).contents("Nothing much!").memo(MEMO), + getFileContents("ntb").payingWith(CIVILIAN).signedBy(CIVILIAN).via("getFileContentsBasic"), + sleepFor(1000), + validateChargedUsd("getFileContentsBasic", BASE_FEE_FILE_GET_CONTENT)); + } + + @HapiTest + @DisplayName("USD base fee as expected for file get info transaction") + final Stream fileGetInfoBaseUSDFee() { + return hapiTest( + cryptoCreate(CIVILIAN).balance(5 * ONE_HUNDRED_HBARS), + fileCreate("ntb").key(CIVILIAN).contents("Nothing much!").memo(MEMO), + getFileInfo("ntb").payingWith(CIVILIAN).signedBy(CIVILIAN).via("getFileInfoBasic"), + sleepFor(1000), + validateChargedUsd("getFileInfoBasic", BASE_FEE_FILE_GET_FILE)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/MiscellaneousFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/MiscellaneousFeesSuite.java new file mode 100644 index 000000000000..bae1c5d0c229 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/MiscellaneousFeesSuite.java @@ -0,0 +1,105 @@ +/* + * 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.fees; + +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getReceipt; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getVersionInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.hapiPrng; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_BILLION_HBARS; +import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; + +import com.hedera.services.bdd.junit.HapiTest; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; + +public class MiscellaneousFeesSuite { + private static final String PRNG_IS_ENABLED = "utilPrng.isEnabled"; + private static final String BOB = "bob"; + private static final String ALICE = "alice"; + private static final double BASE_FEE_MISC_GET_VERSION = 0.0001; + private static final double BASE_FEE_MISC_PRNG_TRX = 0.001; + public static final double BASE_FEE_MISC_GET_TRX_RECORD = 0.0001; + private static final double EXPECTED_FEE_PRNG_RANGE_TRX = 0.0010010316; + + @HapiTest + @DisplayName("USD base fee as expected for Prng transaction") + final Stream miscPrngTrxBaseUSDFee() { + final var baseTxn = "prng"; + final var plusRangeTxn = "prngWithRange"; + + return hapiTest( + overridingAllOf(Map.of(PRNG_IS_ENABLED, "true")), + cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), + hapiPrng().payingWith(BOB).via(baseTxn).blankMemo().logged(), + getTxnRecord(baseTxn).hasOnlyPseudoRandomBytes().logged(), + validateChargedUsd(baseTxn, BASE_FEE_MISC_PRNG_TRX), + hapiPrng(10).payingWith(BOB).via(plusRangeTxn).blankMemo().logged(), + getTxnRecord(plusRangeTxn).hasOnlyPseudoRandomNumberInRange(10).logged(), + validateChargedUsd(plusRangeTxn, EXPECTED_FEE_PRNG_RANGE_TRX, 0.5)); + } + + @HapiTest + @DisplayName("USD base fee as expected for get version info") + final Stream miscGetInfoBaseUSDFee() { + return hapiTest( + cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), + getVersionInfo() + .signedBy(BOB) + .payingWith(BOB) + .via("versionInfo") + .logged(), + sleepFor(1000), + validateChargedUsd("versionInfo", BASE_FEE_MISC_GET_VERSION)); + } + + @HapiTest + @DisplayName("USD base fee as expected for get account balance") + final Stream miscGetAccountBalanceBaseUSDFee() { + return hapiTest( + cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS).via("createTxn").logged(), + getReceipt("createTxn").signedBy(BOB).payingWith(BOB), + // free transaction - verifying that the paying account has the same balance as it was at the beginning + getAccountBalance(BOB).hasTinyBars(ONE_HUNDRED_HBARS)); + } + + @HapiTest + @DisplayName("USD base fee as expected for get transaction record") + final Stream miscGetTransactionRecordBaseUSDFee() { + String baseTransactionGetRecord = "baseTransactionGetRecord"; + String createTxn = "createTxn"; + return hapiTest( + cryptoCreate(ALICE).balance(ONE_BILLION_HBARS), + cryptoCreate(BOB) + .balance(ONE_HUNDRED_HBARS) + .signedBy(ALICE) + .payingWith(ALICE) + .via(createTxn) + .logged(), + getTxnRecord(createTxn).signedBy(BOB).payingWith(BOB).via(baseTransactionGetRecord), + sleepFor(1000), + validateChargedUsd(baseTransactionGetRecord, BASE_FEE_MISC_GET_TRX_RECORD)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ScheduleServiceFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ScheduleServiceFeesSuite.java new file mode 100644 index 000000000000..a5df3039fbea --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/ScheduleServiceFeesSuite.java @@ -0,0 +1,119 @@ +/* + * 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.fees; + +import static com.hedera.services.bdd.junit.ContextRequirement.FEE_SCHEDULE_OVERRIDES; +import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleDelete; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; +import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadScheduledContractPrices; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; +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.schedule.ScheduleUtils.OTHER_PAYER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_SENDER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; +import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIMPLE_UPDATE; + +import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.LeakyHapiTest; +import com.hedera.services.bdd.junit.support.TestLifecycle; +import edu.umd.cs.findbugs.annotations.NonNull; +import java.math.BigInteger; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; + +@HapiTestLifecycle +public class ScheduleServiceFeesSuite { + private static final double BASE_FEE_SCHEDULE_CREATE = 0.01; + private static final double BASE_FEE_SCHEDULE_SIGN = 0.001; + private static final double BASE_FEE_SCHEDULE_DELETE = 0.001; + private static final double BASE_FEE_SCHEDULE_INFO = 0.0001; + private static final double BASE_FEE_CONTRACT_CALL = 0.1; + + @BeforeAll + static void beforeAll(@NonNull final TestLifecycle testLifecycle) { + testLifecycle.overrideInClass(Map.of( + "scheduling.whitelist", "ContractCall,CryptoCreate,CryptoTransfer,FileDelete,FileUpdate,SystemDelete")); + } + + @LeakyHapiTest(requirement = FEE_SCHEDULE_OVERRIDES) + @DisplayName("Schedule ops have expected USD fees") + final Stream scheduleOpsBaseUSDFees() { + final String SCHEDULE_NAME = "canonical"; + return hapiTest( + uploadScheduledContractPrices(GENESIS), + uploadInitCode(SIMPLE_UPDATE), + cryptoCreate(OTHER_PAYER), + cryptoCreate(PAYING_SENDER), + cryptoCreate(RECEIVER).receiverSigRequired(true), + contractCreate(SIMPLE_UPDATE).gas(300_000L), + scheduleCreate( + SCHEDULE_NAME, + cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) + .memo("") + .fee(ONE_HBAR)) + .payingWith(OTHER_PAYER) + .via("canonicalCreation") + .alsoSigningWith(PAYING_SENDER) + .adminKey(OTHER_PAYER), + scheduleSign(SCHEDULE_NAME) + .via("canonicalSigning") + .payingWith(PAYING_SENDER) + .alsoSigningWith(RECEIVER), + scheduleCreate( + "tbd", + cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) + .memo("") + .fee(ONE_HBAR)) + .payingWith(PAYING_SENDER) + .adminKey(PAYING_SENDER), + scheduleDelete("tbd").via("canonicalDeletion").payingWith(PAYING_SENDER), + scheduleCreate( + "contractCall", + contractCall(SIMPLE_UPDATE, "set", BigInteger.valueOf(5), BigInteger.valueOf(42)) + .gas(24_000) + .memo("") + .fee(ONE_HBAR)) + .payingWith(OTHER_PAYER) + .via("canonicalContractCall") + .adminKey(OTHER_PAYER), + getScheduleInfo(SCHEDULE_NAME) + .payingWith(OTHER_PAYER) + .signedBy(OTHER_PAYER) + .via("getScheduleInfoBasic"), + sleepFor(1000), + validateChargedUsdWithin("canonicalCreation", BASE_FEE_SCHEDULE_CREATE, 3.0), + validateChargedUsdWithin("canonicalSigning", BASE_FEE_SCHEDULE_SIGN, 3.0), + validateChargedUsdWithin("canonicalDeletion", BASE_FEE_SCHEDULE_DELETE, 3.0), + validateChargedUsdWithin("canonicalContractCall", BASE_FEE_CONTRACT_CALL, 3.0), + validateChargedUsd("getScheduleInfoBasic", BASE_FEE_SCHEDULE_INFO)); + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/TokenServiceFeesSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/TokenServiceFeesSuite.java new file mode 100644 index 000000000000..2720ab302451 --- /dev/null +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/fees/TokenServiceFeesSuite.java @@ -0,0 +1,911 @@ +/* + * 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.fees; + +import static com.google.protobuf.ByteString.copyFromUtf8; +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.TransactionRecordAsserts.includingFungibleMovement; +import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingFungiblePendingAirdrop; +import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingNftPendingAirdrop; +import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingNonfungibleMovement; +import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenNftInfo; +import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; +import static com.hedera.services.bdd.spec.queries.crypto.ExpectedTokenRel.relationshipWith; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.grantTokenKyc; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.mintToken; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.revokeTokenKyc; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAirdrop; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenAssociate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenCancelAirdrop; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenClaimAirdrop; +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.tokenFeeScheduleUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenFreeze; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenPause; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenReject; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnfreeze; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUnpause; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdate; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdateNfts; +import static com.hedera.services.bdd.spec.transactions.TxnVerbs.wipeTokenAccount; +import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHbarFee; +import static com.hedera.services.bdd.spec.transactions.token.CustomFeeSpecs.fixedHtsFee; +import static com.hedera.services.bdd.spec.transactions.token.HapiTokenCancelAirdrop.pendingAirdrop; +import static com.hedera.services.bdd.spec.transactions.token.HapiTokenCancelAirdrop.pendingNFTAirdrop; +import static com.hedera.services.bdd.spec.transactions.token.HapiTokenReject.rejectingNFT; +import static com.hedera.services.bdd.spec.transactions.token.HapiTokenReject.rejectingToken; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; +import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; +import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; +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.THREE_MONTHS_IN_SECONDS; +import static com.hedera.services.bdd.suites.HapiSuite.flattened; +import static com.hedera.services.bdd.suites.hip904.TokenAirdropBase.setUpTokensAndAllReceivers; +import static com.hedera.services.bdd.suites.leaky.LeakyContractTestsSuite.RECEIVER; +import static com.hedera.services.bdd.suites.utils.MiscEETUtils.metadata; +import static com.hederahashgraph.api.proto.java.TokenPauseStatus.Paused; +import static com.hederahashgraph.api.proto.java.TokenPauseStatus.Unpaused; +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; +import com.hedera.services.bdd.junit.HapiTest; +import com.hedera.services.bdd.spec.transactions.token.HapiTokenClaimAirdrop; +import com.hederahashgraph.api.proto.java.TokenSupplyType; +import com.hederahashgraph.api.proto.java.TokenType; +import java.time.Instant; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.DynamicTest; +import org.junit.jupiter.api.Tag; + +@Tag(TOKEN) +public class TokenServiceFeesSuite { + private static final double ALLOWED_DIFFERENCE_PERCENTAGE = 0.01; + private static final double ALLOWED_DIFFERENCE = 1; + private static String TOKEN_TREASURY = "treasury"; + private static final String NON_FUNGIBLE_TOKEN = "nonFungible"; + private static final String SUPPLY_KEY = "supplyKey"; + private static final String METADATA_KEY = "metadataKey"; + private static final String ADMIN_KEY = "adminKey"; + private static final String PAUSE_KEY = "pauseKey"; + private static final String TREASURE_KEY = "treasureKey"; + private static final String FREEZE_KEY = "freezeKey"; + private static final String FUNGIBLE_FREEZE_KEY = "fungibleTokenFreeze"; + private static final String KYC_KEY = "kycKey"; + private static final String MULTI_KEY = "multiKey"; + private static final String NAME = "012345678912"; + private static final String ALICE = "alice"; + private static final String AUTO_RENEW_ACCOUNT = "autoRenewAccount"; + + private static final String UNFREEZE = "unfreeze"; + + private static final String CIVILIAN_ACCT = "civilian"; + private static final String UNIQUE_TOKEN = "nftType"; + private static final String BASE_TXN = "baseTxn"; + + private static final String WIPE_KEY = "wipeKey"; + private static final String NFT_TEST_METADATA = " test metadata"; + private static final String FUNGIBLE_COMMON_TOKEN = "f"; + private static final String FUNGIBLE_TOKEN = "fungibleToken"; + private static final String RECEIVER_WITH_0_AUTO_ASSOCIATIONS = "receiverWith0AutoAssociations"; + + private static final double EXPECTED_NFT_WIPE_PRICE_USD = 0.001; + private static final double EXPECTED_FREEZE_PRICE_USD = 0.001; + private static final double EXPECTED_UNFREEZE_PRICE_USD = 0.001; + private static final double EXPECTED_NFT_BURN_PRICE_USD = 0.001; + private static final double EXPECTED_GRANTKYC_PRICE_USD = 0.001; + private static final double EXPECTED_REVOKEKYC_PRICE_USD = 0.001; + private static final double EXPECTED_NFT_MINT_PRICE_USD = 0.02; + private static final double EXPECTED_FUNGIBLE_MINT_PRICE_USD = 0.001; + private static final double EXPECTED_FUNGIBLE_REJECT_PRICE_USD = 0.001; + private static final double EXPECTED_NFT_REJECT_PRICE_USD = 0.001; + private static final String OWNER = "owner"; + + @HapiTest + @DisplayName("charge association fee for FT correctly") + final Stream chargeAirdropAssociationFeeForFT() { + var receiver = "receiver"; + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + newKeyNamed(FUNGIBLE_FREEZE_KEY), + tokenCreate(FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(FUNGIBLE_COMMON) + .freezeKey(FUNGIBLE_FREEZE_KEY) + .initialSupply(1000L), + cryptoCreate(receiver).maxAutomaticTokenAssociations(0), + tokenAirdrop(moving(1, FUNGIBLE_TOKEN).between(OWNER, receiver)) + .payingWith(OWNER) + .via("airdrop"), + tokenAirdrop(moving(1, FUNGIBLE_TOKEN).between(OWNER, receiver)) + .payingWith(OWNER) + .via("second airdrop"), + validateChargedUsd("airdrop", 0.1, 1), + validateChargedUsd("second airdrop", 0.05, 1)); + } + + @HapiTest + @DisplayName("charge association fee for NFT correctly") + final Stream chargeAirdropAssociationFeeForNFT() { + var receiver = "receiver"; + var nftSupplyKey = "nftSupplyKey"; + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + newKeyNamed(nftSupplyKey), + tokenCreate(NON_FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(NON_FUNGIBLE_UNIQUE) + .initialSupply(0L) + .name(NON_FUNGIBLE_TOKEN) + .supplyKey(nftSupplyKey), + mintToken( + NON_FUNGIBLE_TOKEN, + IntStream.range(0, 10) + .mapToObj(a -> ByteString.copyFromUtf8(String.valueOf(a))) + .toList()), + cryptoCreate(receiver).maxAutomaticTokenAssociations(0), + tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1).between(OWNER, receiver)) + .payingWith(OWNER) + .via("airdrop"), + tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 2).between(OWNER, receiver)) + .payingWith(OWNER) + .via("second airdrop"), + validateChargedUsd("airdrop", 0.1, 1), + validateChargedUsd("second airdrop", 0.1, 1)); + } + + @HapiTest + final Stream claimFungibleTokenAirdropBaseFee() { + var nftSupplyKey = "nftSupplyKey"; + return hapiTest(flattened( + setUpTokensAndAllReceivers(), + cryptoCreate(RECEIVER).balance(ONE_HUNDRED_HBARS), + // do pending airdrop + newKeyNamed(nftSupplyKey), + tokenCreate(NON_FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(NON_FUNGIBLE_UNIQUE) + .initialSupply(0L) + .name(NON_FUNGIBLE_TOKEN) + .supplyKey(nftSupplyKey), + mintToken( + NON_FUNGIBLE_TOKEN, + IntStream.range(0, 10) + .mapToObj(a -> ByteString.copyFromUtf8(String.valueOf(a))) + .toList()), + tokenAirdrop(moving(10, FUNGIBLE_TOKEN).between(OWNER, RECEIVER)) + .payingWith(OWNER), + tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1).between(OWNER, RECEIVER)) + .payingWith(OWNER), + + // do claim + tokenClaimAirdrop( + HapiTokenClaimAirdrop.pendingAirdrop(OWNER, RECEIVER, FUNGIBLE_TOKEN), + HapiTokenClaimAirdrop.pendingNFTAirdrop(OWNER, RECEIVER, NON_FUNGIBLE_TOKEN, 1)) + .payingWith(RECEIVER) + .via("claimTxn"), // assert txn record + getTxnRecord("claimTxn") + .hasPriority(recordWith() + .tokenTransfers(includingFungibleMovement( + moving(10, FUNGIBLE_TOKEN).between(OWNER, RECEIVER))) + .tokenTransfers(includingNonfungibleMovement( + movingUnique(NON_FUNGIBLE_TOKEN, 1).between(OWNER, RECEIVER)))), + validateChargedUsd("claimTxn", 0.001, 1), + // assert balance fungible tokens + getAccountBalance(RECEIVER).hasTokenBalance(FUNGIBLE_TOKEN, 10), + // assert balances NFT + getAccountBalance(RECEIVER).hasTokenBalance(NON_FUNGIBLE_TOKEN, 1), + // assert token associations + getAccountInfo(RECEIVER).hasToken(relationshipWith(FUNGIBLE_TOKEN)), + getAccountInfo(RECEIVER).hasToken(relationshipWith(NON_FUNGIBLE_TOKEN)))); + } + + @HapiTest + @DisplayName("cancel airdrop FT happy path") + final Stream cancelAirdropFungibleTokenHappyPath() { + final var account = "account"; + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(account), + newKeyNamed(FUNGIBLE_FREEZE_KEY), + tokenCreate(FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(FUNGIBLE_COMMON) + .freezeKey(FUNGIBLE_FREEZE_KEY) + .initialSupply(1000L), + tokenAssociate(account, FUNGIBLE_TOKEN), + cryptoTransfer(moving(10, FUNGIBLE_TOKEN).between(OWNER, account)), + cryptoCreate(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).maxAutomaticTokenAssociations(0), + // Create an airdrop in pending state + tokenAirdrop(moving(10, FUNGIBLE_TOKEN).between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)) + .payingWith(account) + .via("airdrop"), + + // Verify that a pending state is created and the correct usd is charged + getTxnRecord("airdrop") + .hasPriority(recordWith() + .pendingAirdrops(includingFungiblePendingAirdrop(moving(10, FUNGIBLE_TOKEN) + .between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)))), + getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(FUNGIBLE_TOKEN, 0), + validateChargedUsd("airdrop", 0.1, 1), + + // Cancel the airdrop + tokenCancelAirdrop(pendingAirdrop(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, FUNGIBLE_TOKEN)) + .payingWith(account) + .via("cancelAirdrop"), + + // Verify that the receiver doesn't have the token + getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(FUNGIBLE_TOKEN, 0), + validateChargedUsd("cancelAirdrop", 0.001, 1)); + } + + @HapiTest + @DisplayName("cancel airdrop NFT happy path") + final Stream cancelAirdropNftHappyPath() { + var nftSupplyKey = "nftSupplyKey"; + final var account = "account"; + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(account), + newKeyNamed(nftSupplyKey), + tokenCreate(NON_FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(NON_FUNGIBLE_UNIQUE) + .initialSupply(0L) + .name(NON_FUNGIBLE_TOKEN) + .supplyKey(nftSupplyKey), + mintToken( + NON_FUNGIBLE_TOKEN, + IntStream.range(0, 10) + .mapToObj(a -> ByteString.copyFromUtf8(String.valueOf(a))) + .toList()), + tokenAssociate(account, NON_FUNGIBLE_TOKEN), + cryptoTransfer(movingUnique(NON_FUNGIBLE_TOKEN, 1L).between(OWNER, account)), + cryptoCreate(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).maxAutomaticTokenAssociations(0), + // Create an airdrop in pending state + tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1L).between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)) + .payingWith(account) + .via("airdrop"), + + // Verify that a pending state is created and the correct usd is charged + getTxnRecord("airdrop") + .hasPriority(recordWith() + .pendingAirdrops(includingNftPendingAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1L) + .between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)))), + getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(NON_FUNGIBLE_TOKEN, 0), + validateChargedUsd("airdrop", 0.1, 1), + + // Cancel the airdrop + tokenCancelAirdrop( + pendingNFTAirdrop(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, NON_FUNGIBLE_TOKEN, 1L)) + .payingWith(account) + .via("cancelAirdrop"), + + // Verify that the receiver doesn't have the token + getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(NON_FUNGIBLE_TOKEN, 0), + validateChargedUsd("cancelAirdrop", 0.001, 1)); + } + + @HapiTest + final Stream baseCommonTokenRejectChargedAsExpected() { + return hapiTest( + newKeyNamed(MULTI_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + cryptoCreate(ALICE).balance(ONE_HUNDRED_HBARS), + tokenCreate(FUNGIBLE_COMMON_TOKEN) + .initialSupply(1000L) + .adminKey(MULTI_KEY) + .supplyKey(MULTI_KEY) + .treasury(TOKEN_TREASURY), + tokenCreate(UNIQUE_TOKEN) + .initialSupply(0) + .adminKey(MULTI_KEY) + .supplyKey(MULTI_KEY) + .treasury(TOKEN_TREASURY) + .tokenType(TokenType.NON_FUNGIBLE_UNIQUE), + mintToken( + UNIQUE_TOKEN, + List.of(metadata("nemo the fish"), metadata("garfield the cat"), metadata("snoopy the dog"))), + tokenAssociate(ALICE, FUNGIBLE_COMMON_TOKEN, UNIQUE_TOKEN), + cryptoTransfer(movingUnique(UNIQUE_TOKEN, 1L).between(TOKEN_TREASURY, ALICE)) + .payingWith(TOKEN_TREASURY) + .via("nftTransfer"), + cryptoTransfer(moving(100, FUNGIBLE_COMMON_TOKEN).between(TOKEN_TREASURY, ALICE)) + .payingWith(TOKEN_TREASURY) + .via("fungibleTransfer"), + tokenReject(rejectingToken(FUNGIBLE_COMMON_TOKEN)) + .payingWith(ALICE) + .via("rejectFungible"), + tokenReject(rejectingNFT(UNIQUE_TOKEN, 1)).payingWith(ALICE).via("rejectNft"), + validateChargedUsdWithin("fungibleTransfer", EXPECTED_FUNGIBLE_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), + validateChargedUsdWithin("nftTransfer", EXPECTED_NFT_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), + validateChargedUsdWithin("rejectFungible", EXPECTED_FUNGIBLE_REJECT_PRICE_USD, ALLOWED_DIFFERENCE), + validateChargedUsdWithin("rejectNft", EXPECTED_NFT_REJECT_PRICE_USD, ALLOWED_DIFFERENCE)); + } + + @HapiTest + final Stream baseCreationsHaveExpectedPrices() { + final var civilian = "NonExemptPayer"; + + final var expectedCommonNoCustomFeesPriceUsd = 1.00; + final var expectedUniqueNoCustomFeesPriceUsd = 1.00; + final var expectedCommonWithCustomFeesPriceUsd = 2.00; + final var expectedUniqueWithCustomFeesPriceUsd = 2.00; + + final var commonNoFees = "commonNoFees"; + final var commonWithFees = "commonWithFees"; + final var uniqueNoFees = "uniqueNoFees"; + final var uniqueWithFees = "uniqueWithFees"; + + final var customFeeKey = "customFeeKey"; + + return hapiTest( + cryptoCreate(civilian).balance(ONE_HUNDRED_HBARS), + cryptoCreate(TOKEN_TREASURY).balance(0L), + cryptoCreate(AUTO_RENEW_ACCOUNT).balance(0L), + newKeyNamed(ADMIN_KEY), + newKeyNamed(SUPPLY_KEY), + newKeyNamed(customFeeKey), + tokenCreate(commonNoFees) + .blankMemo() + .entityMemo("") + .name(NAME) + .symbol("ABCD") + .payingWith(civilian) + .treasury(TOKEN_TREASURY) + .autoRenewAccount(AUTO_RENEW_ACCOUNT) + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) + .adminKey(ADMIN_KEY) + .via(txnFor(commonNoFees)), + tokenCreate(commonWithFees) + .blankMemo() + .entityMemo("") + .name(NAME) + .symbol("ABCD") + .payingWith(civilian) + .treasury(TOKEN_TREASURY) + .autoRenewAccount(AUTO_RENEW_ACCOUNT) + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) + .adminKey(ADMIN_KEY) + .withCustom(fixedHbarFee(ONE_HBAR, TOKEN_TREASURY)) + .feeScheduleKey(customFeeKey) + .via(txnFor(commonWithFees)), + tokenCreate(uniqueNoFees) + .payingWith(civilian) + .blankMemo() + .entityMemo("") + .name(NAME) + .symbol("ABCD") + .initialSupply(0L) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .autoRenewAccount(AUTO_RENEW_ACCOUNT) + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) + .adminKey(ADMIN_KEY) + .supplyKey(SUPPLY_KEY) + .via(txnFor(uniqueNoFees)), + tokenCreate(uniqueWithFees) + .payingWith(civilian) + .blankMemo() + .entityMemo("") + .name(NAME) + .symbol("ABCD") + .initialSupply(0L) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .autoRenewAccount(AUTO_RENEW_ACCOUNT) + .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) + .adminKey(ADMIN_KEY) + .withCustom(fixedHbarFee(ONE_HBAR, TOKEN_TREASURY)) + .supplyKey(SUPPLY_KEY) + .feeScheduleKey(customFeeKey) + .via(txnFor(uniqueWithFees)), + validateChargedUsdWithin(txnFor(commonNoFees), expectedCommonNoCustomFeesPriceUsd, 0.01), + validateChargedUsdWithin(txnFor(commonWithFees), expectedCommonWithCustomFeesPriceUsd, 0.01), + validateChargedUsdWithin(txnFor(uniqueNoFees), expectedUniqueNoCustomFeesPriceUsd, 0.01), + validateChargedUsdWithin(txnFor(uniqueWithFees), expectedUniqueWithCustomFeesPriceUsd, 0.01)); + } + + @HapiTest + final Stream baseTokenOperationIsChargedExpectedFee() { + final var htsAmount = 2_345L; + final var targetToken = "immutableToken"; + final var feeDenom = "denom"; + final var htsCollector = "denomFee"; + final var feeScheduleKey = "feeSchedule"; + final var expectedBasePriceUsd = 0.001; + + return hapiTest( + newKeyNamed(feeScheduleKey), + cryptoCreate("civilian").key(feeScheduleKey), + cryptoCreate(htsCollector), + tokenCreate(feeDenom).treasury(htsCollector), + tokenCreate(targetToken) + .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) + .feeScheduleKey(feeScheduleKey), + tokenFeeScheduleUpdate(targetToken) + .signedBy(feeScheduleKey) + .payingWith("civilian") + .blankMemo() + .withCustom(fixedHtsFee(htsAmount, feeDenom, htsCollector)) + .via("baseFeeSchUpd"), + validateChargedUsdWithin("baseFeeSchUpd", expectedBasePriceUsd, 1.0)); + } + + @HapiTest + final Stream baseFungibleMintOperationIsChargedExpectedFee() { + return hapiTest( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(CIVILIAN_ACCT).balance(ONE_MILLION_HBARS).key(SUPPLY_KEY), + tokenCreate(FUNGIBLE_TOKEN) + .initialSupply(0L) + .supplyKey(SUPPLY_KEY) + .tokenType(FUNGIBLE_COMMON), + mintToken(FUNGIBLE_TOKEN, 10) + .payingWith(CIVILIAN_ACCT) + .signedBy(SUPPLY_KEY) + .blankMemo() + .via("fungibleMint"), + validateChargedUsd("fungibleMint", EXPECTED_FUNGIBLE_MINT_PRICE_USD)); + } + + @HapiTest + final Stream baseNftMintOperationIsChargedExpectedFee() { + final var standard100ByteMetadata = ByteString.copyFromUtf8( + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(CIVILIAN_ACCT).balance(ONE_MILLION_HBARS).key(SUPPLY_KEY), + tokenCreate(UNIQUE_TOKEN) + .initialSupply(0L) + .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) + .supplyKey(SUPPLY_KEY) + .tokenType(NON_FUNGIBLE_UNIQUE), + mintToken(UNIQUE_TOKEN, List.of(standard100ByteMetadata)) + .payingWith(CIVILIAN_ACCT) + .signedBy(SUPPLY_KEY) + .blankMemo() + .fee(ONE_HUNDRED_HBARS) + .via(BASE_TXN), + validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_MINT_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); + } + + @HapiTest + final Stream nftMintsScaleLinearlyBasedOnNumberOfSerialNumbers() { + final var expectedFee = 10 * EXPECTED_NFT_MINT_PRICE_USD; + final var standard100ByteMetadata = ByteString.copyFromUtf8( + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"); + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(CIVILIAN_ACCT).balance(ONE_MILLION_HBARS).key(SUPPLY_KEY), + tokenCreate(UNIQUE_TOKEN) + .initialSupply(0L) + .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) + .supplyKey(SUPPLY_KEY) + .tokenType(NON_FUNGIBLE_UNIQUE), + mintToken( + UNIQUE_TOKEN, + List.of( + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata, + standard100ByteMetadata)) + .payingWith(CIVILIAN_ACCT) + .signedBy(SUPPLY_KEY) + .blankMemo() + .fee(ONE_HUNDRED_HBARS) + .via(BASE_TXN), + validateChargedUsdWithin(BASE_TXN, expectedFee, ALLOWED_DIFFERENCE_PERCENTAGE)); + } + + @HapiTest + final Stream baseNftBurnOperationIsChargedExpectedFee() { + return hapiTest( + newKeyNamed(SUPPLY_KEY), + cryptoCreate(CIVILIAN_ACCT).key(SUPPLY_KEY), + cryptoCreate(TOKEN_TREASURY), + tokenCreate(UNIQUE_TOKEN) + .initialSupply(0) + .supplyKey(SUPPLY_KEY) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY), + mintToken(UNIQUE_TOKEN, List.of(metadata("memo"))), + burnToken(UNIQUE_TOKEN, List.of(1L)) + .fee(ONE_HBAR) + .payingWith(CIVILIAN_ACCT) + .blankMemo() + .via(BASE_TXN), + validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_BURN_PRICE_USD, 0.01)); + } + + @HapiTest + final Stream baseGrantRevokeKycChargedAsExpected() { + return hapiTest( + newKeyNamed(MULTI_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(MULTI_KEY), + cryptoCreate(CIVILIAN_ACCT), + tokenCreate(FUNGIBLE_TOKEN) + .tokenType(FUNGIBLE_COMMON) + .kycKey(MULTI_KEY) + .payingWith(TOKEN_TREASURY) + .via(BASE_TXN), + tokenAssociate(CIVILIAN_ACCT, FUNGIBLE_TOKEN), + grantTokenKyc(FUNGIBLE_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .signedBy(MULTI_KEY) + .payingWith(TOKEN_TREASURY) + .via("grantKyc"), + revokeTokenKyc(FUNGIBLE_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .signedBy(MULTI_KEY) + .payingWith(TOKEN_TREASURY) + .via("revokeKyc"), + validateChargedUsd("grantKyc", EXPECTED_GRANTKYC_PRICE_USD), + validateChargedUsd("revokeKyc", EXPECTED_REVOKEKYC_PRICE_USD)); + } + + @HapiTest + final Stream baseNftFreezeUnfreezeChargedAsExpected() { + return hapiTest( + newKeyNamed(TREASURE_KEY), + newKeyNamed(ADMIN_KEY), + newKeyNamed(KYC_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(TREASURE_KEY), + cryptoCreate(CIVILIAN_ACCT), + tokenCreate(UNIQUE_TOKEN) + .tokenType(NON_FUNGIBLE_UNIQUE) + .supplyType(TokenSupplyType.INFINITE) + .initialSupply(0L) + .adminKey(ADMIN_KEY) + .freezeKey(TOKEN_TREASURY) + .kycKey(KYC_KEY) + .freezeDefault(false) + .treasury(TOKEN_TREASURY) + .payingWith(TOKEN_TREASURY) + .supplyKey(ADMIN_KEY) + .via(BASE_TXN), + tokenAssociate(CIVILIAN_ACCT, UNIQUE_TOKEN), + tokenFreeze(UNIQUE_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .signedBy(TOKEN_TREASURY) + .payingWith(TOKEN_TREASURY) + .via("freeze"), + tokenUnfreeze(UNIQUE_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .payingWith(TOKEN_TREASURY) + .signedBy(TOKEN_TREASURY) + .via(UNFREEZE), + validateChargedUsdWithin("freeze", EXPECTED_FREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE), + validateChargedUsdWithin(UNFREEZE, EXPECTED_UNFREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); + } + + @HapiTest + final Stream baseCommonFreezeUnfreezeChargedAsExpected() { + return hapiTest( + newKeyNamed(TREASURE_KEY), + newKeyNamed(ADMIN_KEY), + newKeyNamed(FREEZE_KEY), + newKeyNamed(SUPPLY_KEY), + newKeyNamed(WIPE_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(TREASURE_KEY), + cryptoCreate(CIVILIAN_ACCT), + tokenCreate(FUNGIBLE_COMMON_TOKEN) + .adminKey(ADMIN_KEY) + .freezeKey(TOKEN_TREASURY) + .wipeKey(WIPE_KEY) + .supplyKey(SUPPLY_KEY) + .freezeDefault(false) + .treasury(TOKEN_TREASURY) + .payingWith(TOKEN_TREASURY), + tokenAssociate(CIVILIAN_ACCT, FUNGIBLE_COMMON_TOKEN), + tokenFreeze(FUNGIBLE_COMMON_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .signedBy(TOKEN_TREASURY) + .payingWith(TOKEN_TREASURY) + .via("freeze"), + tokenUnfreeze(FUNGIBLE_COMMON_TOKEN, CIVILIAN_ACCT) + .blankMemo() + .payingWith(TOKEN_TREASURY) + .signedBy(TOKEN_TREASURY) + .via(UNFREEZE), + validateChargedUsdWithin("freeze", EXPECTED_FREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE), + validateChargedUsdWithin(UNFREEZE, EXPECTED_UNFREEZE_PRICE_USD, ALLOWED_DIFFERENCE_PERCENTAGE)); + } + + @HapiTest + final Stream basePauseAndUnpauseHaveExpectedPrices() { + final var expectedBaseFee = 0.001; + final var token = "token"; + final var tokenPauseTransaction = "tokenPauseTxn"; + final var tokenUnpauseTransaction = "tokenUnpauseTxn"; + final var civilian = "NonExemptPayer"; + + return hapiTest( + cryptoCreate(TOKEN_TREASURY), + newKeyNamed(PAUSE_KEY), + cryptoCreate(civilian).key(PAUSE_KEY), + tokenCreate(token).pauseKey(PAUSE_KEY).treasury(TOKEN_TREASURY).payingWith(civilian), + tokenPause(token).blankMemo().payingWith(civilian).via(tokenPauseTransaction), + getTokenInfo(token).hasPauseStatus(Paused), + tokenUnpause(token).blankMemo().payingWith(civilian).via(tokenUnpauseTransaction), + getTokenInfo(token).hasPauseStatus(Unpaused), + validateChargedUsd(tokenPauseTransaction, expectedBaseFee), + validateChargedUsd(tokenUnpauseTransaction, expectedBaseFee)); + } + + @HapiTest + final Stream baseNftWipeOperationIsChargedExpectedFee() { + return defaultHapiSpec("BaseUniqueWipeOperationIsChargedExpectedFee") + .given( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(WIPE_KEY), + cryptoCreate(CIVILIAN_ACCT).key(WIPE_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS).key(WIPE_KEY), + tokenCreate(UNIQUE_TOKEN) + .tokenType(NON_FUNGIBLE_UNIQUE) + .supplyType(TokenSupplyType.INFINITE) + .initialSupply(0L) + .supplyKey(SUPPLY_KEY) + .wipeKey(WIPE_KEY) + .treasury(TOKEN_TREASURY), + tokenAssociate(CIVILIAN_ACCT, UNIQUE_TOKEN), + mintToken(UNIQUE_TOKEN, List.of(ByteString.copyFromUtf8("token_to_wipe"))), + cryptoTransfer(movingUnique(UNIQUE_TOKEN, 1L).between(TOKEN_TREASURY, CIVILIAN_ACCT))) + .when(wipeTokenAccount(UNIQUE_TOKEN, CIVILIAN_ACCT, List.of(1L)) + .payingWith(TOKEN_TREASURY) + .fee(ONE_HBAR) + .blankMemo() + .via(BASE_TXN)) + .then(validateChargedUsdWithin(BASE_TXN, EXPECTED_NFT_WIPE_PRICE_USD, 0.01)); + } + + @HapiTest + final Stream updateTokenChargedAsExpected() { + final var expectedUpdatePriceUsd = 0.001; + + return hapiTest( + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(FUNGIBLE_COMMON_TOKEN) + .tokenType(FUNGIBLE_COMMON) + .treasury(TOKEN_TREASURY) + .initialSupply(0) + .entityMemo("") + .symbol("a"), + tokenUpdate(FUNGIBLE_COMMON_TOKEN).via("uniqueTokenUpdate").payingWith(TOKEN_TREASURY), + validateChargedUsd("uniqueTokenUpdate", expectedUpdatePriceUsd)); + } + + @HapiTest + final Stream updateNftChargedAsExpected() { + final var expectedNftUpdatePriceUsd = 0.001; + final var nftUpdateTxn = "nftUpdateTxn"; + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(METADATA_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(NON_FUNGIBLE_TOKEN) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .maxSupply(12L) + .supplyKey(SUPPLY_KEY) + .metadataKey(METADATA_KEY) + .initialSupply(0L), + mintToken( + NON_FUNGIBLE_TOKEN, + List.of( + copyFromUtf8("a"), + copyFromUtf8("b"), + copyFromUtf8("c"), + copyFromUtf8("d"), + copyFromUtf8("e"), + copyFromUtf8("f"), + copyFromUtf8("g"))), + tokenUpdateNfts(NON_FUNGIBLE_TOKEN, NFT_TEST_METADATA, List.of(7L)) + .signedBy(TOKEN_TREASURY, METADATA_KEY) + .payingWith(TOKEN_TREASURY) + .fee(10 * ONE_HBAR) + .via(nftUpdateTxn), + validateChargedUsdWithin(nftUpdateTxn, expectedNftUpdatePriceUsd, 0.01)); + } + + @HapiTest + final Stream deleteTokenChargedAsExpected() { + final var expectedDeletePriceUsd = 0.001; + + return hapiTest( + newKeyNamed(MULTI_KEY), + cryptoCreate(MULTI_KEY).balance(ONE_HUNDRED_HBARS), + tokenCreate(FUNGIBLE_COMMON_TOKEN).tokenType(FUNGIBLE_COMMON).adminKey(MULTI_KEY), + tokenDelete(FUNGIBLE_COMMON_TOKEN).via("uniqueTokenDelete").payingWith(MULTI_KEY), + validateChargedUsd("uniqueTokenDelete", expectedDeletePriceUsd)); + } + + @HapiTest + @DisplayName("FT happy path") + final Stream tokenAssociateDissociateChargedAsExpected() { + final var account = "account"; + return hapiTest( + newKeyNamed(MULTI_KEY), + cryptoCreate(account), + cryptoCreate(MULTI_KEY).balance(ONE_HUNDRED_HBARS), + tokenCreate(FUNGIBLE_COMMON_TOKEN).tokenType(FUNGIBLE_COMMON), + tokenAssociate(MULTI_KEY, FUNGIBLE_COMMON_TOKEN) + .via("tokenAssociate") + .payingWith(MULTI_KEY), + validateChargedUsd("tokenAssociate", 0.05), + tokenDissociate(MULTI_KEY, FUNGIBLE_COMMON_TOKEN) + .via("tokenDissociate") + .payingWith(MULTI_KEY), + validateChargedUsd("tokenDissociate", 0.05)); + } + + @HapiTest + final Stream updateMultipleNftsFeeChargedAsExpected() { + final var expectedNftUpdatePriceUsd = 0.005; + final var nftUpdateTxn = "nftUpdateTxn"; + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(WIPE_KEY), + newKeyNamed(METADATA_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(NON_FUNGIBLE_TOKEN) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .maxSupply(12L) + .wipeKey(WIPE_KEY) + .supplyKey(SUPPLY_KEY) + .metadataKey(METADATA_KEY) + .initialSupply(0L), + mintToken( + NON_FUNGIBLE_TOKEN, + List.of( + copyFromUtf8("a"), + copyFromUtf8("b"), + copyFromUtf8("c"), + copyFromUtf8("d"), + copyFromUtf8("e"), + copyFromUtf8("f"), + copyFromUtf8("g"))), + tokenUpdateNfts(NON_FUNGIBLE_TOKEN, NFT_TEST_METADATA, List.of(1L, 2L, 3L, 4L, 5L)) + .signedBy(TOKEN_TREASURY, METADATA_KEY) + .payingWith(TOKEN_TREASURY) + .fee(10 * ONE_HBAR) + .via(nftUpdateTxn), + validateChargedUsdWithin(nftUpdateTxn, expectedNftUpdatePriceUsd, 0.01)); + } + + @HapiTest + final Stream tokenGetInfoFeeChargedAsExpected() { + final var expectedTokenGetInfo = 0.0001; + final var account = "account"; + + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + newKeyNamed(FUNGIBLE_FREEZE_KEY), + tokenCreate(FUNGIBLE_TOKEN) + .treasury(OWNER) + .tokenType(FUNGIBLE_COMMON) + .freezeKey(FUNGIBLE_FREEZE_KEY) + .initialSupply(1000L), + getTokenInfo(FUNGIBLE_TOKEN).via("getTokenInfo").payingWith(OWNER), + sleepFor(1000), + validateChargedUsd("getTokenInfo", expectedTokenGetInfo)); + } + + @HapiTest + final Stream tokenGetNftInfoFeeChargedAsExpected() { + final var expectedTokenGetNftInfo = 0.0001; + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(WIPE_KEY), + newKeyNamed(METADATA_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(NON_FUNGIBLE_TOKEN) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .maxSupply(12L) + .wipeKey(WIPE_KEY) + .supplyKey(SUPPLY_KEY) + .metadataKey(METADATA_KEY) + .initialSupply(0L), + mintToken( + NON_FUNGIBLE_TOKEN, + List.of( + copyFromUtf8("a"), + copyFromUtf8("b"), + copyFromUtf8("c"), + copyFromUtf8("d"), + copyFromUtf8("e"), + copyFromUtf8("f"), + copyFromUtf8("g"))), + getTokenNftInfo(NON_FUNGIBLE_TOKEN, 1L).via("getTokenInfo").payingWith(TOKEN_TREASURY), + sleepFor(1000), + validateChargedUsd("getTokenInfo", expectedTokenGetNftInfo)); + } + + @HapiTest + final Stream tokenUpdateNftsFeeChargedAsExpected() { + final var expectedTokenUpdateNfts = 0.001; + + return hapiTest( + newKeyNamed(SUPPLY_KEY), + newKeyNamed(WIPE_KEY), + newKeyNamed(METADATA_KEY), + cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), + tokenCreate(NON_FUNGIBLE_TOKEN) + .supplyType(TokenSupplyType.FINITE) + .tokenType(NON_FUNGIBLE_UNIQUE) + .treasury(TOKEN_TREASURY) + .maxSupply(12L) + .wipeKey(WIPE_KEY) + .metadataKey(METADATA_KEY) + .supplyKey(SUPPLY_KEY) + .initialSupply(0L), + mintToken( + NON_FUNGIBLE_TOKEN, + List.of( + copyFromUtf8("a"), + copyFromUtf8("b"), + copyFromUtf8("c"), + copyFromUtf8("d"), + copyFromUtf8("e"), + copyFromUtf8("f"), + copyFromUtf8("g"))), + tokenUpdateNfts(NON_FUNGIBLE_TOKEN, NFT_TEST_METADATA, List.of(1L)) + .signedBy(TOKEN_TREASURY, METADATA_KEY) + .payingWith(TOKEN_TREASURY) + .via("nftUpdateTxn"), + validateChargedUsd("nftUpdateTxn", expectedTokenUpdateNfts)); + } + + private String txnFor(String tokenSubType) { + return tokenSubType + "Txn"; + } +} diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java index 95e51a5a8ec9..2313c3418ab7 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/file/FileAppendSuite.java @@ -20,25 +20,18 @@ import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileContents; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getFileInfo; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileAppend; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; -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.overriding; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; -import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; -import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.MAX_FILE_SIZE_EXCEEDED; import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.LeakyHapiTest; import java.util.Arrays; -import java.util.List; import java.util.UUID; import java.util.stream.Stream; import org.junit.jupiter.api.DynamicTest; @@ -69,39 +62,6 @@ final Stream getInfoIdVariantsTreatedAsExpected() { .then(sendModified(withSuccessivelyVariedQueryIds(), () -> getFileInfo("file"))); } - @HapiTest - final Stream baseOpsHaveExpectedPrices() { - final var civilian = "NonExemptPayer"; - - final var expectedAppendFeesPriceUsd = 0.05; - - final var baseAppend = "baseAppend"; - final var targetFile = "targetFile"; - final var contentBuilder = new StringBuilder(); - for (int i = 0; i < 1000; i++) { - contentBuilder.append("A"); - } - final var magicKey = "magicKey"; - final var magicWacl = "magicWacl"; - - return defaultHapiSpec("BaseOpsHaveExpectedPrices") - .given( - newKeyNamed(magicKey), - newKeyListNamed(magicWacl, List.of(magicKey)), - cryptoCreate(civilian).balance(ONE_HUNDRED_HBARS).key(magicKey), - fileCreate(targetFile) - .key(magicWacl) - .lifetime(THREE_MONTHS_IN_SECONDS) - .contents("Nothing much!")) - .when(fileAppend(targetFile) - .signedBy(magicKey) - .blankMemo() - .content(contentBuilder.toString()) - .payingWith(civilian) - .via(baseAppend)) - .then(validateChargedUsdWithin(baseAppend, expectedAppendFeesPriceUsd, 0.01)); - } - @HapiTest final Stream vanillaAppendSucceeds() { final byte[] first4K = randomUtf8Bytes(BYTES_4K); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenAirdropBase.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenAirdropBase.java index 333d0c8cb953..3673927b2f87 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenAirdropBase.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenAirdropBase.java @@ -146,7 +146,7 @@ protected TokenMovement moveFungibleTokensTo(String receiver) { * * @return array of operations */ - protected static SpecOperation[] setUpTokensAndAllReceivers() { + public static SpecOperation[] setUpTokensAndAllReceivers() { var nftSupplyKey = "nftSupplyKey"; final var t = new ArrayList(List.of( cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenCancelAirdropTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenCancelAirdropTest.java index 5d0518bd055c..a0cdb2336ac2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenCancelAirdropTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenCancelAirdropTest.java @@ -18,11 +18,6 @@ import static com.hedera.services.bdd.junit.TestTags.CRYPTO; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingFungiblePendingAirdrop; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.includingNftPendingAirdrop; -import static com.hedera.services.bdd.spec.assertions.TransactionRecordAsserts.recordWith; -import static com.hedera.services.bdd.spec.queries.QueryVerbs.getAccountBalance; -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.transactions.TxnVerbs.cryptoDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; @@ -41,7 +36,6 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.suites.HapiSuite.FIVE_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.ACCOUNT_HAS_PENDING_AIRDROPS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.EMPTY_PENDING_AIRDROP_ID_LIST; @@ -84,73 +78,6 @@ static void beforeAll(@NonNull final TestLifecycle lifecycle) { lifecycle.doAdhoc(setUpTokensAndAllReceivers()); } - @HapiTest - @DisplayName("FT happy path") - final Stream ftHappyPath() { - final var account = "account"; - return hapiTest( - // setup initial account - cryptoCreate(account), - tokenAssociate(account, FUNGIBLE_TOKEN), - cryptoTransfer(moving(10, FUNGIBLE_TOKEN).between(OWNER, account)), - - // Create an airdrop in pending state - tokenAirdrop(moving(10, FUNGIBLE_TOKEN).between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)) - .payingWith(account) - .via("airdrop"), - - // Verify that a pending state is created and the correct usd is charged - getTxnRecord("airdrop") - .hasPriority(recordWith() - .pendingAirdrops(includingFungiblePendingAirdrop(moving(10, FUNGIBLE_TOKEN) - .between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)))), - getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(FUNGIBLE_TOKEN, 0), - validateChargedUsd("airdrop", 0.1, 1), - - // Cancel the airdrop - tokenCancelAirdrop(pendingAirdrop(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, FUNGIBLE_TOKEN)) - .payingWith(account) - .via("cancelAirdrop"), - - // Verify that the receiver doesn't have the token - getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(FUNGIBLE_TOKEN, 0), - validateChargedUsd("cancelAirdrop", 0.001, 1)); - } - - @HapiTest - @DisplayName("NFT happy path") - final Stream nftHappyPath() { - final var account = "account"; - return hapiTest( - // setup initial account - cryptoCreate(account), - tokenAssociate(account, NON_FUNGIBLE_TOKEN), - cryptoTransfer(movingUnique(NON_FUNGIBLE_TOKEN, 1L).between(OWNER, account)), - - // Create an airdrop in pending state - tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1L).between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)) - .payingWith(account) - .via("airdrop"), - - // Verify that a pending state is created and the correct usd is charged - getTxnRecord("airdrop") - .hasPriority(recordWith() - .pendingAirdrops(includingNftPendingAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1L) - .between(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)))), - getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(NON_FUNGIBLE_TOKEN, 0), - validateChargedUsd("airdrop", 0.1, 1), - - // Cancel the airdrop - tokenCancelAirdrop( - pendingNFTAirdrop(account, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, NON_FUNGIBLE_TOKEN, 1L)) - .payingWith(account) - .via("cancelAirdrop"), - - // Verify that the receiver doesn't have the token - getAccountBalance(RECEIVER_WITH_0_AUTO_ASSOCIATIONS).hasTokenBalance(NON_FUNGIBLE_TOKEN, 0), - validateChargedUsd("cancelAirdrop", 0.001, 1)); - } - @HapiTest @DisplayName("not created NFT pending airdrop") final Stream cancelNotCreatedNFTPendingAirdrop() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java index 1c2436aecbc5..45e9e72f226e 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java @@ -187,41 +187,6 @@ final Stream claimTokenAirdropAfterDissociation() { getAccountInfo(RECEIVER).hasToken(relationshipWith(NON_FUNGIBLE_TOKEN)))); } - @HapiTest - final Stream claimFungibleTokenAirdrop() { - return defaultHapiSpec("should transfer fungible tokens") - .given(flattened( - setUpTokensAndAllReceivers(), cryptoCreate(RECEIVER).balance(ONE_HUNDRED_HBARS))) - .when( - // do pending airdrop - tokenAirdrop(moving(10, FUNGIBLE_TOKEN).between(OWNER, RECEIVER)) - .payingWith(OWNER), - tokenAirdrop(movingUnique(NON_FUNGIBLE_TOKEN, 1).between(OWNER, RECEIVER)) - .payingWith(OWNER), - - // do claim - tokenClaimAirdrop( - pendingAirdrop(OWNER, RECEIVER, FUNGIBLE_TOKEN), - pendingNFTAirdrop(OWNER, RECEIVER, NON_FUNGIBLE_TOKEN, 1)) - .payingWith(RECEIVER) - .via("claimTxn")) - .then( // assert txn record - getTxnRecord("claimTxn") - .hasPriority(recordWith() - .tokenTransfers(includingFungibleMovement( - moving(10, FUNGIBLE_TOKEN).between(OWNER, RECEIVER))) - .tokenTransfers(includingNonfungibleMovement(movingUnique(NON_FUNGIBLE_TOKEN, 1) - .between(OWNER, RECEIVER)))), - validateChargedUsd("claimTxn", 0.001, 1), - // assert balance fungible tokens - getAccountBalance(RECEIVER).hasTokenBalance(FUNGIBLE_TOKEN, 10), - // assert balances NFT - getAccountBalance(RECEIVER).hasTokenBalance(NON_FUNGIBLE_TOKEN, 1), - // assert token associations - getAccountInfo(RECEIVER).hasToken(relationshipWith(FUNGIBLE_TOKEN)), - getAccountInfo(RECEIVER).hasToken(relationshipWith(NON_FUNGIBLE_TOKEN))); - } - @HapiTest @DisplayName("single token claim success that receiver paying for it") final Stream singleTokenClaimSuccessThatReceiverPayingForIt() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/FutureSchedulableOpsTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/FutureSchedulableOpsTest.java index c4e1f502b49b..7a92660e006d 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/FutureSchedulableOpsTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/FutureSchedulableOpsTest.java @@ -16,7 +16,6 @@ package com.hedera.services.bdd.suites.schedule; -import static com.hedera.services.bdd.junit.ContextRequirement.FEE_SCHEDULE_OVERRIDES; import static com.hedera.services.bdd.spec.HapiSpec.hapiTest; import static com.hedera.services.bdd.spec.keys.ControlForKey.forKey; import static com.hedera.services.bdd.spec.keys.KeyShape.SIMPLE; @@ -29,27 +28,19 @@ import static com.hedera.services.bdd.spec.queries.QueryVerbs.getScheduleInfo; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTxnRecord; import static com.hedera.services.bdd.spec.transactions.TxnUtils.randomUppercase; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCall; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.contractCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoTransfer; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleCreate; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleDelete; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.scheduleSign; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.systemFileDelete; -import static com.hedera.services.bdd.spec.transactions.TxnVerbs.uploadInitCode; -import static com.hedera.services.bdd.spec.transactions.crypto.HapiCryptoTransfer.tinyBarsFromTo; import static com.hedera.services.bdd.spec.utilops.CustomSpecAssert.allRunFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doAdhoc; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.doWithStartupConfig; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sleepFor; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sourcing; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.uploadScheduledContractPrices; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.suites.HapiSuite.FREEZE_ADMIN; import static com.hedera.services.bdd.suites.HapiSuite.GENESIS; @@ -60,15 +51,11 @@ import static com.hedera.services.bdd.suites.freeze.UpgradeSuite.standardUpdateFile; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.A_SCHEDULE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.ORIG_FILE; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.OTHER_PAYER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_ACCOUNT_2; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.PAYING_SENDER; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.RECEIVER; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SCHEDULED_TRANSACTION_MUST_SUCCEED; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SHARED_KEY; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIGN_TX; -import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SIMPLE_UPDATE; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SOMEBODY; import static com.hedera.services.bdd.suites.schedule.ScheduleUtils.SUCCESS_TXN; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.AUTHORIZATION_FAILED; @@ -80,12 +67,10 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestLifecycle; -import com.hedera.services.bdd.junit.LeakyHapiTest; import com.hedera.services.bdd.junit.support.TestLifecycle; import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.keys.SigControl; import edu.umd.cs.findbugs.annotations.NonNull; -import java.math.BigInteger; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -103,51 +88,6 @@ static void beforeAll(@NonNull final TestLifecycle testLifecycle) { "scheduling.whitelist", "ContractCall,CryptoCreate,CryptoTransfer,FileDelete,FileUpdate,SystemDelete")); } - @LeakyHapiTest(requirement = FEE_SCHEDULE_OVERRIDES) - final Stream canonicalScheduleOpsHaveExpectedUsdFees() { - return hapiTest( - uploadScheduledContractPrices(GENESIS), - uploadInitCode(SIMPLE_UPDATE), - cryptoCreate(OTHER_PAYER), - cryptoCreate(PAYING_SENDER), - cryptoCreate(RECEIVER).receiverSigRequired(true), - contractCreate(SIMPLE_UPDATE).gas(300_000L), - scheduleCreate( - "canonical", - cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) - .memo("") - .fee(ONE_HBAR)) - .payingWith(OTHER_PAYER) - .via("canonicalCreation") - .alsoSigningWith(PAYING_SENDER) - .adminKey(OTHER_PAYER), - scheduleSign("canonical") - .via("canonicalSigning") - .payingWith(PAYING_SENDER) - .alsoSigningWith(RECEIVER), - scheduleCreate( - "tbd", - cryptoTransfer(tinyBarsFromTo(PAYING_SENDER, RECEIVER, 1L)) - .memo("") - .fee(ONE_HBAR)) - .payingWith(PAYING_SENDER) - .adminKey(PAYING_SENDER), - scheduleDelete("tbd").via("canonicalDeletion").payingWith(PAYING_SENDER), - scheduleCreate( - "contractCall", - contractCall(SIMPLE_UPDATE, "set", BigInteger.valueOf(5), BigInteger.valueOf(42)) - .gas(24_000) - .memo("") - .fee(ONE_HBAR)) - .payingWith(OTHER_PAYER) - .via("canonicalContractCall") - .adminKey(OTHER_PAYER), - validateChargedUsdWithin("canonicalCreation", 0.01, 3.0), - validateChargedUsdWithin("canonicalSigning", 0.001, 3.0), - validateChargedUsdWithin("canonicalDeletion", 0.001, 3.0), - validateChargedUsdWithin("canonicalContractCall", 0.1, 3.0)); - } - @HapiTest final Stream scheduledPermissionedFileUpdateWorksAsExpected() { return hapiTest( diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java index b7cd4ca1afb4..3d5200ea4111 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/schedule/ScheduleUtils.java @@ -49,15 +49,15 @@ public final class ScheduleUtils { static final String ONLY_BODY_AND_MEMO = "onlyBodyAndMemo"; static final String ONLY_BODY_AND_PAYER = "onlyBodyAndPayer"; static final String ORIGINAL = "original"; - static final String OTHER_PAYER = "otherPayer"; + public static final String OTHER_PAYER = "otherPayer"; static final String PAYER = "payer"; static final String PAYING_ACCOUNT = "payingAccount"; static final String PAYING_ACCOUNT_2 = "payingAccount2"; - static final String PAYING_SENDER = "payingSender"; + public static final String PAYING_SENDER = "payingSender"; static final String RANDOM_KEY = "randomKey"; static final String RANDOM_MSG = "Little did they care who danced between / And little she by whom her dance was seen"; - static final String RECEIVER = "receiver"; + public static final String RECEIVER = "receiver"; static final String RECEIVER_A = "receiverA"; static final String RECEIVER_B = "receiverB"; static final String RECEIVER_C = "receiverC"; @@ -75,7 +75,7 @@ public final class ScheduleUtils { static final String SHARED_KEY = "sharedKey"; static final String SIGN_TX = "signTx"; static final String SIGN_TXN = "signTx"; - static final String SIMPLE_UPDATE = "SimpleUpdate"; + public static final String SIMPLE_UPDATE = "SimpleUpdate"; static final String SIMPLE_XFER_SCHEDULE = "simpleXferSchedule"; static final String SOMEBODY = "somebody"; static final String SUCCESS_TXN = "successTxn"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java index 75c5ff559ac7..3a3026967bc2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenCreateSpecs.java @@ -57,13 +57,11 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.sendModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.specOps; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedQueryIds; 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; import static com.hedera.services.bdd.suites.HapiSuite.THREE_MONTHS_IN_SECONDS; import static com.hedera.services.bdd.suites.HapiSuite.salted; @@ -134,7 +132,6 @@ public class TokenCreateSpecs { private static final String ADMIN_KEY = "adminKey"; private static final String SUPPLY_KEY = "supplyKey"; private static final String AUTO_RENEW = "autoRenew"; - private static final String NAME = "012345678912"; private static final String CREATE_TXN = "createTxn"; private static final String PAYER = "payer"; @@ -378,93 +375,6 @@ final Stream creationWithoutKYCSetsCorrectStatus() { .hasToken(relationshipWith(PRIMARY).kyc(TokenKycStatus.KycNotApplicable))); } - @HapiTest - final Stream baseCreationsHaveExpectedPrices() { - final var civilian = "NonExemptPayer"; - - final var expectedCommonNoCustomFeesPriceUsd = 1.00; - final var expectedUniqueNoCustomFeesPriceUsd = 1.00; - final var expectedCommonWithCustomFeesPriceUsd = 2.00; - final var expectedUniqueWithCustomFeesPriceUsd = 2.00; - - final var commonNoFees = "commonNoFees"; - final var commonWithFees = "commonWithFees"; - final var uniqueNoFees = "uniqueNoFees"; - final var uniqueWithFees = "uniqueWithFees"; - - final var customFeeKey = "customFeeKey"; - - return hapiTest( - cryptoCreate(civilian).balance(ONE_HUNDRED_HBARS), - cryptoCreate(TOKEN_TREASURY).balance(0L), - cryptoCreate(AUTO_RENEW_ACCOUNT).balance(0L), - newKeyNamed(ADMIN_KEY), - newKeyNamed(SUPPLY_KEY), - newKeyNamed(customFeeKey), - tokenCreate(commonNoFees) - .blankMemo() - .entityMemo("") - .name(NAME) - .symbol("ABCD") - .payingWith(civilian) - .treasury(TOKEN_TREASURY) - .autoRenewAccount(AUTO_RENEW_ACCOUNT) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .adminKey(ADMIN_KEY) - .via(txnFor(commonNoFees)), - tokenCreate(commonWithFees) - .blankMemo() - .entityMemo("") - .name(NAME) - .symbol("ABCD") - .payingWith(civilian) - .treasury(TOKEN_TREASURY) - .autoRenewAccount(AUTO_RENEW_ACCOUNT) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .adminKey(ADMIN_KEY) - .withCustom(fixedHbarFee(ONE_HBAR, TOKEN_TREASURY)) - .feeScheduleKey(customFeeKey) - .via(txnFor(commonWithFees)), - tokenCreate(uniqueNoFees) - .payingWith(civilian) - .blankMemo() - .entityMemo("") - .name(NAME) - .symbol("ABCD") - .initialSupply(0L) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY) - .autoRenewAccount(AUTO_RENEW_ACCOUNT) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .adminKey(ADMIN_KEY) - .supplyKey(SUPPLY_KEY) - .via(txnFor(uniqueNoFees)), - tokenCreate(uniqueWithFees) - .payingWith(civilian) - .blankMemo() - .entityMemo("") - .name(NAME) - .symbol("ABCD") - .initialSupply(0L) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY) - .autoRenewAccount(AUTO_RENEW_ACCOUNT) - .autoRenewPeriod(THREE_MONTHS_IN_SECONDS) - .adminKey(ADMIN_KEY) - .withCustom(fixedHbarFee(ONE_HBAR, TOKEN_TREASURY)) - .supplyKey(SUPPLY_KEY) - .feeScheduleKey(customFeeKey) - .via(txnFor(uniqueWithFees)), - validateChargedUsdWithin(txnFor(commonNoFees), expectedCommonNoCustomFeesPriceUsd, 0.01), - validateChargedUsdWithin(txnFor(commonWithFees), expectedCommonWithCustomFeesPriceUsd, 0.01), - validateChargedUsdWithin(txnFor(uniqueNoFees), expectedUniqueNoCustomFeesPriceUsd, 0.01), - validateChargedUsdWithin(txnFor(uniqueWithFees), expectedUniqueWithCustomFeesPriceUsd, 0.01)); - } - - private String txnFor(String tokenSubType) { - return tokenSubType + "Txn"; - } - @HapiTest final Stream creationHappyPath() { String memo = "JUMP"; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java index 156d25166632..be4f4c07cba3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenFeeScheduleUpdateSpecs.java @@ -18,7 +18,6 @@ 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.queries.QueryVerbs.getTokenInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.cryptoCreate; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.fileUpdate; @@ -37,12 +36,10 @@ import static com.hedera.services.bdd.spec.transactions.token.CustomFeeTests.fractionalFeeInSchedule; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.suites.HapiSuite.APP_PROPERTIES; 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.THREE_MONTHS_IN_SECONDS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEES_LIST_TOO_LONG; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_MUST_BE_POSITIVE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.CUSTOM_FEE_NOT_FULLY_SPECIFIED; @@ -56,7 +53,6 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hederahashgraph.api.proto.java.TokenType; -import java.time.Instant; import java.util.Map; import java.util.OptionalLong; import java.util.stream.Stream; @@ -65,31 +61,6 @@ @Tag(TOKEN) public class TokenFeeScheduleUpdateSpecs { - @HapiTest - final Stream baseOperationIsChargedExpectedFee() { - final var htsAmount = 2_345L; - final var targetToken = "immutableToken"; - final var feeDenom = "denom"; - final var htsCollector = "denomFee"; - final var feeScheduleKey = "feeSchedule"; - final var expectedBasePriceUsd = 0.001; - - return hapiTest( - newKeyNamed(feeScheduleKey), - cryptoCreate("civilian").key(feeScheduleKey), - cryptoCreate(htsCollector), - tokenCreate(feeDenom).treasury(htsCollector), - tokenCreate(targetToken) - .expiry(Instant.now().getEpochSecond() + THREE_MONTHS_IN_SECONDS) - .feeScheduleKey(feeScheduleKey), - tokenFeeScheduleUpdate(targetToken) - .signedBy(feeScheduleKey) - .payingWith("civilian") - .blankMemo() - .withCustom(fixedHtsFee(htsAmount, feeDenom, htsCollector)) - .via("baseFeeSchUpd"), - validateChargedUsdWithin("baseFeeSchUpd", expectedBasePriceUsd, 1.0)); - } @HapiTest final Stream idVariantsTreatedAsExpected() { diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java index 15e4e426ab4a..c629e798a3b9 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenPauseSpecs.java @@ -44,7 +44,6 @@ import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.moving; import static com.hedera.services.bdd.spec.transactions.token.TokenMovement.movingUnique; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; 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; @@ -383,33 +382,6 @@ final Stream cannotChangePauseStatusIfMissingPauseKey() { .hasKnownStatus(ResponseCodeEnum.TOKEN_HAS_NO_PAUSE_KEY)); } - @HapiTest - final Stream basePauseAndUnpauseHaveExpectedPrices() { - final var expectedBaseFee = 0.001; - final var token = "token"; - final var tokenPauseTransaction = "tokenPauseTxn"; - final var tokenUnpauseTransaction = "tokenUnpauseTxn"; - final var civilian = "NonExemptPayer"; - - return defaultHapiSpec("BasePauseAndUnpauseHaveExpectedPrices") - .given( - cryptoCreate(TOKEN_TREASURY), - newKeyNamed(PAUSE_KEY), - cryptoCreate(civilian).key(PAUSE_KEY)) - .when( - tokenCreate(token) - .pauseKey(PAUSE_KEY) - .treasury(TOKEN_TREASURY) - .payingWith(civilian), - tokenPause(token).blankMemo().payingWith(civilian).via(tokenPauseTransaction), - getTokenInfo(token).hasPauseStatus(Paused), - tokenUnpause(token).blankMemo().payingWith(civilian).via(tokenUnpauseTransaction), - getTokenInfo(token).hasPauseStatus(Unpaused)) - .then( - validateChargedUsd(tokenPauseTransaction, expectedBaseFee), - validateChargedUsd(tokenUnpauseTransaction, expectedBaseFee)); - } - public static class TokenIdOrderingAsserts extends BaseErroringAssertsProvider> { private final String[] expectedTokenIds; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java index 1adb8471f183..b2c9810833a6 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/token/TokenUpdateNftsSuite.java @@ -19,7 +19,6 @@ import static com.google.protobuf.ByteString.copyFromUtf8; 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.queries.QueryVerbs.getAccountBalance; import static com.hedera.services.bdd.spec.queries.QueryVerbs.getTokenNftInfo; import static com.hedera.services.bdd.spec.transactions.TxnVerbs.burnToken; @@ -32,12 +31,10 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.tokenUpdateNfts; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.newKeyNamed; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; 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; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.SUCCESS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.TOKEN_HAS_NO_METADATA_KEY; @@ -454,78 +451,4 @@ final Stream updateMetadataOfNfts() { burnToken(NON_FUNGIBLE_TOKEN, List.of(7L)), getAccountBalance(TOKEN_TREASURY).hasTokenBalance(NON_FUNGIBLE_TOKEN, 6L)); } - - @HapiTest - final Stream updateSingleNftFeeChargedAsExpected() { - final var expectedNftUpdatePriceUsd = 0.001; - final var nftUpdateTxn = "nftUpdateTxn"; - - return hapiTest( - newKeyNamed(SUPPLY_KEY), - newKeyNamed(WIPE_KEY), - newKeyNamed(METADATA_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), - tokenCreate(NON_FUNGIBLE_TOKEN) - .supplyType(TokenSupplyType.FINITE) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY) - .maxSupply(12L) - .wipeKey(WIPE_KEY) - .supplyKey(SUPPLY_KEY) - .metadataKey(METADATA_KEY) - .initialSupply(0L), - mintToken( - NON_FUNGIBLE_TOKEN, - List.of( - copyFromUtf8("a"), - copyFromUtf8("b"), - copyFromUtf8("c"), - copyFromUtf8("d"), - copyFromUtf8("e"), - copyFromUtf8("f"), - copyFromUtf8("g"))), - tokenUpdateNfts(NON_FUNGIBLE_TOKEN, NFT_TEST_METADATA, List.of(7L)) - .signedBy(TOKEN_TREASURY, METADATA_KEY) - .payingWith(TOKEN_TREASURY) - .fee(10 * ONE_HBAR) - .via(nftUpdateTxn), - validateChargedUsdWithin(nftUpdateTxn, expectedNftUpdatePriceUsd, 0.01)); - } - - @HapiTest - final Stream updateMultipleNftsFeeChargedAsExpected() { - final var expectedNftUpdatePriceUsd = 0.005; - final var nftUpdateTxn = "nftUpdateTxn"; - - return hapiTest( - newKeyNamed(SUPPLY_KEY), - newKeyNamed(WIPE_KEY), - newKeyNamed(METADATA_KEY), - cryptoCreate(TOKEN_TREASURY).balance(ONE_HUNDRED_HBARS), - tokenCreate(NON_FUNGIBLE_TOKEN) - .supplyType(TokenSupplyType.FINITE) - .tokenType(NON_FUNGIBLE_UNIQUE) - .treasury(TOKEN_TREASURY) - .maxSupply(12L) - .wipeKey(WIPE_KEY) - .supplyKey(SUPPLY_KEY) - .metadataKey(METADATA_KEY) - .initialSupply(0L), - mintToken( - NON_FUNGIBLE_TOKEN, - List.of( - copyFromUtf8("a"), - copyFromUtf8("b"), - copyFromUtf8("c"), - copyFromUtf8("d"), - copyFromUtf8("e"), - copyFromUtf8("f"), - copyFromUtf8("g"))), - tokenUpdateNfts(NON_FUNGIBLE_TOKEN, NFT_TEST_METADATA, List.of(1L, 2L, 3L, 4L, 5L)) - .signedBy(TOKEN_TREASURY, METADATA_KEY) - .payingWith(TOKEN_TREASURY) - .fee(10 * ONE_HBAR) - .via(nftUpdateTxn), - validateChargedUsdWithin(nftUpdateTxn, expectedNftUpdatePriceUsd, 0.01)); - } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java index f670fba7a69c..6bf329433cf3 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/util/UtilPrngSuite.java @@ -22,8 +22,6 @@ import static com.hedera.services.bdd.spec.transactions.TxnVerbs.hapiPrng; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.overridingAllOf; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.submitModified; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; -import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsdWithin; import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PRNG_RANGE; @@ -38,34 +36,6 @@ public class UtilPrngSuite { private static final String PRNG_IS_ENABLED = "utilPrng.isEnabled"; public static final String BOB = "bob"; - @HapiTest - final Stream usdFeeAsExpected() { - double baseFee = 0.001; - double plusRangeFee = 0.0010010316; - - final var baseTxn = "prng"; - final var plusRangeTxn = "prngWithRange"; - - return defaultHapiSpec("usdFeeAsExpected") - .given( - overridingAllOf(Map.of(PRNG_IS_ENABLED, "true")), - cryptoCreate(BOB).balance(ONE_HUNDRED_HBARS), - hapiPrng().payingWith(BOB).via(baseTxn).blankMemo().logged(), - getTxnRecord(baseTxn).hasOnlyPseudoRandomBytes().logged(), - validateChargedUsd(baseTxn, baseFee)) - .when( - hapiPrng(10) - .payingWith(BOB) - .via(plusRangeTxn) - .blankMemo() - .logged(), - getTxnRecord(plusRangeTxn) - .hasOnlyPseudoRandomNumberInRange(10) - .logged(), - validateChargedUsdWithin(plusRangeTxn, plusRangeFee, 0.5)) - .then(); - } - @HapiTest final Stream failsInPreCheckForNegativeRange() { return defaultHapiSpec("failsInPreCheckForNegativeRange") From 13bd3a9fa1bf4f87b9a71d111b44880aafc4ab2c Mon Sep 17 00:00:00 2001 From: anthony-swirldslabs <152534762+anthony-swirldslabs@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:54:11 -0800 Subject: [PATCH 16/39] =?UTF-8?q?chore:=20replace=20PlatformStateAccessor.?= =?UTF-8?q?getAddressBook()=20with=20calls=20to=20R=E2=80=A6=20(#17047)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Anthony Petrov --- .../main/java/com/hedera/node/app/Hedera.java | 5 +- .../node/app/info/DiskStartupNetworks.java | 14 +- .../hedera/node/app/roster/RosterService.java | 12 +- .../app/roster/schemas/V0540RosterSchema.java | 13 +- .../app/info/DiskStartupNetworksTest.java | 9 +- .../node/app/roster/RosterServiceTest.java | 6 +- .../roster/schemas/V0540RosterSchemaTest.java | 48 ++++++- .../builder/PlatformComponentBuilder.java | 7 +- .../cli/ValidateAddressBookStateCommand.java | 9 +- .../platform/roster/RosterRetriever.java | 8 +- .../swirlds/platform/roster/RosterUtils.java | 27 +++- .../state/address/AddressBookInitializer.java | 7 +- .../state/service/WritableRosterStore.java | 5 +- .../state/snapshot/SavedStateMetadata.java | 10 +- .../system/address/AddressBookUtils.java | 7 +- .../platform/AddressBookInitializerTest.java | 39 ++++-- .../platform/SavedStateMetadataTests.java | 13 +- .../SignedStateFileReadWriteTest.java | 1 + .../platform/StateFileManagerTests.java | 6 +- .../DefaultSignedStateValidatorTests.java | 13 +- .../platform/reconnect/ReconnectTest.java | 8 +- .../platform/state/StateRegistryTests.java | 3 - .../platform/state/StateSigningTests.java | 7 +- .../state/hashlogger/HashLoggerTest.java | 11 +- .../state/signed/StartupStateUtilsTests.java | 8 ++ .../roster/RosterServiceStateMock.java | 129 ++++++++++++++++++ .../fixtures/state/BlockingSwirldState.java | 4 +- .../state/RandomSignedStateGenerator.java | 23 +++- .../event/preconsensus/PcesReplayerTests.java | 9 +- 29 files changed, 355 insertions(+), 106 deletions(-) create mode 100644 platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/roster/RosterServiceStateMock.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 fc47299d054d..ac8a769a966d 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 @@ -458,10 +458,7 @@ public Hedera( // FUTURE: a lambda that tests if a ReadableTssStore // constructed from the migration state returns a // RosterKeys with the ledger id for the given roster - new RosterService( - roster -> true, - () -> new ReadablePlatformStateStore( - requireNonNull(initState).getReadableStates(PlatformStateService.NAME))), + new RosterService(roster -> true, () -> requireNonNull(initState)), PLATFORM_STATE_SERVICE) .forEach(servicesRegistry::register); try { diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java index 2df85c36d661..44b2eaccc2ef 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/info/DiskStartupNetworks.java @@ -44,8 +44,7 @@ import com.hedera.pbj.runtime.io.stream.WritableStreamingData; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; -import com.swirlds.platform.state.service.PlatformStateService; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; +import com.swirlds.platform.roster.RosterRetriever; import com.swirlds.platform.state.service.ReadableRosterStore; import com.swirlds.platform.state.service.ReadableRosterStoreImpl; import com.swirlds.platform.system.address.AddressBook; @@ -164,32 +163,31 @@ public Network migrationNetworkOrThrow() { public static void writeNetworkInfo(@NonNull final State state, @NonNull final Path path) { requireNonNull(state); writeNetworkInfo( + state, new ReadableTssStoreImpl(state.getReadableStates(TssBaseService.NAME)), new ReadableNodeStoreImpl(state.getReadableStates(AddressBookService.NAME)), new ReadableRosterStoreImpl(state.getReadableStates(RosterService.NAME)), - new ReadablePlatformStateStore(state.getReadableStates(PlatformStateService.NAME)), path); } /** * Writes a JSON representation of the {@link Network} information in the given state to a given path. * - * @param platformStateStore the platform state store to read the network information from + * @param state the state to read the network information from * @param path the path to write the JSON network information to. */ public static void writeNetworkInfo( + @NonNull final State state, @NonNull final ReadableTssStore tssStore, @NonNull final ReadableNodeStore nodeStore, @NonNull final ReadableRosterStore rosterStore, - @NonNull final ReadablePlatformStateStore platformStateStore, @NonNull final Path path) { + requireNonNull(state); requireNonNull(tssStore); requireNonNull(nodeStore); requireNonNull(rosterStore); requireNonNull(path); - requireNonNull(platformStateStore); - Optional.ofNullable(rosterStore.getActiveRoster()) - .or(() -> Optional.ofNullable(buildRoster(platformStateStore.getAddressBook()))) + Optional.ofNullable(RosterRetriever.retrieveActiveOrGenesisRoster(state)) .ifPresent(activeRoster -> { final var network = Network.newBuilder(); final List nodeMetadata = new ArrayList<>(); diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java index 470cc01d0bcd..44b87189e17b 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/RosterService.java @@ -21,8 +21,8 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.roster.schemas.V0540RosterSchema; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.state.State; import com.swirlds.state.lifecycle.SchemaRegistry; import com.swirlds.state.lifecycle.Service; import edu.umd.cs.findbugs.annotations.NonNull; @@ -54,13 +54,11 @@ public class RosterService implements Service { * we must initialize the active roster from the platform state's legacy address books. */ @Deprecated - private final Supplier platformStateStoreFactory; + private final Supplier stateSupplier; - public RosterService( - @NonNull final Predicate canAdopt, - @NonNull final Supplier platformStateStoreFactory) { + public RosterService(@NonNull final Predicate canAdopt, @NonNull final Supplier stateSupplier) { this.canAdopt = requireNonNull(canAdopt); - this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); + this.stateSupplier = requireNonNull(stateSupplier); } @NonNull @@ -77,6 +75,6 @@ public int migrationOrder() { @Override public void registerSchemas(@NonNull final SchemaRegistry registry) { requireNonNull(registry); - registry.register(new V0540RosterSchema(canAdopt, WritableRosterStore::new, platformStateStoreFactory)); + registry.register(new V0540RosterSchema(canAdopt, WritableRosterStore::new, stateSupplier)); } } diff --git a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java index 4c2220218178..0ae44a84c692 100644 --- a/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java +++ b/hedera-node/hedera-app/src/main/java/com/hedera/node/app/roster/schemas/V0540RosterSchema.java @@ -16,7 +16,6 @@ package com.hedera.node.app.roster.schemas; -import static com.swirlds.platform.roster.RosterRetriever.buildRoster; import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.base.SemanticVersion; @@ -24,10 +23,11 @@ import com.hedera.hapi.node.state.roster.RosterState; import com.hedera.node.app.version.ServicesSoftwareVersion; import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.roster.RosterRetriever; import com.swirlds.platform.roster.RosterUtils; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.WritableRosterStore; import com.swirlds.platform.state.service.schemas.V0540RosterBaseSchema; +import com.swirlds.state.State; import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.StateDefinition; @@ -74,16 +74,16 @@ public class V0540RosterSchema extends Schema implements RosterTransplantSchema * we must initialize the active roster from the platform state's legacy address books. */ @Deprecated - private final Supplier platformStateStoreFactory; + private final Supplier stateSupplier; public V0540RosterSchema( @NonNull final Predicate canAdopt, @NonNull final Function rosterStoreFactory, - @NonNull final Supplier platformStateStoreFactory) { + @NonNull final Supplier stateSupplier) { super(VERSION); this.canAdopt = requireNonNull(canAdopt); this.rosterStoreFactory = requireNonNull(rosterStoreFactory); - this.platformStateStoreFactory = requireNonNull(platformStateStoreFactory); + this.stateSupplier = requireNonNull(stateSupplier); } @Override @@ -113,8 +113,7 @@ public void restart(@NonNull final MigrationContext ctx) { } else if (rosterStore.getActiveRoster() == null) { // (FUTURE) Once the roster lifecycle is active by default, remove this code building an initial // roster history from the last address book and the first roster at the upgrade boundary - final var addressBook = platformStateStoreFactory.get().getAddressBook(); - final var previousRoster = buildRoster(requireNonNull(addressBook)); + final var previousRoster = RosterRetriever.retrieveActiveOrGenesisRoster(stateSupplier.get()); rosterStore.putActiveRoster(previousRoster, 0); final var currentRoster = RosterUtils.rosterFrom(startupNetworks.migrationNetworkOrThrow()); rosterStore.putActiveRoster(currentRoster, activeRoundNumber); diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java index 1a5efe9c8157..99fa787b8c40 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/info/DiskStartupNetworksTest.java @@ -76,8 +76,6 @@ import com.hedera.pbj.runtime.io.stream.WritableStreamingData; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.roster.RosterUtils; -import com.swirlds.platform.state.service.PlatformStateService; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.ReadableRosterStoreImpl; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; @@ -343,10 +341,7 @@ private State stateContainingInfoFrom(@NonNull final Network network) { tssBaseService, PLATFORM_STATE_SERVICE, new EntityIdService(), - new RosterService( - roster -> true, - () -> new ReadablePlatformStateStore( - state.getReadableStates(PlatformStateService.NAME))), + new RosterService(roster -> true, () -> state), new AddressBookServiceImpl()) .forEach(servicesRegistry::register); final var migrator = new FakeServiceMigrator(); @@ -378,7 +373,7 @@ private void addRosterInfo(@NonNull final FakeState state, @NonNull final Networ final var rosters = writableStates.get(ROSTER_KEY); rosters.put(new ProtoBytes(currentRosterHash), currentRoster); final var rosterState = writableStates.getSingleton(ROSTER_STATES_KEY); - rosterState.put(new RosterState(Bytes.EMPTY, List.of(new RoundRosterPair(1L, currentRosterHash)))); + rosterState.put(new RosterState(Bytes.EMPTY, List.of(new RoundRosterPair(0L, currentRosterHash)))); ((CommittableWritableStates) writableStates).commit(); } diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java index 68df0a18558b..51d3113651eb 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/RosterServiceTest.java @@ -23,7 +23,7 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.node.app.roster.schemas.RosterTransplantSchema; import com.hedera.node.app.roster.schemas.V0540RosterSchema; -import com.swirlds.platform.state.service.ReadablePlatformStateStore; +import com.swirlds.state.State; import com.swirlds.state.lifecycle.Schema; import com.swirlds.state.lifecycle.SchemaRegistry; import java.util.function.Predicate; @@ -42,13 +42,13 @@ class RosterServiceTest { private Predicate canAdopt; @Mock - private Supplier platformStateStoreFactory; + private Supplier stateSupplier; private RosterService rosterService; @BeforeEach void setUp() { - rosterService = new RosterService(canAdopt, platformStateStoreFactory); + rosterService = new RosterService(canAdopt, stateSupplier); } @Test diff --git a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java index 12cfdb250b9e..0175c2eaae3b 100644 --- a/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java +++ b/hedera-node/hedera-app/src/test/java/com/hedera/node/app/roster/schemas/V0540RosterSchemaTest.java @@ -25,6 +25,8 @@ 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.doReturn; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; @@ -32,21 +34,28 @@ import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.node.state.roster.RosterState; +import com.hedera.hapi.platform.state.PlatformState; import com.hedera.node.internal.network.Network; import com.hedera.node.internal.network.NodeMetadata; +import com.swirlds.common.RosterStateId; +import com.swirlds.platform.state.service.PbjConverter; +import com.swirlds.platform.state.service.PlatformStateService; import com.swirlds.platform.state.service.ReadablePlatformStateStore; import com.swirlds.platform.state.service.WritableRosterStore; +import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.state.State; import com.swirlds.state.lifecycle.MigrationContext; import com.swirlds.state.lifecycle.StartupNetworks; import com.swirlds.state.lifecycle.StateDefinition; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; import com.swirlds.state.spi.WritableSingletonState; import com.swirlds.state.spi.WritableStates; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.Supplier; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -72,6 +81,9 @@ class V0540RosterSchemaTest { @Mock private MigrationContext ctx; + @Mock + private ReadableStates readableStates; + @Mock private WritableStates writableStates; @@ -88,11 +100,21 @@ class V0540RosterSchemaTest { private Predicate canAdopt; @Mock - private Supplier platformStateStoreFactory; + private State state; + + private State getState() { + return state; + } @Mock private ReadablePlatformStateStore platformStateStore; + @Mock + private ReadableSingletonState platformStateSingleton; + + @Mock + private PlatformState platformState; + @Mock private WritableSingletonState rosterState; @@ -100,7 +122,7 @@ class V0540RosterSchemaTest { @BeforeEach void setUp() { - subject = new V0540RosterSchema(canAdopt, rosterStoreFactory, platformStateStoreFactory); + subject = new V0540RosterSchema(canAdopt, rosterStoreFactory, this::getState); } @Test @@ -145,8 +167,24 @@ void usesAdaptedAddressBookAndMigrationRosterIfLifecycleEnabledIfApropos() { given(ctx.startupNetworks()).willReturn(startupNetworks); given(ctx.roundNumber()).willReturn(ROUND_NO); given(startupNetworks.migrationNetworkOrThrow()).willReturn(NETWORK); - given(platformStateStoreFactory.get()).willReturn(platformStateStore); - given(platformStateStore.getAddressBook()).willReturn(ADDRESS_BOOK); + + // Setup PlatformService states to return a given ADDRESS_BOOK, + // and the readable RosterService states to be empty: + doReturn(readableStates).when(state).getReadableStates(PlatformStateService.NAME); + doReturn(platformStateSingleton).when(readableStates).getSingleton(V0540PlatformStateSchema.PLATFORM_STATE_KEY); + doReturn(platformState).when(platformStateSingleton).get(); + doReturn(PbjConverter.toPbjAddressBook(ADDRESS_BOOK)) + .when(platformState) + .addressBook(); + final ReadableStates rosterReadableStates = mock(ReadableStates.class); + doReturn(rosterReadableStates).when(state).getReadableStates(RosterStateId.NAME); + final ReadableSingletonState rosterStateSingleton = mock(ReadableSingletonState.class); + doReturn(rosterStateSingleton).when(rosterReadableStates).getSingleton(RosterStateId.ROSTER_STATES_KEY); + final RosterState rosterState = mock(RosterState.class); + doReturn(rosterState).when(rosterStateSingleton).get(); + doReturn(List.of()).when(rosterState).roundRosterPairs(); + + // This is the rosterStore for when the code updates it and writes to it upon the migration: given(rosterStoreFactory.apply(writableStates)).willReturn(rosterStore); subject.restart(ctx); 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 9de541dc6002..29077dc89838 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 @@ -74,6 +74,7 @@ import com.swirlds.platform.gossip.SyncGossip; import com.swirlds.platform.pool.DefaultTransactionPool; import com.swirlds.platform.pool.TransactionPool; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.hasher.DefaultStateHasher; import com.swirlds.platform.state.hasher.StateHasher; import com.swirlds.platform.state.hashlogger.DefaultHashLogger; @@ -890,11 +891,7 @@ public IssDetector buildIssDetector() { issDetector = new DefaultIssDetector( blocks.platformContext(), - blocks.initialState() - .get() - .getState() - .getReadablePlatformState() - .getAddressBook(), + RosterUtils.buildAddressBook(blocks.rosterHistory().getCurrentRoster()), blocks.appVersion().getPbjSemanticVersion(), ignorePreconsensusSignatures, roundToIgnore); 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 9a8b4dab3f28..7b4b53bfd771 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 @@ -22,7 +22,8 @@ import com.swirlds.config.api.Configuration; import com.swirlds.config.api.ConfigurationBuilder; import com.swirlds.platform.config.DefaultConfiguration; -import com.swirlds.platform.state.PlatformStateAccessor; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.snapshot.DeserializedSignedState; import com.swirlds.platform.state.snapshot.SignedStateFileReader; @@ -30,6 +31,7 @@ import com.swirlds.platform.system.address.AddressBookUtils; import com.swirlds.platform.system.address.AddressBookValidator; import com.swirlds.platform.util.BootstrapUtils; +import com.swirlds.state.State; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -77,10 +79,9 @@ public Integer call() throws IOException, ExecutionException, InterruptedExcepti final AddressBook stateAddressBook; try (final ReservedSignedState reservedSignedState = deserializedSignedState.reservedSignedState()) { - final PlatformStateAccessor platformState = - reservedSignedState.get().getState().getReadablePlatformState(); System.out.printf("Extracting the state address book for comparison %n"); - stateAddressBook = platformState.getAddressBook(); + stateAddressBook = RosterUtils.buildAddressBook(RosterRetriever.retrieveActiveOrGenesisRoster( + (State) reservedSignedState.get().getState().getSwirldState())); } System.out.printf("Validating address book %n"); 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 7fb3962bb3bd..83a890a5f55b 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 @@ -62,10 +62,14 @@ private RosterRetriever() {} * If the active roster is missing from RosterState, * then fall back to reading an AddressBook from the PlatformState * and converting it to a Roster. + *

    + * This method may return null in case the RosterService states are not populated, + * and the PlatformService state doesn't have an AddressBook, + * which generally represents a new network genesis case. * * @return an active Roster for the round of the state, or a Roster that represents the current AddressBook in PlatformState */ - @NonNull + @Nullable public static Roster retrieveActiveOrGenesisRoster(@NonNull final State state) { final var roster = retrieveActive(state, getRound(state)); if (roster != null) { @@ -74,7 +78,7 @@ public static Roster retrieveActiveOrGenesisRoster(@NonNull final State state) { // We are currently in bootstrap for the genesis roster, which is set in the address book final var readablePlatformStateStore = new ReadablePlatformStateStore(state.getReadableStates(PlatformStateService.NAME)); - return buildRoster(requireNonNull(readablePlatformStateStore.getAddressBook())); + return buildRoster(readablePlatformStateStore.getAddressBook()); } /** 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 26c157b7d23a..7a90a51a25db 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 @@ -23,15 +23,19 @@ import com.hedera.node.internal.network.Network; import com.hedera.node.internal.network.NodeMetadata; import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.RosterStateId; import com.swirlds.common.crypto.CryptographyException; import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.crypto.CryptoStatic; import com.swirlds.platform.state.service.ReadableRosterStore; +import com.swirlds.platform.state.service.WritableRosterStore; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.util.PbjRecordHasher; import com.swirlds.state.State; +import com.swirlds.state.spi.CommittableWritableStates; +import com.swirlds.state.spi.WritableStates; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.security.cert.X509Certificate; @@ -273,6 +277,20 @@ public static RosterHistory createRosterHistory(@NonNull final ReadableRosterSto } } + /** + * Sets the active Roster in a given State. + * + * @param state a state to set a Roster in + * @param roster a Roster to set as active + * @param round a round number since which the roster is considered active + */ + public static void setActiveRoster(@NonNull final State state, @NonNull final Roster roster, final long round) { + final WritableStates writableStates = state.getWritableStates(RosterStateId.NAME); + final WritableRosterStore writableRosterStore = new WritableRosterStore(writableStates); + writableRosterStore.putActiveRoster(roster, round); + ((CommittableWritableStates) writableStates).commit(); + } + /** * Build an Address object out of a given RosterEntry object. * @deprecated To be removed once AddressBook to Roster refactoring is complete. @@ -325,13 +343,18 @@ public static Address buildAddress(@NonNull final RosterEntry entry) { /** * Build an AddressBook object out of a given Roster object. + * Returns null if the input roster is null. * @deprecated To be removed once AddressBook to Roster refactoring is complete. * @param roster a Roster * @return an AddressBook */ @Deprecated(forRemoval = true) - @NonNull - public static AddressBook buildAddressBook(@NonNull final Roster roster) { + @Nullable + public static AddressBook buildAddressBook(@Nullable final Roster roster) { + if (roster == null) { + return null; + } + AddressBook addressBook = new AddressBook(); for (final RosterEntry entry : roster.rosterEntries()) { 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 41b4643e7c80..6adf7ca86179 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 @@ -22,11 +22,14 @@ import com.swirlds.common.context.PlatformContext; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.SoftwareVersion; import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.system.address.AddressBookValidator; +import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; @@ -130,8 +133,8 @@ public AddressBookInitializer( platformContext.getConfiguration().getConfigData(AddressBookConfig.class); this.initialState = Objects.requireNonNull(initialState, "The initialState must not be null."); - this.stateAddressBook = - initialState.getState().getReadablePlatformState().getAddressBook(); + this.stateAddressBook = RosterUtils.buildAddressBook(RosterRetriever.retrieveActiveOrGenesisRoster( + (State) initialState.getState().getSwirldState())); 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/WritableRosterStore.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java index dc1b52745605..cb6da5ceed31 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/service/WritableRosterStore.java @@ -120,8 +120,9 @@ public void putActiveRoster(@NonNull final Roster roster, final long round) { if (!roundRosterPairs.isEmpty()) { final RoundRosterPair activeRosterPair = roundRosterPairs.getFirst(); if (round < 0 || round <= activeRosterPair.roundNumber()) { - throw new IllegalArgumentException( - "incoming round number must be greater than the round number of the current active roster."); + throw new IllegalArgumentException("incoming round number = " + round + + " must be greater than the round number of the current active roster = " + + activeRosterPair.roundNumber() + "."); } } final Bytes activeRosterHash = RosterUtils.hash(roster).getBytes(); 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 08adc60b523a..f684241e94a2 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 @@ -34,11 +34,15 @@ import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.TOTAL_WEIGHT; import static com.swirlds.platform.state.snapshot.SavedStateMetadataField.WALL_CLOCK_TIME; +import com.hedera.hapi.node.state.roster.Roster; import com.swirlds.common.crypto.Hash; import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.signed.SignedState; +import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.BufferedReader; @@ -168,6 +172,8 @@ public static SavedStateMetadata create( Objects.requireNonNull(now, "now must not be null"); final PlatformStateAccessor platformState = signedState.getState().getReadablePlatformState(); + final Roster roster = RosterRetriever.retrieveActiveOrGenesisRoster( + (State) signedState.getState().getSwirldState()); final List signingNodes = signedState.getSigSet().getSigningNodes(); Collections.sort(signingNodes); @@ -186,9 +192,7 @@ public static SavedStateMetadata create( selfId, signingNodes, signedState.getSigningWeight(), - platformState.getAddressBook() == null - ? 0 - : platformState.getAddressBook().getTotalWeight()); + roster == null ? 0 : RosterUtils.computeTotalWeight(roster)); } /** 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 da6a59601422..56f514211e4a 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 @@ -24,11 +24,14 @@ import com.swirlds.common.formatting.TextTable; import com.swirlds.common.platform.NodeId; import com.swirlds.platform.config.AddressBookConfig; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformStateModifier; import com.swirlds.platform.state.address.AddressBookInitializer; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.system.SoftwareVersion; +import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.text.ParseException; @@ -273,8 +276,8 @@ public static ServiceEndpoint endpointFor(@NonNull final String host, final int } // 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(); + final AddressBook addressBook = RosterUtils.buildAddressBook(RosterRetriever.retrieveActiveOrGenesisRoster( + (State) initialState.get().getState().getSwirldState())); 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/test/java/com/swirlds/platform/AddressBookInitializerTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/AddressBookInitializerTest.java index 613376d544b9..78e975864eb0 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 @@ -30,7 +30,9 @@ import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; +import com.hedera.hapi.node.state.roster.Roster; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.io.utility.FileUtils; import com.swirlds.common.platform.NodeId; @@ -38,6 +40,8 @@ import com.swirlds.common.test.fixtures.platform.TestPlatformContextBuilder; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.AddressBookConfig_; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.address.AddressBookInitializer; @@ -47,6 +51,8 @@ import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.roster.RosterServiceStateMock; +import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; import java.io.File; @@ -197,11 +203,11 @@ void currentVersionEqualsStateVersion() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); - assertEquals( + assertEqualsAsRosters( signedState.getAddressBook(), inititializedAddressBook, "The initial address book must equal the state address book."); - assertEquals( + assertEqualsAsRosters( null, initializer.getPreviousAddressBook(), "When there is no upgrade, the address book should not change"); @@ -211,6 +217,20 @@ void currentVersionEqualsStateVersion() throws IOException { assertFalse(initializer.hasAddressBookChanged()); } + /** + * Compare AddressBooks after converting them to rosters to exclude fields unsupported by the Roster + * (memo, selfName, agreeCert, etc.) + * + * @param expected expected AddressBook + * @param actual actual AddressBook + * @param message error message + */ + void assertEqualsAsRosters(final AddressBook expected, final AddressBook actual, final String message) { + final Roster expectedRoster = RosterRetriever.buildRoster(expected); + final Roster actualRoster = RosterRetriever.buildRoster(actual); + assertEquals(expectedRoster, actualRoster, message); + } + @Test @DisplayName("Version upgrade, SwirldState set 0 weight.") void versionUpgradeSwirldStateZeroWeight() throws IOException { @@ -376,13 +396,16 @@ private SignedState getMockSignedState( when(signedState.getSwirldState()).thenReturn(swirldState); final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); when(platformState.getCreationSoftwareVersion()).thenReturn(softwareVersion); - when(platformState.getAddressBook()).thenReturn(currentAddressBook); - when(platformState.getPreviousAddressBook()).thenReturn(previousAddressBook); + final Roster currentRoster = RosterRetriever.buildRoster(currentAddressBook); + RosterServiceStateMock.setup( + (State) swirldState, currentRoster, 1L, RosterRetriever.buildRoster(previousAddressBook)); final MerkleRoot state = mock(MerkleRoot.class); when(state.getReadablePlatformState()).thenReturn(platformState); + when(state.getSwirldState()).thenReturn(swirldState); when(signedState.getState()).thenReturn(state); when(signedState.isGenesisState()).thenReturn(fromGenesis); - when(signedState.getAddressBook()).thenReturn(currentAddressBook); + // clean up fields unsupported by the Roster (selfName, etc.) by re-building the AddressBook: + when(signedState.getAddressBook()).thenReturn(RosterUtils.buildAddressBook(currentRoster)); return signedState; } @@ -395,7 +418,7 @@ private SignedState getMockSignedState( private Supplier getMockSwirldStateSupplier(int scenario) { final AtomicReference configAddressBook = new AtomicReference<>(); - final SwirldState swirldState = mock(SwirldState.class); + final SwirldState swirldState = mock(SwirldState.class, withSettings().extraInterfaces(State.class)); final OngoingStubbing stub = when(swirldState.updateWeight( argThat(confAB -> { @@ -514,9 +537,9 @@ private void assertAddressBookFileContent( // check usedAddressBook content String usedText = USED_ADDRESS_BOOK_HEADER + "\n"; - if (usedAddressBook == configAddressBook) { + if (usedAddressBook.equals(configAddressBook)) { usedText += CONFIG_ADDRESS_BOOK_USED; - } else if (usedAddressBook == stateAddressBook) { + } else if (usedAddressBook.equals(stateAddressBook)) { usedText += STATE_ADDRESS_BOOK_USED; } else { usedText += usedAddressBook.toConfigText(); 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 3ca6cb49546b..66d513611c38 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 @@ -37,7 +37,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; +import com.hedera.hapi.node.state.roster.Roster; import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.RandomUtils; @@ -50,7 +52,9 @@ import com.swirlds.platform.state.snapshot.SavedStateMetadataField; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.SwirldState; +import com.swirlds.platform.test.fixtures.roster.RosterServiceStateMock; +import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; import java.nio.file.Files; @@ -210,11 +214,14 @@ void signingNodesSortedTest() { final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); when(platformState.getLegacyRunningEventHash()).thenReturn(randomHash(random)); when(platformState.getSnapshot()).thenReturn(mock(ConsensusSnapshot.class)); - final AddressBook addressBook = mock(AddressBook.class); + + final Roster roster = mock(Roster.class); + final State theState = mock(State.class, withSettings().extraInterfaces(SwirldState.class)); + when(state.getSwirldState()).thenReturn((SwirldState) theState); + RosterServiceStateMock.setup(theState, roster); when(signedState.getState()).thenReturn(state); when(state.getReadablePlatformState()).thenReturn(platformState); - when(platformState.getAddressBook()).thenReturn(addressBook); when(signedState.getSigSet()).thenReturn(sigSet); when(sigSet.getSigningNodes()) .thenReturn(new ArrayList<>(List.of(NodeId.of(3L), NodeId.of(1L), NodeId.of(2L)))); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java index 78af965fc10d..b611d823dfff 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/SignedStateFileReadWriteTest.java @@ -146,6 +146,7 @@ void writeThenReadStateFileTest() throws IOException { assertTrue(exists(stateFile), "signed state file should be present"); assertTrue(exists(signatureSetFile), "signature set file should be present"); + MerkleDb.resetDefaultInstancePath(); final DeserializedSignedState deserializedSignedState = readStateFile(TestPlatformContextBuilder.create().build().getConfiguration(), stateFile); MerkleCryptoFactory.getInstance() diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java index a32832d3eb1c..895e1571fa18 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/StateFileManagerTests.java @@ -157,6 +157,7 @@ private void validateSavingOfState(final SignedState originalState, final Path s assertEquals(-1, originalState.getReservationCount(), "invalid reservation count"); + MerkleDb.resetDefaultInstancePath(); final DeserializedSignedState deserializedSignedState = readStateFile(TestPlatformContextBuilder.create().build().getConfiguration(), stateFile); MerkleCryptoFactory.getInstance() @@ -272,7 +273,10 @@ void sequenceOfStatesTest(final boolean startAtGenesis) throws IOException { .withConfiguration(configBuilder.getOrCreateConfig()) .build(); - final int totalStates = 100; + // Each state now has a VirtualMap for ROSTERS, and each VirtualMap consumes a lot of RAM. + // So one cannot keep too many VirtualMaps in memory at once, or OOMs pop up. + // Therefore, the number of states this test can use at once should be reasonably small: + final int totalStates = 10; final int averageTimeBetweenStates = 10; final double standardDeviationTimeBetweenStates = 0.5; 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 f89eed926070..6d18a08f717e 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 @@ -122,10 +122,15 @@ private static Stream randomizedNodeParams() { final List signingNodes = getRandomizedSigningNodes(r, nodes); final long validSigningWeight = getValidSignatureWeight(signingNodes); final long totalWeight = getTotalWeight(nodes); - final String desc = String.format( - "\nseed: %sL:, valid signing weight: %s, total weight: %s\n", - seed, validSigningWeight, totalWeight); - arguments.add(Arguments.of(desc, nodes, signingNodes)); + + // A Roster object is considered invalid if it has a total weight of zero + // because such a Roster is practically unusable. Therefore, we don't test it. + if (totalWeight != 0L) { + final String desc = String.format( + "\nseed: %sL:, valid signing weight: %s, total weight: %s\n", + seed, validSigningWeight, totalWeight); + arguments.add(Arguments.of(desc, nodes, signingNodes)); + } } return arguments.stream(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java index 5f170a9a39a7..1d2ae718ab26 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/reconnect/ReconnectTest.java @@ -40,7 +40,6 @@ import com.swirlds.platform.network.Connection; import com.swirlds.platform.network.SocketConnection; import com.swirlds.platform.state.MerkleRoot; -import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateValidator; import com.swirlds.platform.system.address.AddressBook; @@ -144,8 +143,8 @@ private void executeReconnect(final ReconnectMetrics reconnectMetrics) throws In final Thread thread = new Thread(() -> { try { + signedState.reserve("test"); final ReconnectTeacher sender = buildSender( - signedState.reserve("test"), new DummyConnection( platformContext, pairedStreams.getTeacherInput(), pairedStreams.getTeacherOutput()), reconnectMetrics); @@ -161,10 +160,7 @@ private void executeReconnect(final ReconnectMetrics reconnectMetrics) throws In } } - private ReconnectTeacher buildSender( - final ReservedSignedState signedState, - final SocketConnection connection, - final ReconnectMetrics reconnectMetrics) + private ReconnectTeacher buildSender(final SocketConnection connection, final ReconnectMetrics reconnectMetrics) throws IOException { final PlatformContext platformContext = 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 e7f9bafba016..0165713bc1ed 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 @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import com.hedera.hapi.node.base.SemanticVersion; -import com.swirlds.common.constructable.ClassConstructorPair; import com.swirlds.common.constructable.ConstructableRegistry; import com.swirlds.common.constructable.ConstructableRegistryException; import com.swirlds.common.crypto.Hash; @@ -31,7 +30,6 @@ import com.swirlds.common.utility.RuntimeObjectRegistry; import com.swirlds.platform.system.BasicSoftwareVersion; import com.swirlds.platform.system.SoftwareVersion; -import com.swirlds.platform.test.fixtures.state.BlockingSwirldState; import java.io.IOException; import java.nio.file.Path; import java.util.LinkedList; @@ -63,7 +61,6 @@ class StateRegistryTests { static void setUp() throws ConstructableRegistryException { registry = ConstructableRegistry.getInstance(); version = SemanticVersion.newBuilder().major(nextInt(1, 100)).build(); - registry.registerConstructable(new ClassConstructorPair(BlockingSwirldState.class, BlockingSwirldState::new)); registerMerkleStateRootClassIds(); } 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 c622fa801c07..11c304751da0 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 @@ -335,7 +335,10 @@ 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().getWritablePlatformState().setAddressBook(updatedAddressBook); + // We cannot really update the AddressBook/Roster in an already signed state because it's already hashed and + // immutable. + // However, we can call a version of the `SignedState.pruneInvalidSignatures()` method that accepts a random + // AddressBook instead. // Tamper with a node's signature final long weightWithModifiedSignature = nodes.get(1).getWeight(); @@ -343,7 +346,7 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { tamperedBytes[0] = 0; when(signatures.get(1).getBytes()).thenReturn(Bytes.wrap(tamperedBytes)); - signedState.pruneInvalidSignatures(); + signedState.pruneInvalidSignatures(updatedAddressBook); assertEquals(signaturesAdded.size() - 2, sigSet.size()); assertEquals( 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 cc42a4cac511..09d5beadc45e 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 @@ -25,6 +25,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.mockito.Mockito.withSettings; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.merkle.MerkleNode; @@ -38,7 +39,8 @@ import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.system.address.AddressBook; +import com.swirlds.platform.system.SwirldState; +import com.swirlds.state.State; import java.util.ArrayList; import java.util.List; import org.apache.logging.log4j.Logger; @@ -148,20 +150,17 @@ private ReservedSignedState createSignedState(final long round) { final MerkleNode merkleNode = MerkleTestUtils.buildLessSimpleTree(); MerkleCryptoFactory.getInstance().digestTreeSync(merkleNode); final SignedState signedState = mock(SignedState.class); - final MerkleRoot state = mock(MerkleRoot.class); + final MerkleRoot state = mock(MerkleRoot.class, withSettings().extraInterfaces(State.class, SwirldState.class)); final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); - final AddressBook addressBook = new AddressBook(); - addressBook.setHash(merkleNode.getHash()); - when(platformState.getRound()).thenReturn(round); - when(platformState.getAddressBook()).thenReturn(addressBook); when(state.getReadablePlatformState()).thenReturn(platformState); when(state.getRoute()).thenReturn(merkleNode.getRoute()); when(state.getHash()).thenReturn(merkleNode.getHash()); when(signedState.getState()).thenReturn(state); + when(state.getSwirldState()).thenReturn((SwirldState) state); when(signedState.getRound()).thenReturn(round); ReservedSignedState reservedSignedState = mock(ReservedSignedState.class); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java index 8f22bb76f1c1..60b562c90108 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/signed/StartupStateUtilsTests.java @@ -197,6 +197,7 @@ void normalRestartTest() throws IOException, SignedStateLoadingException { } final RecycleBin recycleBin = initializeRecycleBin(platformContext, selfId); + MerkleDb.resetDefaultInstancePath(); final SignedState loadedState = StartupStateUtils.loadStateFile( platformContext.getConfiguration(), recycleBin, @@ -211,6 +212,7 @@ void normalRestartTest() throws IOException, SignedStateLoadingException { assertEquals(latestState.getRound(), loadedState.getRound()); assertEquals(latestState.getState().getHash(), loadedState.getState().getHash()); + RandomSignedStateGenerator.releaseReservable(loadedState.getState()); } @Test @@ -271,7 +273,9 @@ void corruptedStateRecyclingPermittedTest(final int invalidStateCount) latestUncorruptedState = state; } } + RandomSignedStateGenerator.releaseAllBuiltSignedStates(); + MerkleDb.resetDefaultInstancePath(); final SignedState loadedState = StartupStateUtils.loadStateFile( platformContext.getConfiguration(), recycleBin, @@ -293,6 +297,10 @@ void corruptedStateRecyclingPermittedTest(final int invalidStateCount) assertNull(loadedState); } + if (loadedState != null) { + RandomSignedStateGenerator.releaseReservable(loadedState.getState()); + } + final Path savedStateDirectory = signedStateFilePath .getSignedStateDirectory(mainClassName, selfId, swirldName, latestRound) .getParent(); diff --git a/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/roster/RosterServiceStateMock.java b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/roster/RosterServiceStateMock.java new file mode 100644 index 000000000000..42969f46ac46 --- /dev/null +++ b/platform-sdk/swirlds-platform-core/src/testFixtures/java/com/swirlds/platform/test/fixtures/roster/RosterServiceStateMock.java @@ -0,0 +1,129 @@ +/* + * 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.test.fixtures.roster; + +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import com.hedera.hapi.node.state.primitives.ProtoBytes; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterState; +import com.hedera.hapi.node.state.roster.RoundRosterPair; +import com.hedera.hapi.platform.state.ConsensusSnapshot; +import com.hedera.hapi.platform.state.PlatformState; +import com.hedera.pbj.runtime.io.buffer.Bytes; +import com.swirlds.common.RosterStateId; +import com.swirlds.platform.roster.RosterUtils; +import com.swirlds.platform.state.service.PlatformStateService; +import com.swirlds.platform.state.service.schemas.V0540PlatformStateSchema; +import com.swirlds.state.State; +import com.swirlds.state.spi.ReadableKVState; +import com.swirlds.state.spi.ReadableSingletonState; +import com.swirlds.state.spi.ReadableStates; +import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.Nullable; +import java.util.ArrayList; +import java.util.List; + +/** + * A utility class to help set up mock states with given current/previous rosters. + */ +public final class RosterServiceStateMock { + private RosterServiceStateMock() {} + + /** + * A helper version of the setup() method that configures the currentRoster only + * which becomes active at round zero. See the doc for the complete setup() + * method below for more information. + * + * @param stateMock a mock of the State interface + * @param currentRoster a Roster to be active since round zero + */ + public static void setup(final State stateMock, final Roster currentRoster) { + setup(stateMock, currentRoster, 0, null); + } + + /** + * Configures the provided State mock with the currentRoster starting at the given round, + * and optionally with the previousRoster (if not null) starting at round zero. + *

    + * This method properly configures the RosterService states to provide proper RosterHistory + * and have the roster(s) in the RosterMap. It also configures the ConsensusSnapshot mock + * in the PlatformState to return the given round number. + *

    + * If the previousRoster is not null, then the given round number must be greater than zero + * because it's assumed that the previousRoster is active since the round zero, + * and the current roster must have a round number greater than that. + *

    + * To support tests that verify the behavior at genesis when no roster history exists yet, + * the currentRoster may also be null. Normally, the previousRoster would also be null + * in this case (although the method won't prevent one from specifying a non-null value), + * which would normally result in an empty roster history. + * + * @param stateMock a mock of the State interface + * @param currentRoster a Roster to be currently active, may be null + * @param round a round number since which the currentRoster is active + * @param previousRoster an optional Roster to be the previousRoster, active since round zero + */ + public static void setup( + @NonNull final State stateMock, + @Nullable final Roster currentRoster, + final long round, + @Nullable final Roster previousRoster) { + final ReadableStates readableStates = mock(ReadableStates.class); + when(stateMock.getReadableStates(RosterStateId.NAME)).thenReturn(readableStates); + final ReadableKVState rosterMap = mock(ReadableKVState.class); + when(readableStates.get(RosterStateId.ROSTER_KEY)).thenReturn(rosterMap); + + List roundRosterPairs = new ArrayList<>(); + + if (currentRoster != null) { + final Bytes rosterHash = RosterUtils.hash(currentRoster).getBytes(); + when(rosterMap.get(eq(new ProtoBytes(rosterHash)))).thenReturn(currentRoster); + roundRosterPairs.add(new RoundRosterPair(round, rosterHash)); + } + + if (previousRoster != null) { + if (round <= 0L) { + throw new IllegalArgumentException( + "With a non-null previousRoster, the round number for the currentRoster must be greater than zero: previousRoster=" + + Roster.JSON.toJSON(previousRoster)); + } + final Bytes previousRosterHash = RosterUtils.hash(previousRoster).getBytes(); + when(rosterMap.get(eq(new ProtoBytes(previousRosterHash)))).thenReturn(previousRoster); + roundRosterPairs.add(new RoundRosterPair(0, previousRosterHash)); + } + + final RosterState rosterState = new RosterState(Bytes.EMPTY, roundRosterPairs); + final ReadableSingletonState rosterReadableState = mock(ReadableSingletonState.class); + when(readableStates.getSingleton(RosterStateId.ROSTER_STATES_KEY)) + .thenReturn(rosterReadableState); + when(rosterReadableState.get()).thenReturn(rosterState); + + final ReadableSingletonState platformReadableState = mock(ReadableSingletonState.class); + final PlatformState platformState = mock(PlatformState.class); + when(stateMock.getReadableStates(PlatformStateService.NAME)).thenReturn(readableStates); + when(readableStates.getSingleton(V0540PlatformStateSchema.PLATFORM_STATE_KEY)) + .thenReturn(platformReadableState); + when(platformReadableState.get()).thenReturn(platformState); + + final ConsensusSnapshot consensusSnapshot = mock(ConsensusSnapshot.class); + when(consensusSnapshot.round()).thenReturn(round); + when(platformState.consensusSnapshot()).thenReturn(consensusSnapshot); + } +} 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 1ad15bfc1495..e5537a738c89 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 @@ -114,9 +114,7 @@ public boolean equals(final Object obj) { if (!(obj instanceof final BlockingSwirldState that)) { return false; } - return Objects.equals( - this.getReadablePlatformState().getAddressBook(), - that.getReadablePlatformState().getAddressBook()); + return Objects.equals(this.getReadablePlatformState(), that.getReadablePlatformState()); } /** 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 5f91a5db6bc4..bce9d4fd6a86 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 @@ -22,7 +22,9 @@ import static com.swirlds.platform.test.fixtures.state.FakeMerkleStateLifecycles.FAKE_MERKLE_STATE_LIFECYCLES; import static com.swirlds.platform.test.fixtures.state.FakeMerkleStateLifecycles.registerMerkleStateRootClassIds; +import com.hedera.hapi.node.state.roster.Roster; import com.swirlds.base.time.Time; +import com.swirlds.common.Reservable; import com.swirlds.common.context.PlatformContext; import com.swirlds.common.crypto.Hash; import com.swirlds.common.crypto.Signature; @@ -35,6 +37,8 @@ import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.consensus.ConsensusSnapshot; import com.swirlds.platform.crypto.SignatureVerifier; +import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.MinimumJudgeInfo; import com.swirlds.platform.state.PlatformMerkleStateRoot; @@ -45,6 +49,7 @@ import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; import com.swirlds.platform.test.fixtures.state.manager.SignatureVerificationTestUtils; +import com.swirlds.state.State; import com.swirlds.state.merkle.MerkleStateRoot; import edu.umd.cs.findbugs.annotations.NonNull; import java.time.Instant; @@ -125,6 +130,7 @@ public SignedState build() { } else { addressBookInstance = addressBook; } + final Roster rosterInstance = RosterRetriever.buildRoster(addressBookInstance); final SoftwareVersion softwareVersionInstance; if (softwareVersion == null) { @@ -200,7 +206,6 @@ public SignedState build() { platformState.bulkUpdate(v -> { v.setSnapshot(consensusSnapshotInstance); - v.setAddressBook(addressBookInstance); v.setLegacyRunningEventHash(legacyRunningEventHashInstance); v.setCreationSoftwareVersion(softwareVersionInstance); v.setRoundsNonAncient(roundsNonAncientInstance); @@ -208,7 +213,7 @@ public SignedState build() { }); FAKE_MERKLE_STATE_LIFECYCLES.initRosterState((MerkleStateRoot) stateInstance); - // Future Work: populate roster history and stop setting AddressBook on platformState above. + RosterUtils.setActiveRoster((State) stateInstance, rosterInstance, roundInstance); if (signatureVerifier == null) { signatureVerifier = SignatureVerificationTestUtils::verifySignature; @@ -463,15 +468,23 @@ public RandomSignedStateGenerator setUseBlockingState(boolean useBlockingState) return this; } + /** + * Keep calling release() on a given Reservable until it's completely released. + * @param reservable a reservable to release + */ + public static void releaseReservable(@NonNull final Reservable reservable) { + while (reservable.getReservationCount() >= 0) { + reservable.release(); + } + } + /** * Release all the SignedState objects built by this generator on the current thread, * and then clear the list of built states. */ public static void releaseAllBuiltSignedStates() { builtSignedStates.get().forEach(signedState -> { - while (signedState.getState().getReservationCount() >= 0) { - signedState.getState().release(); - } + releaseReservable(signedState.getState()); }); builtSignedStates.get().clear(); } diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesReplayerTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesReplayerTests.java index f2bd0fe00e40..2d8f8598e387 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesReplayerTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/event/preconsensus/PcesReplayerTests.java @@ -17,6 +17,7 @@ package com.swirlds.platform.test.event.preconsensus; import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyEquals; +import static com.swirlds.common.test.fixtures.AssertionUtils.assertEventuallyTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -182,7 +183,11 @@ void testRateLimitedOperation() { "Event count should have increased from %s to %s".formatted(i - 1, i)); } - assertTrue(flushIntakeCalled.get()); - assertTrue(flushTransactionHandlingCalled.get()); + assertEventuallyTrue( + () -> flushIntakeCalled.get(), Duration.ofSeconds(1), "Flush intake should have been called"); + assertEventuallyTrue( + () -> flushTransactionHandlingCalled.get(), + Duration.ofSeconds(1), + "Flush transaction handling should have been called"); } } From 7ffc174967339cdead67e607bbc11229101368dd Mon Sep 17 00:00:00 2001 From: timfn-hg Date: Tue, 17 Dec 2024 17:27:36 -0600 Subject: [PATCH 17/39] chore: replace AddressBook with Roster in SignedState (#17076) Signed-off-by: Tim Farber-Newman --- .../platform/reconnect/ReconnectTeacher.java | 3 +- .../recovery/EventRecoveryWorkflow.java | 3 +- .../recovery/internal/RecoveryPlatform.java | 6 +- .../platform/roster/RosterRetriever.java | 1 + .../swirlds/platform/roster/RosterUtils.java | 25 +- .../platform/state/signed/SignedState.java | 151 +++----- .../state/signed/SignedStateInfo.java | 9 +- .../snapshot/DefaultStateSnapshotManager.java | 9 +- .../state/snapshot/SignedStateFileWriter.java | 3 +- .../platform/AddressBookInitializerTest.java | 92 ++--- .../platform/state/StateSigningTests.java | 338 ++++++++---------- .../AbstractStateSignatureCollectorTest.java | 15 +- 12 files changed, 319 insertions(+), 336 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectTeacher.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectTeacher.java index 671f1df76dd7..1c6709f60702 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectTeacher.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/reconnect/ReconnectTeacher.java @@ -33,6 +33,7 @@ import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.metrics.ReconnectMetrics; import com.swirlds.platform.network.Connection; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.SignedState; import edu.umd.cs.findbugs.annotations.NonNull; import java.io.IOException; @@ -249,7 +250,7 @@ private void sendSignatures(final SignedState signedState) throws IOException { sb.append(" (signing weight = ") .append(signedState.getSigningWeight()) .append("/") - .append(signedState.getAddressBook().getTotalWeight()) + .append(RosterUtils.computeTotalWeight(signedState.getRoster())) .append(") for state hash ") .append(signedState.getState().getHash()); 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 11f872b9d2ab..5acb363bb1fe 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 @@ -52,7 +52,6 @@ import com.swirlds.platform.recovery.internal.RecoveredState; import com.swirlds.platform.recovery.internal.RecoveryPlatform; import com.swirlds.platform.recovery.internal.StreamedRound; -import com.swirlds.platform.roster.RosterRetriever; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.PlatformStateAccessor; import com.swirlds.platform.state.PlatformStateModifier; @@ -164,7 +163,7 @@ public static void recoverState( logger.info(STARTUP.getMarker(), "Loading event stream at {}", eventStreamDirectory); final IOIterator roundIterator = new EventStreamRoundIterator( - RosterRetriever.buildRoster(initialState.get().getAddressBook()), + initialState.get().getRoster(), eventStreamDirectory, initialState.get().getRound() + 1, allowPartialRounds); diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java index 9203874ea70d..ea6a54b25650 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/recovery/internal/RecoveryPlatform.java @@ -29,7 +29,7 @@ import com.swirlds.config.api.Configuration; import com.swirlds.platform.crypto.KeysAndCerts; import com.swirlds.platform.crypto.PlatformSigner; -import com.swirlds.platform.roster.RosterRetriever; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateReference; @@ -74,8 +74,8 @@ public RecoveryPlatform( Objects.requireNonNull(initialState, "initialState must not be null"); this.selfId = Objects.requireNonNull(selfId, "selfId must not be null"); - this.addressBook = initialState.getAddressBook(); - this.roster = RosterRetriever.buildRoster(addressBook); + this.roster = initialState.getRoster(); + this.addressBook = RosterUtils.buildAddressBook(this.roster); if (loadSigningKeys) { keysAndCerts = initNodeSecurity(addressBook, configuration).get(selfId); 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 83a890a5f55b..52f9521186be 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 @@ -245,6 +245,7 @@ public static Roster buildRoster(@Nullable final AddressBook addressBook) { if (addressBook == null) { return null; } + return Roster.newBuilder() .rosterEntries(addressBook.getNodeIdSet().stream() .map(addressBook::getAddress) 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 7a90a51a25db..ee547c7e2347 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 @@ -43,6 +43,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -203,12 +204,34 @@ public static long computeTotalWeight(@NonNull final Roster roster) { * @throws RosterEntryNotFoundException if RosterEntry is not found in Roster */ public static RosterEntry getRosterEntry(@NonNull final Roster roster, final long nodeId) { + final RosterEntry entry = getRosterEntryOrNull(roster, nodeId); + if (entry != null) { + return entry; + } + + throw new RosterEntryNotFoundException("No RosterEntry with nodeId: " + nodeId + " in Roster: " + roster); + } + + /** + * Retrieves the roster entry that matches the specified node ID, returning null if one does not exist. + *

    + * Useful for one-off look-ups. If code needs to look up multiple entries by NodeId, then the code should use the + * {@link #toMap(Roster)} method and keep the map instance for the look-ups. + * + * @param roster the roster to search + * @param nodeId the ID of the node to retrieve + * @return the found roster entry that matches the specified node ID, else null + */ + public static RosterEntry getRosterEntryOrNull(@NonNull final Roster roster, final long nodeId) { + Objects.requireNonNull(roster, "roster"); + for (final RosterEntry entry : roster.rosterEntries()) { if (entry.nodeId() == nodeId) { return entry; } } - throw new RosterEntryNotFoundException("No RosterEntry with nodeId: " + nodeId + " in Roster: " + roster); + + return null; } /** 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 2058742e2baa..4b2f9e462f96 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 @@ -23,6 +23,7 @@ import static com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction.CREATION; import static com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction.RELEASE; import static com.swirlds.platform.state.signed.SignedStateHistory.SignedStateAction.RESERVE; +import static java.util.Objects.requireNonNull; import com.hedera.hapi.node.state.roster.Roster; import com.hedera.hapi.node.state.roster.RosterEntry; @@ -43,7 +44,6 @@ import com.swirlds.platform.state.snapshot.StateToDiskReason; import com.swirlds.platform.system.SwirldState; import com.swirlds.platform.system.address.Address; -import com.swirlds.platform.system.address.AddressBook; import com.swirlds.state.merkle.MerkleStateRoot; import edu.umd.cs.findbugs.annotations.NonNull; import edu.umd.cs.findbugs.annotations.Nullable; @@ -196,7 +196,7 @@ public SignedState( state.reserve(); - this.signatureVerifier = Objects.requireNonNull(signatureVerifier); + this.signatureVerifier = requireNonNull(signatureVerifier); this.state = state; final StateConfig stateConfig = configuration.getConfigData(StateConfig.class); @@ -246,14 +246,16 @@ public boolean isGenesisState() { * @param sigSet the signatures to be attached to this signed state */ public void setSigSet(@NonNull final SigSet sigSet) { - this.sigSet = Objects.requireNonNull(sigSet); + this.sigSet = requireNonNull(sigSet); signingWeight = 0; if (!isGenesisState()) { // Only non-genesis states will have signing weight - final AddressBook addressBook = getAddressBook(); + final Map entries = RosterUtils.toMap(getRoster()); + for (final NodeId signingNode : sigSet) { - if (addressBook.contains(signingNode)) { - signingWeight += addressBook.getAddress(signingNode).getWeight(); + final RosterEntry entry = entries.get(signingNode.id()); + if (entry != null) { + signingWeight += entry.weight(); } } } @@ -263,11 +265,14 @@ public void setSigSet(@NonNull final SigSet sigSet) { * {@inheritDoc} */ @Override - public @NonNull AddressBook getAddressBook() { - return Objects.requireNonNull( - RosterUtils.buildAddressBook(RosterRetriever.retrieveActiveOrGenesisRoster( - (MerkleStateRoot) getState().getSwirldState())), - "address book stored in this signed state is null, this should never happen"); + public @NonNull Roster getRoster() { + /* + Ideally the roster would be captured in the constructor but due to the mutable underlying state, the roster + can change from underneath us. Therefore, the roster must be regenerated on each access. + */ + final Roster roster = RosterRetriever.retrieveActiveOrGenesisRoster( + (MerkleStateRoot) getState().getSwirldState()); + return requireNonNull(roster, "Roster stored in signed state is null (this should never happen)"); } /** @@ -454,7 +459,7 @@ public int hashCode() { @Override public String toString() { return "SS(round: %d, sigs: %d/%s, hash: %s)" - .formatted(getRound(), signingWeight, getAddressBook().getTotalWeight(), state.getHash()); + .formatted(getRound(), signingWeight, RosterUtils.computeTotalWeight(getRoster()), state.getHash()); } /** @@ -566,8 +571,7 @@ public boolean isVerifiable() { * @return true if this state is signed by the threshold, false otherwise */ private boolean signedBy(@NonNull final Threshold threshold) { - return Objects.requireNonNull(threshold) - .isSatisfiedBy(signingWeight, getAddressBook().getTotalWeight()); + return requireNonNull(threshold).isSatisfiedBy(signingWeight, RosterUtils.computeTotalWeight(getRoster())); } /** @@ -581,7 +585,7 @@ public void throwIfNotVerifiable() { throw new SignedStateInvalidException( "Signed state lacks sufficient valid signatures. This state has " + sigSet.size() + " valid signatures representing " + signingWeight + "/" - + getAddressBook().getTotalWeight() + " weight"); + + RosterUtils.computeTotalWeight(getRoster()) + " weight"); } } @@ -594,7 +598,34 @@ public void throwIfNotVerifiable() { * state is either not complete or was previously complete prior to this signature */ public boolean addSignature(@NonNull final NodeId nodeId, @NonNull final Signature signature) { - return addSignature(getAddressBook(), nodeId, signature); + requireNonNull(nodeId, "nodeId"); + requireNonNull(signature, "signature"); + + if (isComplete()) { + // No need to add more signatures + return false; + } + + final RosterEntry rosterEntry = RosterUtils.getRosterEntryOrNull(getRoster(), nodeId.id()); + + if (rosterEntry == null) { + // we ignore signatures from nodes no longer in the roster + return false; + } + + if (!isSignatureValid(rosterEntry, signature)) { + return false; + } + + if (sigSet.hasSignature(nodeId)) { + // we already have this signature + return false; + } + + sigSet.addSignature(nodeId, signature); + signingWeight += rosterEntry.weight(); + + return isComplete(); } /** @@ -636,99 +667,32 @@ private boolean isSignatureValid(@Nullable final Address address, @NonNull final */ private boolean isSignatureValid(@Nullable final RosterEntry rosterEntry, @NonNull final Signature signature) { if (rosterEntry == null) { + // Signing node is not in the roster. return false; } if (rosterEntry.weight() == 0) { + // Signing node has no weight. return false; } - X509Certificate cert = RosterUtils.fetchGossipCaCertificate(rosterEntry); + final X509Certificate cert = RosterUtils.fetchGossipCaCertificate(rosterEntry); if (cert == null) { + // If the address does not have a valid public key, the signature is invalid. + // https://github.com/hashgraph/hedera-services/issues/16648 return false; } return signatureVerifier.verifySignature(state.getHash().getBytes(), signature.getBytes(), cert.getPublicKey()); } - /** - * Add a signature to the sigset if the signature is valid. - * - * @param addressBook use this address book to determine if the signature is valid or not - * @param nodeId the ID of the signing node - * @param signature the signature to add - * @return true if the signed state is now complete as a result of the signature being added, false if the signed - * state is either not complete or was previously complete prior to this signature - */ - private boolean addSignature( - @NonNull final AddressBook addressBook, @NonNull final NodeId nodeId, @NonNull final Signature signature) { - Objects.requireNonNull(addressBook, "addressBook"); - Objects.requireNonNull(nodeId, "nodeId"); - Objects.requireNonNull(signature, "signature"); - - if (isComplete()) { - // No need to add more signatures - return false; - } - - if (!addressBook.contains(nodeId)) { - // we can ignore signatures from nodes no longer in the address book - return false; - } - - final Address address = addressBook.getAddress(nodeId); - if (!isSignatureValid(address, signature)) { - return false; - } - - if (sigSet.hasSignature(address.getNodeId())) { - // We already have this signature. - return false; - } - - sigSet.addSignature(nodeId, signature); - signingWeight += address.getWeight(); - - return isComplete(); - } - /** * Remove all invalid signatures from a signed state. Uses the address book in the state when judging the validity * of signatures. */ public void pruneInvalidSignatures() { - pruneInvalidSignatures(getAddressBook()); - } - - /** - * Remove all invalid signatures from a signed state. - * - * @param trustedAddressBook use this address book to determine signature validity instead of the one inside the - * signed state. Useful if validating signed states from untrusted sources. - */ - public void pruneInvalidSignatures(@NonNull final AddressBook trustedAddressBook) { - Objects.requireNonNull(trustedAddressBook); - - final List signaturesToRemove = new ArrayList<>(); - for (final NodeId nodeId : sigSet) { - final Address address = trustedAddressBook.contains(nodeId) ? trustedAddressBook.getAddress(nodeId) : null; - if (!isSignatureValid(address, sigSet.getSignature(nodeId))) { - signaturesToRemove.add(nodeId); - } - } - - for (final NodeId nodeId : signaturesToRemove) { - sigSet.removeSignature(nodeId); - } - - // Recalculate signing weight. We should do this even if we don't remove signatures. - signingWeight = 0; - for (final NodeId nodeId : sigSet) { - if (trustedAddressBook.contains(nodeId)) { - signingWeight += trustedAddressBook.getAddress(nodeId).getWeight(); - } - } + pruneInvalidSignatures(getRoster()); } /** @@ -738,7 +702,7 @@ public void pruneInvalidSignatures(@NonNull final AddressBook trustedAddressBook * state. (Useful if validating signed states from untrusted sources.) */ public void pruneInvalidSignatures(@NonNull final Roster trustedRoster) { - Objects.requireNonNull(trustedRoster); + requireNonNull(trustedRoster); final Map entriesByNodeId = RosterUtils.toMap(trustedRoster); final List signaturesToRemove = new ArrayList<>(); @@ -754,16 +718,15 @@ public void pruneInvalidSignatures(@NonNull final Roster trustedRoster) { sigSet.removeSignature(nodeId); } - long newWeight = 0; + // Recalculate signing weight. We should do this even if we don't remove signatures. + signingWeight = 0; for (final NodeId nodeId : sigSet) { final RosterEntry entry = entriesByNodeId.get(nodeId.id()); if (entry != null) { - newWeight += entry.weight(); + signingWeight += entry.weight(); } } - - signingWeight = newWeight; } /** diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java index 33f4aa024ae7..ffd6e15cb1a8 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/signed/SignedStateInfo.java @@ -16,7 +16,7 @@ package com.swirlds.platform.state.signed; -import com.swirlds.platform.system.address.AddressBook; +import com.hedera.hapi.node.state.roster.Roster; /** * Contains information about a signed state. A SignedStateInfo object is still ok to read after the parent SignedState @@ -49,5 +49,10 @@ public interface SignedStateInfo { */ boolean isComplete(); - AddressBook getAddressBook(); + /** + * Get the roster for this signed state. + * + * @return the roster + */ + Roster getRoster(); } diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java index fc9f2066f4b0..498b3fd42b8b 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/state/snapshot/DefaultStateSnapshotManager.java @@ -29,6 +29,7 @@ import com.swirlds.config.api.Configuration; import com.swirlds.logging.legacy.payload.InsufficientSignaturesPayload; import com.swirlds.platform.config.StateConfig; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.ReservedSignedState; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.system.events.EventConstants; @@ -197,7 +198,7 @@ private boolean saveStateTask(@NonNull final SignedState state, @NonNull final P private void checkSignatures(@NonNull final SignedState reservedState) { // this is debug information for ticket #11422 final long signingWeight1 = reservedState.getSigningWeight(); - final long totalWeight1 = reservedState.getAddressBook().getTotalWeight(); + final long totalWeight1 = RosterUtils.computeTotalWeight(reservedState.getRoster()); if (reservedState.isComplete() || reservedState.isPcesRound()) { // state is complete, nothing to do // no signatures are generated for PCES rounds: https://github.com/hashgraph/hedera-services/issues/15229 @@ -206,12 +207,12 @@ private void checkSignatures(@NonNull final SignedState reservedState) { metrics.getTotalUnsignedDiskStatesMetric().increment(); final long signingWeight2 = reservedState.getSigningWeight(); - final long totalWeight2 = reservedState.getAddressBook().getTotalWeight(); + final long totalWeight2 = RosterUtils.computeTotalWeight(reservedState.getRoster()); // don't log an error if this is a freeze state. they are expected to lack signatures if (reservedState.isFreezeState()) { final double signingWeightPercent = (((double) reservedState.getSigningWeight()) - / ((double) reservedState.getAddressBook().getTotalWeight())) + / ((double) RosterUtils.computeTotalWeight(reservedState.getRoster()))) * 100.0; logger.info( @@ -222,7 +223,7 @@ private void checkSignatures(@NonNull final SignedState reservedState) { """, reservedState.getRound(), reservedState.getSigningWeight(), - reservedState.getAddressBook().getTotalWeight(), + RosterUtils.computeTotalWeight(reservedState.getRoster()), signingWeightPercent); } else { final double signingWeight1Percent = (((double) signingWeight1) / ((double) totalWeight1)) * 100.0; 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 5be11a4f7a65..87407dc7d3f8 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 @@ -34,6 +34,7 @@ import com.swirlds.logging.legacy.payload.StateSavedToDiskPayload; import com.swirlds.platform.config.StateConfig; import com.swirlds.platform.recovery.emergencyfile.EmergencyRecoveryFile; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.MerkleRoot; import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; @@ -161,7 +162,7 @@ public static void writeSignedStateFilesToDirectory( writeEmergencyRecoveryFile(directory, signedState); if (!signedState.isGenesisState()) { // Genesis states do not have address books. - writeStateAddressBookFile(directory, signedState.getAddressBook()); + writeStateAddressBookFile(directory, RosterUtils.buildAddressBook(signedState.getRoster())); } writeSettingsUsed(directory, platformContext.getConfiguration()); 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 78e975864eb0..abd9d6b2f80f 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 @@ -51,6 +51,7 @@ import com.swirlds.platform.system.address.Address; import com.swirlds.platform.system.address.AddressBook; import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomRosterBuilder; import com.swirlds.platform.test.fixtures.roster.RosterServiceStateMock; import com.swirlds.state.State; import edu.umd.cs.findbugs.annotations.NonNull; @@ -90,16 +91,16 @@ void forceUseOfConfigAddressBook() throws IOException { configAddressBook, getPlatformContext(true)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); assertEquals( configAddressBook, inititializedAddressBook, "The initial address book must equal the config address book."); assertEquals( - signedState.getAddressBook(), + signedStateAddressBook, initializer.getPreviousAddressBook(), "The previous address book must equal the state address book."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -120,13 +121,13 @@ void noStateLoadedFromDisk() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); assertEquals( configAddressBook, inititializedAddressBook, "The initial address book must equal the expected address book."); assertNull(initializer.getPreviousAddressBook(), "The previous address book should be null."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -147,13 +148,13 @@ void noStateLoadedFromDiskGenesisStateSetZeroWeight() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); assertEquals( configAddressBook, inititializedAddressBook, "The initial address book must equal the config address book."); assertNull(initializer.getPreviousAddressBook(), "The previous address book should be null."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -162,8 +163,9 @@ void noStateLoadedFromDiskGenesisStateSetZeroWeight() throws IOException { void noStateLoadedFromDiskGenesisStateChangedAddressBook() throws IOException { final Randotron randotron = Randotron.create(); clearTestDirectory(); - final AddressBook configAddressBook = getRandomAddressBook(randotron); - final SignedState signedState = getMockSignedState(7, configAddressBook, null, true); + final Roster roster = getRandomRoster(randotron); + final AddressBook configAddressBook = RosterUtils.buildAddressBook(roster); + final SignedState signedState = getMockSignedState(7, roster, null, true); final AddressBookInitializer initializer = new AddressBookInitializer( NodeId.of(0), getMockSoftwareVersion(2), @@ -173,13 +175,13 @@ void noStateLoadedFromDiskGenesisStateChangedAddressBook() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); assertEquals( configAddressBook, inititializedAddressBook, "The initial address book must equal the config address book."); assertNull(initializer.getPreviousAddressBook(), "The previous address book should be null."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); // Even when the genesis state has the correct address book, we always adopt the config.txt address book and // indicate an address book change. assertTrue(initializer.hasAddressBookChanged()); @@ -192,8 +194,9 @@ void currentVersionEqualsStateVersion() throws IOException { clearTestDirectory(); // start state with previous address book final SignedState signedState = - getMockSignedState(2, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); - final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 10); + getMockSignedState(2, getRandomRoster(randotron), getRandomAddressBook(randotron), false); + final AddressBook configAddressBook = + copyWithWeightChanges(RosterUtils.buildAddressBook(signedState.getRoster()), 10); final AddressBookInitializer initializer = new AddressBookInitializer( NodeId.of(0), getMockSoftwareVersion(2), @@ -203,16 +206,16 @@ void currentVersionEqualsStateVersion() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); - assertEqualsAsRosters( - signedState.getAddressBook(), + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); + assertEquals( + signedStateAddressBook, inititializedAddressBook, "The initial address book must equal the state address book."); assertEqualsAsRosters( null, initializer.getPreviousAddressBook(), "When there is no upgrade, the address book should not change"); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); // The addressBooks remain unchanged when there is no software upgrade. assertFalse(initializer.hasAddressBookChanged()); } @@ -236,9 +239,9 @@ void assertEqualsAsRosters(final AddressBook expected, final AddressBook actual, void versionUpgradeSwirldStateZeroWeight() throws IOException { final Randotron randotron = Randotron.create(); clearTestDirectory(); - final SignedState signedState = - getMockSignedState(0, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); - final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 10); + final Roster roster = getRandomRoster(randotron); + final SignedState signedState = getMockSignedState(0, roster, getRandomAddressBook(randotron), false); + final AddressBook configAddressBook = copyWithWeightChanges(RosterUtils.buildAddressBook(roster), 10); final AddressBookInitializer initializer = new AddressBookInitializer( NodeId.of(0), getMockSoftwareVersion(3), @@ -248,16 +251,16 @@ void versionUpgradeSwirldStateZeroWeight() throws IOException { configAddressBook, getPlatformContext(false)); final AddressBook inititializedAddressBook = initializer.getCurrentAddressBook(); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); assertEquals( configAddressBook, inititializedAddressBook, "The initial address book must equal the config address book."); assertEquals( - signedState.getAddressBook(), + signedStateAddressBook, initializer.getPreviousAddressBook(), "The previous address book must equal the state address book."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -266,9 +269,10 @@ void versionUpgradeSwirldStateZeroWeight() throws IOException { void versionUpgradeSwirldStateModifiedAddressBook() throws IOException { final Randotron randotron = Randotron.create(); clearTestDirectory(); - final SignedState signedState = - getMockSignedState(2, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); - final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 3); + final Roster roster = getRandomRoster(randotron); + final SignedState signedState = getMockSignedState(2, roster, getRandomAddressBook(randotron), false); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(roster); + final AddressBook configAddressBook = copyWithWeightChanges(signedStateAddressBook, 3); final AddressBookInitializer initializer = new AddressBookInitializer( NodeId.of(0), getMockSoftwareVersion(3), @@ -283,11 +287,10 @@ void versionUpgradeSwirldStateModifiedAddressBook() throws IOException { inititializedAddressBook, "The initial address book must equal the config address book."); assertEquals( - signedState.getAddressBook(), + signedStateAddressBook, initializer.getPreviousAddressBook(), "The previous address book must equal the state address book."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -297,7 +300,8 @@ void versionUpgradeSwirldStateWeightUpdateWorks() throws IOException { final Randotron randotron = Randotron.create(); clearTestDirectory(); final SignedState signedState = getMockSignedState7WeightRandomAddressBook(randotron); - final AddressBook configAddressBook = copyWithWeightChanges(signedState.getAddressBook(), 5); + final AddressBook signedStateAddressBook = RosterUtils.buildAddressBook(signedState.getRoster()); + final AddressBook configAddressBook = copyWithWeightChanges(signedStateAddressBook, 5); final AddressBookInitializer initializer = new AddressBookInitializer( NodeId.of(0), getMockSoftwareVersion(3), @@ -312,15 +316,14 @@ void versionUpgradeSwirldStateWeightUpdateWorks() throws IOException { inititializedAddressBook, "The initial address book must not equal the config address book."); assertNotEquals( - signedState.getAddressBook(), + signedStateAddressBook, inititializedAddressBook, "The initial address book must not equal the state address book."); assertEquals( - signedState.getAddressBook(), + signedStateAddressBook, initializer.getPreviousAddressBook(), "The previous address book must equal the state address book."); - assertAddressBookFileContent( - initializer, configAddressBook, signedState.getAddressBook(), inititializedAddressBook); + assertAddressBookFileContent(initializer, configAddressBook, signedStateAddressBook, inititializedAddressBook); assertTrue(initializer.hasAddressBookChanged()); } @@ -370,7 +373,7 @@ private SoftwareVersion getMockSoftwareVersion(int version) { * @return The mock SignedState. */ private SignedState getMockSignedState7WeightRandomAddressBook(@NonNull final Randotron randotron) { - return getMockSignedState(7, getRandomAddressBook(randotron), getRandomAddressBook(randotron), false); + return getMockSignedState(7, getRandomRoster(randotron), getRandomAddressBook(randotron), false); } /** @@ -378,8 +381,8 @@ private SignedState getMockSignedState7WeightRandomAddressBook(@NonNull final Ra * * @param weightValue The weight value that the SwirldState should set all addresses to in its updateWeight * method. - * @param currentAddressBook The address book that should be returned by {@link SignedState#getAddressBook()} and - * {@link PlatformStateAccessor#getAddressBook()} + * @param currentRoster The roster that should be returned by {@link SignedState#getRoster()} and used to + * derive the address book for {@link PlatformStateAccessor#getAddressBook()} * @param previousAddressBook The address book that should be returned by * {@link PlatformStateAccessor#getPreviousAddressBook()} * @param fromGenesis Whether the state should be from genesis or not. @@ -387,7 +390,7 @@ private SignedState getMockSignedState7WeightRandomAddressBook(@NonNull final Ra */ private SignedState getMockSignedState( final int weightValue, - @Nullable final AddressBook currentAddressBook, + @Nullable final Roster currentRoster, @Nullable final AddressBook previousAddressBook, boolean fromGenesis) { final SignedState signedState = mock(SignedState.class); @@ -396,7 +399,6 @@ private SignedState getMockSignedState( when(signedState.getSwirldState()).thenReturn(swirldState); final PlatformStateAccessor platformState = mock(PlatformStateAccessor.class); when(platformState.getCreationSoftwareVersion()).thenReturn(softwareVersion); - final Roster currentRoster = RosterRetriever.buildRoster(currentAddressBook); RosterServiceStateMock.setup( (State) swirldState, currentRoster, 1L, RosterRetriever.buildRoster(previousAddressBook)); final MerkleRoot state = mock(MerkleRoot.class); @@ -404,8 +406,7 @@ private SignedState getMockSignedState( when(state.getSwirldState()).thenReturn(swirldState); when(signedState.getState()).thenReturn(state); when(signedState.isGenesisState()).thenReturn(fromGenesis); - // clean up fields unsupported by the Roster (selfName, etc.) by re-building the AddressBook: - when(signedState.getAddressBook()).thenReturn(RosterUtils.buildAddressBook(currentRoster)); + when(signedState.getRoster()).thenReturn(currentRoster); return signedState; } @@ -462,6 +463,11 @@ private AddressBook getRandomAddressBook(@NonNull final Random random) { return RandomAddressBookBuilder.create(random).withSize(5).build(); } + @NonNull + private Roster getRandomRoster(@NonNull final Random random) { + return RandomRosterBuilder.create(random).withSize(5).build(); + } + /** * removes all files and subdirectories from the test directory. * @@ -537,9 +543,9 @@ private void assertAddressBookFileContent( // check usedAddressBook content String usedText = USED_ADDRESS_BOOK_HEADER + "\n"; - if (usedAddressBook.equals(configAddressBook)) { + if (Objects.equals(usedAddressBook, configAddressBook)) { usedText += CONFIG_ADDRESS_BOOK_USED; - } else if (usedAddressBook.equals(stateAddressBook)) { + } else if (Objects.equals(usedAddressBook, stateAddressBook)) { usedText += STATE_ADDRESS_BOOK_USED; } else { usedText += usedAddressBook.toConfigText(); 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 11c304751da0..e3c13ef6659e 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 @@ -31,20 +31,24 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +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.crypto.Hash; import com.swirlds.common.crypto.Signature; import com.swirlds.common.platform.NodeId; import com.swirlds.merkledb.MerkleDb; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.signed.SigSet; import com.swirlds.platform.state.signed.SignedState; import com.swirlds.platform.state.signed.SignedStateInvalidException; -import com.swirlds.platform.system.address.Address; -import com.swirlds.platform.system.address.AddressBook; -import com.swirlds.platform.test.fixtures.addressbook.RandomAddressBookBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomRosterBuilder; +import com.swirlds.platform.test.fixtures.addressbook.RandomRosterBuilder.WeightDistributionStrategy; import com.swirlds.platform.test.fixtures.crypto.PreGeneratedX509Certs; import com.swirlds.platform.test.fixtures.state.RandomSignedStateGenerator; import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.PublicKey; +import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -53,8 +57,8 @@ import java.util.List; import java.util.Random; import java.util.Set; +import java.util.stream.IntStream; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; @@ -81,24 +85,18 @@ void addValidSignaturesTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); - final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); - // Randomize address order - final List

    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); final Set signaturesAdded = new HashSet<>(); @@ -106,19 +104,21 @@ void addValidSignaturesTest(final boolean evenWeighting) { final SigSet sigSet = signedState.getSigSet(); final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - signatures.add(buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash())); + for (final RosterEntry node : nodes) { + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + signatures.add(buildFakeSignature(publicKey, signedState.getState().getHash())); } long expectedWeight = 0; int count = 0; for (int index = 0; index < nodeCount; index++) { - final Address address = nodes.get(index); + final RosterEntry node = nodes.get(index); + final NodeId nodeId = NodeId.of(node.nodeId()); final Signature signature = signatures.get(index); final boolean previouslyComplete = signedState.isComplete(); - final boolean completed = signedState.addSignature(address.getNodeId(), signature); + final boolean completed = signedState.addSignature(NodeId.of(node.nodeId()), signature); final boolean nowComplete = signedState.isComplete(); final boolean verifiable = signedState.isVerifiable(); @@ -130,8 +130,8 @@ void addValidSignaturesTest(final boolean evenWeighting) { if (!previouslyComplete || !nowComplete) { count++; - expectedWeight += address.getWeight(); - signaturesAdded.add(address.getNodeId()); + expectedWeight += node.weight(); + signaturesAdded.add(nodeId); } if (completed) { @@ -141,26 +141,25 @@ void addValidSignaturesTest(final boolean evenWeighting) { if (random.nextBoolean()) { // Sometimes offer the signature more than once. This should have no effect // since duplicates are ignored. - assertFalse(signedState.addSignature(address.getNodeId(), signature)); + assertFalse(signedState.addSignature(nodeId, signature)); } - assertEquals( - SUPER_MAJORITY.isSatisfiedBy(expectedWeight, addressBook.getTotalWeight()), - signedState.isComplete()); - assertEquals( - MAJORITY.isSatisfiedBy(expectedWeight, addressBook.getTotalWeight()), signedState.isVerifiable()); + final long totalWeight = RosterUtils.computeTotalWeight(roster); + + assertEquals(SUPER_MAJORITY.isSatisfiedBy(expectedWeight, totalWeight), signedState.isComplete()); + assertEquals(MAJORITY.isSatisfiedBy(expectedWeight, totalWeight), signedState.isVerifiable()); assertEquals(expectedWeight, signedState.getSigningWeight()); assertEquals(count, sigSet.size()); for (int metaIndex = 0; metaIndex < nodeCount; metaIndex++) { - final NodeId nodeId = nodes.get(metaIndex).getNodeId(); + final NodeId metaNodeId = NodeId.of(nodes.get(metaIndex).nodeId()); - if (signaturesAdded.contains(nodeId)) { + if (signaturesAdded.contains(metaNodeId)) { // We have added this signature, make sure the sigset is tracking it - assertSame(signatures.get(metaIndex), sigSet.getSignature(nodeId)); + assertSame(signatures.get(metaIndex), sigSet.getSignature(metaNodeId)); } else { // We haven't yet added this signature, the sigset should not be tracking it - assertNull(sigSet.getSignature(nodeId)); + assertNull(sigSet.getSignature(metaNodeId)); } } @@ -186,16 +185,13 @@ void addInvalidSignaturesTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); - final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); @@ -203,38 +199,39 @@ void addInvalidSignaturesTest(final boolean evenWeighting) { final SigSet sigSet = signedState.getSigSet(); - // Randomize address order - final List
    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - if (isInvalid(address.getNodeId())) { + for (final RosterEntry node : nodes) { + if (isInvalid(NodeId.of(node.nodeId()))) { // A random signature won't be valid with high probability signatures.add(randomSignature(random)); } else { - signatures.add(buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash())); + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + final Signature signature = + buildFakeSignature(publicKey, signedState.getState().getHash()); + signatures.add(signature); } } long expectedWeight = 0; int count = 0; for (int index = 0; index < nodeCount; index++) { - final Address address = nodes.get(index); + final RosterEntry node = nodes.get(index); + final NodeId nodeId = NodeId.of(node.nodeId()); final Signature signature = signatures.get(index); final boolean previouslyComplete = signedState.isComplete(); - final boolean completed = signedState.addSignature(address.getNodeId(), signature); + final boolean completed = signedState.addSignature(nodeId, signature); final boolean nowComplete = signedState.isComplete(); - if (!isInvalid(address.getNodeId()) && !previouslyComplete) { + if (!isInvalid(nodeId) && !previouslyComplete) { count++; - expectedWeight += address.getWeight(); - signaturesAdded.add(address.getNodeId()); + expectedWeight += node.weight(); + signaturesAdded.add(nodeId); } if (completed) { @@ -244,26 +241,25 @@ void addInvalidSignaturesTest(final boolean evenWeighting) { if (random.nextBoolean()) { // Sometimes offer the signature more than once. This should have no effect // since duplicates are ignored. - assertFalse(signedState.addSignature(address.getNodeId(), signature)); + assertFalse(signedState.addSignature(nodeId, signature)); } - assertEquals( - SUPER_MAJORITY.isSatisfiedBy(expectedWeight, addressBook.getTotalWeight()), - signedState.isComplete()); - assertEquals( - MAJORITY.isSatisfiedBy(expectedWeight, addressBook.getTotalWeight()), signedState.isVerifiable()); + final long totalWeight = RosterUtils.computeTotalWeight(roster); + + assertEquals(SUPER_MAJORITY.isSatisfiedBy(expectedWeight, totalWeight), signedState.isComplete()); + assertEquals(MAJORITY.isSatisfiedBy(expectedWeight, totalWeight), signedState.isVerifiable()); assertEquals(expectedWeight, signedState.getSigningWeight()); assertEquals(count, sigSet.size()); for (int metaIndex = 0; metaIndex < nodeCount; metaIndex++) { - final NodeId nodeId = nodes.get(metaIndex).getNodeId(); + final NodeId metaNodeId = NodeId.of(nodes.get(metaIndex).nodeId()); - if (signaturesAdded.contains(nodeId)) { + if (signaturesAdded.contains(metaNodeId)) { // We have added this signature, make sure the sigset is tracking it - assertSame(signatures.get(metaIndex), sigSet.getSignature(nodeId)); + assertSame(signatures.get(metaIndex), sigSet.getSignature(metaNodeId)); } else { // We haven't yet added this signature or it is invalid, the sigset should not be tracking it - assertNull(sigSet.getSignature(nodeId)); + assertNull(sigSet.getSignature(metaNodeId)); } } @@ -283,16 +279,14 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); @@ -301,17 +295,16 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { final SigSet sigSet = signedState.getSigSet(); - // Randomize address order - final List
    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - final Signature signature = buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash()); + for (final RosterEntry node : nodes) { + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + final Signature signature = + buildFakeSignature(publicKey, signedState.getState().getHash()); final Signature mockSignature = mock(Signature.class); when(mockSignature.getBytes()).thenReturn(signature.getBytes()); when(mockSignature.getType()).thenReturn(signature.getType()); @@ -319,11 +312,13 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { } for (int index = 0; index < nodeCount; index++) { + final RosterEntry node = nodes.get(index); + final NodeId nodeId = NodeId.of(node.nodeId()); final boolean alreadyComplete = signedState.isComplete(); - signedState.addSignature(nodes.get(index).getNodeId(), signatures.get(index)); + signedState.addSignature(nodeId, signatures.get(index)); if (!alreadyComplete) { - signaturesAdded.add(nodes.get(index).getNodeId()); - expectedWeight += nodes.get(index).getWeight(); + signaturesAdded.add(nodeId); + expectedWeight += node.weight(); } } @@ -332,36 +327,31 @@ void signatureBecomesInvalidTest(final boolean evenWeighting) { assertEquals(expectedWeight, signedState.getSigningWeight()); // Remove a node from the address book - final NodeId nodeRemovedFromAddressBook = nodes.get(0).getNodeId(); - final long weightRemovedFromAddressBook = nodes.get(0).getWeight(); - final AddressBook updatedAddressBook = signedState.getAddressBook().remove(nodeRemovedFromAddressBook); - // We cannot really update the AddressBook/Roster in an already signed state because it's already hashed and - // immutable. - // However, we can call a version of the `SignedState.pruneInvalidSignatures()` method that accepts a random - // AddressBook instead. + final RosterEntry nodeToRemove = nodes.getFirst(); + final List entries = signedState.getRoster().rosterEntries().stream() + .filter(entry -> entry.nodeId() != nodeToRemove.nodeId()) + .toList(); + final Roster updatedRoster = new Roster(entries); // Tamper with a node's signature - final long weightWithModifiedSignature = nodes.get(1).getWeight(); + final long weightWithModifiedSignature = nodes.get(1).weight(); final byte[] tamperedBytes = signatures.get(1).getBytes().toByteArray(); tamperedBytes[0] = 0; when(signatures.get(1).getBytes()).thenReturn(Bytes.wrap(tamperedBytes)); - signedState.pruneInvalidSignatures(updatedAddressBook); + signedState.pruneInvalidSignatures(updatedRoster); assertEquals(signaturesAdded.size() - 2, sigSet.size()); assertEquals( - expectedWeight - weightWithModifiedSignature - weightRemovedFromAddressBook, - signedState.getSigningWeight()); + expectedWeight - weightWithModifiedSignature - nodeToRemove.weight(), signedState.getSigningWeight()); for (int index = 0; index < nodes.size(); index++) { - if (index == 0 - || index == 1 - || !signaturesAdded.contains(nodes.get(index).getNodeId())) { - assertNull(sigSet.getSignature(nodes.get(index).getNodeId())); + final NodeId nodeId = NodeId.of(nodes.get(index).nodeId()); + + if (index == 0 || index == 1 || !signaturesAdded.contains(nodeId)) { + assertNull(sigSet.getSignature(nodeId)); } else { - assertSame( - signatures.get(index), - sigSet.getSignature(nodes.get(index).getNodeId())); + assertSame(signatures.get(index), sigSet.getSignature(nodeId)); } } } @@ -374,36 +364,29 @@ void allSignaturesBecomeInvalidTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); final SigSet sigSet = signedState.getSigSet(); - // Randomize address order - final List
    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); - final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - signatures.add(buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash())); - } - - for (int index = 0; index < nodeCount; index++) { - signedState.addSignature(nodes.get(index).getNodeId(), signatures.get(index)); + for (final RosterEntry node : nodes) { + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + final Signature signature = + buildFakeSignature(publicKey, signedState.getState().getHash()); + signedState.addSignature(NodeId.of(node.nodeId()), signature); } assertTrue(signedState.isComplete()); @@ -419,58 +402,53 @@ void allSignaturesBecomeInvalidTest(final boolean evenWeighting) { @ParameterizedTest @ValueSource(booleans = {true, false}) - @DisplayName("Signatures Invalid With Different Address Book Test") - void signaturesInvalidWithDifferentAddressBookTest(final boolean evenWeighting) { + @DisplayName("Signatures Invalid With Different Roster Test") + void signaturesInvalidWithDifferentRosterTest(final boolean evenWeighting) throws CertificateEncodingException { final Random random = getRandomPrintSeed(); final int nodeCount = random.nextInt(10, 20); - - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); final SigSet sigSet = signedState.getSigSet(); - // Randomize address order - final List
    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); - final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - signatures.add(buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash())); - } - - for (int index = 0; index < nodeCount; index++) { - signedState.addSignature(nodes.get(index).getNodeId(), signatures.get(index)); + for (final RosterEntry node : nodes) { + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + final Signature signature = + buildFakeSignature(publicKey, signedState.getState().getHash()); + signedState.addSignature(NodeId.of(node.nodeId()), signature); } assertTrue(signedState.isComplete()); - final AddressBook newAddressBook = addressBook.copy(); - for (final Address address : newAddressBook) { - // need to get signatures that are outside the current address book range from PreGeneratedX509Certs - final X509Certificate certificate = PreGeneratedX509Certs.getSigCert( - 50 + address.getNodeId().id()) - .getCertificate(); - final Address newAddress = address.copySetSigCert(certificate); - // This replaces the old address - newAddressBook.add(newAddress); + final List newRosterEntries = new ArrayList<>(nodeCount); + + for (final RosterEntry originalNode : roster.rosterEntries()) { + final X509Certificate certificate = + PreGeneratedX509Certs.getSigCert(50 + originalNode.nodeId()).getCertificate(); + final RosterEntry newNode = originalNode + .copyBuilder() + .gossipCaCertificate(Bytes.wrap(certificate.getEncoded())) + .build(); + newRosterEntries.add(newNode); } - signedState.pruneInvalidSignatures(newAddressBook); + final Roster newRoster = new Roster(newRosterEntries); + + signedState.pruneInvalidSignatures(newRoster); assertEquals(0, sigSet.size()); assertEquals(0, signedState.getSigningWeight()); @@ -485,56 +463,56 @@ void signaturesInvalidDueToZeroWeightTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster tempRoster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); // set node to zero weight - final NodeId nodeWithZeroWeight = addressBook.getNodeId(0); - addressBook.updateWeight(nodeWithZeroWeight, 0); + final List rosterEntries = IntStream.range( + 0, tempRoster.rosterEntries().size()) + .mapToObj(idx -> { + final RosterEntry entry = tempRoster.rosterEntries().get(idx); + if (idx == 0) { + // replace the first node such that it has a weight of 0 + return entry.copyBuilder().weight(0L).build(); + } + return entry; + }) + .toList(); + final Roster roster = new Roster(rosterEntries); final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); final SigSet sigSet = signedState.getSigSet(); - // Randomize address order - final List
    nodes = new ArrayList<>(addressBook.getSize()); - for (final Address address : addressBook) { - nodes.add(address); - } + // Randomize roster order + final List nodes = new ArrayList<>(roster.rosterEntries()); Collections.shuffle(nodes, random); - final List signatures = new ArrayList<>(nodeCount); - for (final Address address : nodes) { - signatures.add(buildFakeSignature( - address.getSigPublicKey(), signedState.getState().getHash())); - } - - for (int index = 0; index < nodeCount; index++) { - signedState.addSignature(nodes.get(index).getNodeId(), signatures.get(index)); + for (final RosterEntry node : nodes) { + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(node).getPublicKey(); + final Signature signature = + buildFakeSignature(publicKey, signedState.getState().getHash()); + signedState.addSignature(NodeId.of(node.nodeId()), signature); } - assertFalse(sigSet.hasSignature(nodeWithZeroWeight), "Signature for node with zero weight should not be added"); + assertFalse( + sigSet.hasSignature(NodeId.of(roster.rosterEntries().getFirst().nodeId())), + "Signature for node with zero weight should not be added"); assertTrue(signedState.isComplete()); - final AddressBook newAddressBook = new AddressBook(); - int i = 0; - for (final Address address : addressBook) { - newAddressBook.add(address.copySetWeight(0)); - final Address newAddress = newAddressBook.getAddress(newAddressBook.getNodeId(i)); - Assertions.assertNotNull(newAddress); - assertTrue(address.equalsWithoutWeight(newAddress)); - i++; - } + final List newRosterEntries = roster.rosterEntries().stream() + .map(entry -> entry.copyBuilder().weight(0L).build()) + .toList(); + final Roster newRoster = new Roster(newRosterEntries); - signedState.pruneInvalidSignatures(newAddressBook); + signedState.pruneInvalidSignatures(newRoster); assertEquals(0, sigSet.size()); assertEquals(0, signedState.getSigningWeight()); @@ -549,16 +527,14 @@ void recoveryStateIsCompleteTest(final boolean evenWeighting) { final int nodeCount = random.nextInt(10, 20); - final AddressBook addressBook = RandomAddressBookBuilder.create(random) + final Roster roster = RandomRosterBuilder.create(random) .withWeightDistributionStrategy( - evenWeighting - ? RandomAddressBookBuilder.WeightDistributionStrategy.BALANCED - : RandomAddressBookBuilder.WeightDistributionStrategy.GAUSSIAN) + evenWeighting ? WeightDistributionStrategy.BALANCED : WeightDistributionStrategy.GAUSSIAN) .withSize(nodeCount) .build(); final SignedState signedState = new RandomSignedStateGenerator(random) - .setAddressBook(addressBook) + .setAddressBook(RosterUtils.buildAddressBook(roster)) .setSignatures(new HashMap<>()) .build(); diff --git a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java index 5b7545194f95..6af2083913f1 100644 --- a/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java +++ b/platform-sdk/swirlds-platform-core/src/test/java/com/swirlds/platform/state/manager/AbstractStateSignatureCollectorTest.java @@ -21,17 +21,21 @@ import static com.swirlds.platform.test.fixtures.state.manager.SignatureVerificationTestUtils.buildFakeSignatureBytes; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import com.hedera.hapi.node.state.roster.Roster; +import com.hedera.hapi.node.state.roster.RosterEntry; import com.hedera.hapi.platform.event.StateSignatureTransaction; import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.config.api.Configuration; import com.swirlds.config.extensions.test.fixtures.TestConfigBuilder; import com.swirlds.platform.config.StateConfig_; +import com.swirlds.platform.roster.RosterUtils; import com.swirlds.platform.state.StateSignatureCollectorTester; import com.swirlds.platform.state.signed.SignedState; -import com.swirlds.platform.system.address.AddressBook; import edu.umd.cs.findbugs.annotations.NonNull; +import java.security.PublicKey; import java.time.Duration; import java.util.Map; import java.util.Objects; @@ -94,13 +98,16 @@ protected void addSignature( return; } - final AddressBook addressBook = signedState.getAddressBook(); + final Roster roster = signedState.getRoster(); + final RosterEntry rosterEntry = RosterUtils.getRosterEntryOrNull(roster, nodeId.id()); + assertNotNull(rosterEntry); + final PublicKey publicKey = + RosterUtils.fetchGossipCaCertificate(rosterEntry).getPublicKey(); final Hash hash = signedState.getState().getHash(); final StateSignatureTransaction transaction = StateSignatureTransaction.newBuilder() .round(round) - .signature( - buildFakeSignatureBytes(addressBook.getAddress(nodeId).getSigPublicKey(), hash)) + .signature(buildFakeSignatureBytes(publicKey, hash)) .hash(hash.getBytes()) .build(); From 7f75a4d06595b81166afc9cdd2d2d01c58d7f1f6 Mon Sep 17 00:00:00 2001 From: David Bakin <117694041+david-bakin-sl@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:14:24 -0800 Subject: [PATCH 18/39] fix: BLOBBASEFEE opcode (Cancun) to return 1 Wei not 0 Wei (#17046) Signed-off-by: David S Bakin <117694041+david-bakin-sl@users.noreply.github.com> --- .../impl/exec/utils/FrameBuilder.java | 1 + .../test/exec/utils/FrameBuilderTest.java | 6 +++ .../ethereum/HelloWorldEthereumSuite.java | 40 +++++++++++++++++++ .../contracts/BlockQueries/BlockQueries.bin | 1 + .../contracts/BlockQueries/BlockQueries.json | 35 ++++++++++++++++ .../contracts/BlockQueries/BlockQueries.sol | 21 ++++++++++ 6 files changed, 104 insertions(+) create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.bin create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.json create mode 100644 hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.sol diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java index 2cb1626db109..0f1f8ffda13d 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/com/hedera/node/app/service/contract/impl/exec/utils/FrameBuilder.java @@ -105,6 +105,7 @@ public MessageFrame buildInitialFrameWith( .initialGas(transaction.gasAvailable(intrinsicGas)) .originator(from) .gasPrice(Wei.of(context.gasPrice())) + .blobGasPrice(Wei.ONE) // Per Hedera CANCUN adaptation .sender(from) .value(value) .apparentValue(value) diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java index 88ccc42b116b..ddb0091c61cb 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/utils/FrameBuilderTest.java @@ -142,6 +142,7 @@ void constructsExpectedFrameForCallToExtantContractIncludingOptionalContextVaria assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); @@ -190,6 +191,7 @@ void constructsExpectedFrameForCallToExtantContractNotIncludingAccessTrackerWith assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); @@ -257,6 +259,7 @@ void callSucceedsWhenContractNotFoundIfPermitted() { assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); @@ -297,6 +300,7 @@ void callSucceedsWhenContractFoundButDeleted() { assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); @@ -339,6 +343,7 @@ void constructsExpectedFrameForCallToMissingContract() { assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); @@ -382,6 +387,7 @@ void constructsExpectedFrameForCreate() { assertEquals(transaction.gasAvailable(INTRINSIC_GAS), frame.getRemainingGas()); assertSame(EIP_1014_ADDRESS, frame.getOriginatorAddress()); assertEquals(Wei.of(NETWORK_GAS_PRICE), frame.getGasPrice()); + assertEquals(Wei.ONE, frame.getBlobGasPrice()); assertEquals(Wei.of(VALUE), frame.getValue()); assertEquals(Wei.of(VALUE), frame.getApparentValue()); assertSame(blockValues, frame.getBlockValues()); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java index 56944966fdb5..0b54a10d2e5c 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/ethereum/HelloWorldEthereumSuite.java @@ -110,6 +110,7 @@ public class HelloWorldEthereumSuite { private static final String TOKEN_CREATE_CONTRACT = "TokenCreateContract"; private static final String OC_TOKEN_CONTRACT = "OcToken"; private static final String CALLDATA_SIZE_CONTRACT = "CalldataSize"; + private static final String BLOCKQUERIES_CONTRACT = "BlockQueries"; private static final String DEPOSIT = "deposit"; @HapiTest @@ -364,6 +365,45 @@ final Stream ethereumCallWithCalldataBiggerThanMaxSucceeds() { getAliasedAccountInfo(SECP_256K1_SOURCE_KEY).has(accountWith().nonce(1L))); } + @HapiTest + final Stream customizedEvmValuesAreCustomized() { + return hapiTest( + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via("autoAccount"), + getTxnRecord("autoAccount").andAllChildRecords(), + uploadInitCode(BLOCKQUERIES_CONTRACT), + contractCreate(BLOCKQUERIES_CONTRACT).adminKey(THRESHOLD), + // Blobbasefee set correctly initially: + ethereumCall(BLOCKQUERIES_CONTRACT, "getBlobBaseFee") + .via("callTxn1") + .hasKnownStatus(SUCCESS), + // Blobbasefee propagates to child frames correctly: + ethereumCall(BLOCKQUERIES_CONTRACT, "getBlobBaseFeeR", BigInteger.TEN) + .via("callTxn2") + .hasKnownStatus(SUCCESS), + withOpContext((spec, opLog) -> updateSpecFor(spec, SECP_256K1_SOURCE_KEY)), + withOpContext((spec, opLog) -> allRunFor( + spec, + getTxnRecord("callTxn1") + .logged() + .hasPriority(recordWith() + .contractCallResult(resultWith() + .logs(inOrder(logWith() + .longAtBytes(1L /* i.e., 1 Wei */, 24) + .withTopicsInOrder( + List.of(eventSignatureOf("Info(uint256)"))))))), + getTxnRecord("callTxn2") + .logged() + .hasPriority(recordWith() + .contractCallResult(resultWith() + .logs(inOrder(logWith() + .longAtBytes(1L /* i.e., 1 Wei */, 24) + .withTopicsInOrder( + List.of(eventSignatureOf("Info(uint256)")))))))))); + } + @HapiTest final Stream createWithSelfDestructInConstructorHasSaneRecord() { final var txn = "txn"; diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.bin b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.bin new file mode 100644 index 000000000000..344a5cff5b6f --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.bin @@ -0,0 +1 @@ +6080604052348015600e575f5ffd5b506103838061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c80631f6d6ef714610038578063b18f694614610042575b5f5ffd5b61004061005e565b005b61005c60048036038101906100579190610211565b610097565b005b7fe6f250dc95a22400933f5bcd71fc73995f522c0a0b4b8fcdc9719d2ba5fc3a364a60405161008d9190610254565b60405180910390a1565b5f81136100ab576100a661005e565b6101d7565b5f3073ffffffffffffffffffffffffffffffffffffffff166040516024016040516020818303038152906040527f1f6d6ef7000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505060405161015391906102bf565b5f604051808303815f865af19150503d805f811461018c576040519150601f19603f3d011682016040523d82523d5f602084013e610191565b606091505b50509050806101d5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101cc9061032f565b60405180910390fd5b505b50565b5f5ffd5b5f819050919050565b6101f0816101de565b81146101fa575f5ffd5b50565b5f8135905061020b816101e7565b92915050565b5f60208284031215610226576102256101da565b5b5f610233848285016101fd565b91505092915050565b5f819050919050565b61024e8161023c565b82525050565b5f6020820190506102675f830184610245565b92915050565b5f81519050919050565b5f81905092915050565b8281835e5f83830152505050565b5f6102998261026d565b6102a38185610277565b93506102b3818560208601610281565b80840191505092915050565b5f6102ca828461028f565b915081905092915050565b5f82825260208201905092915050565b7f6661696c000000000000000000000000000000000000000000000000000000005f82015250565b5f6103196004836102d5565b9150610324826102e5565b602082019050919050565b5f6020820190508181035f8301526103468161030d565b905091905056fea2646970667358221220fdaadfc506c2f031d2dc9d46a710ab367aa018e20db4296c900bd6b1377c1fd264736f6c634300081c0033 \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.json b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.json new file mode 100644 index 000000000000..9623e902ce83 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.json @@ -0,0 +1,35 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "Info", + "type": "event" + }, + { + "inputs": [], + "name": "getBlobBaseFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "n", + "type": "int256" + } + ], + "name": "getBlobBaseFeeR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.sol b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.sol new file mode 100644 index 000000000000..036917b8ae28 --- /dev/null +++ b/hedera-node/test-clients/src/main/resources/contract/contracts/BlockQueries/BlockQueries.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.10; + +contract BlockQueries { + + event Info(uint n); + + // Emit log with blobbasefee + function getBlobBaseFee() public { + emit Info(block.blobbasefee); + } + + // Recurse n-1 times then finally emit log with blobbasefee + function getBlobBaseFeeR(int n) public { + if (n <= 0) getBlobBaseFee(); + else { + (bool success,) = address(this).call(abi.encodeWithSignature("getBlobBaseFee()")); + require(success, "fail"); + } + } +} From 8718aedafe5bed58e73113fa5ff594f35ebaed1d Mon Sep 17 00:00:00 2001 From: Mariusz Jasuwienas Date: Wed, 18 Dec 2024 13:14:21 +0100 Subject: [PATCH 19/39] =?UTF-8?q?fix:=20adding=20support=20for=20chain=20i?= =?UTF-8?q?ds=20where=20the=20value=20requires=20a=20sign=20bit=E2=80=A6?= =?UTF-8?q?=20(#15997)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mariusz Jasuwienas --- .../app/hapi/utils/ethereum/EthTxData.java | 8 +++- .../hapi/utils/ethereum/EthTxDataTest.java | 19 +++++++++ .../suites/leaky/LeakyEthereumTestsSuite.java | 40 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java index e655ef159256..1ee5034aa730 100644 --- a/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java +++ b/hedera-node/hapi-utils/src/main/java/com/hedera/node/app/hapi/utils/ethereum/EthTxData.java @@ -31,6 +31,7 @@ import java.util.Objects; import org.apache.commons.codec.binary.Hex; import org.bouncycastle.jcajce.provider.digest.Keccak; +import org.bouncycastle.util.BigIntegers; public record EthTxData( byte[] rawTx, @@ -385,7 +386,12 @@ private static EthTxData populateLegacyEthTxData(RLPItem rlpItem, byte[] rawTx) if (vBI.compareTo(BigInteger.valueOf(34)) > 0) { // after EIP155 the chain id is equal to // CHAIN_ID = (v - {0,1} - 35) / 2 - chainId = vBI.subtract(BigInteger.valueOf(35)).shiftRight(1).toByteArray(); + // BigIntegers.asUnsignedByteArray method is used here to ensure no extra byte is added at the beginning + // of the byte array, which can happen in BigInteger.toByteArray when the highest bit + // in the result is already occupied by stored values. This issue is further explained + // in https://github.com/hashgraph/hedera-services/issues/15953 + chainId = BigIntegers.asUnsignedByteArray( + vBI.subtract(BigInteger.valueOf(35)).shiftRight(1)); } else if (isLegacyUnprotectedEtx(vBI)) { // before EIP155 the chain id is considered equal to 0 chainId = new byte[0]; diff --git a/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java b/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java index 3d87475d37fb..db51e4dad18c 100644 --- a/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java +++ b/hedera-node/hapi-utils/src/test/java/com/hedera/node/app/hapi/utils/ethereum/EthTxDataTest.java @@ -35,6 +35,7 @@ import java.math.BigInteger; import java.util.Arrays; import java.util.List; +import org.bouncycastle.util.BigIntegers; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -46,6 +47,8 @@ class EthTxDataTest { static final String SIGNATURE_PUBKEY = "033a514176466fa815ed481ffad09110a2d344f6c9b78c1d14afc351c3a51be33d"; static final String RAW_TX_TYPE_0 = "f864012f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc18180827653820277a0f9fbff985d374be4a55f296915002eec11ac96f1ce2df183adf992baa9390b2fa00c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290fb792"; + static final String RAW_TX_TYPE_0_WITH_CHAIN_ID_11155111 = + "f86b048503ff9aca0782520f94e64fac7f3df5ab44333ad3d3eb3fb68be43f2e8c830fffff808401546d71a026cf0758fda122862a4de71a82a3210ef7c172ee13eae42997f5d32b747ec78ca03587c5c2eee373b1e45693544edcde8dde883d2be3e211b3f0f3c840d6389c8a"; static final String RAW_TX_TYPE_0_TRIMMED_LAST_BYTES = "f864012f83018000947e3a9eaf9bcc39e2ffa38eb30bf7a93feacbc18180827653820277a0f9fbff985d374be4a55f296915002eec11ac96f1ce2df183adf992baa9390b2fa00c1e867cc960d9c74ec2e6a662b7908ec4c8cc9f3091e886bcefbeb2290000"; // { @@ -601,4 +604,20 @@ void bigPositiveValueWithDifferentTypes(EthTransactionType type) { assertEquals(bigValue, populateEthTxData.value()); } + + @Test + void populateEthTxDataComparedToUnsignedByteArrayNoExtraByteAdded() { + final var subject = EthTxData.populateEthTxData(Hex.decode(RAW_TX_TYPE_0_WITH_CHAIN_ID_11155111)); + byte[] passingChainId = BigIntegers.asUnsignedByteArray(BigInteger.valueOf(11155111L)); + assertEquals(Hex.toHexString(subject.chainId()), Hex.toHexString(passingChainId)); + } + + @Test + // In this scenario we are adding unexpected byte at the beginning of the bytes array. + // Issue is better described here: https://github.com/hashgraph/hedera-services/issues/15953 + void populateEthTxDataComparedToSignedByteArrayExtraByteAdded() { + final var subject = EthTxData.populateEthTxData(Hex.decode(RAW_TX_TYPE_0_WITH_CHAIN_ID_11155111)); + byte[] failingChainId = BigInteger.valueOf(11155111L).toByteArray(); + assertNotEquals(Hex.toHexString(subject.chainId()), Hex.toHexString(failingChainId)); + } } diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java index 1af307167297..ed8cde964fb8 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/leaky/LeakyEthereumTestsSuite.java @@ -91,6 +91,46 @@ final Stream legacyUnprotectedEtxBeforeEIP155() { .hasPriority(recordWith().status(SUCCESS))))); } + /** + * test unprotected legacy ethereum transactions before EIP155 + * When using a `CHAIN_ID` represented as a BigInteger, an additional byte is required + * to store the sign information (indicating whether the value is positive or negative), + * if there is no free bit available for this information, as in values like 11155111. + */ + @LeakyHapiTest(overrides = {"contracts.chainId"}) + /* default */ final Stream legacyUnprotectedEtxBeforeEIP155WithChainIdHavingExtraByteForSign() { + final var deposit = "deposit"; + final var depositAmount = 20_000L; + final var chainId = 11_155_111; + + return hapiTest( + overriding("contracts.chainId", "" + chainId), + newKeyNamed(SECP_256K1_SOURCE_KEY).shape(SECP_256K1_SHAPE), + cryptoCreate(RELAYER).balance(6 * ONE_MILLION_HBARS), + cryptoTransfer(tinyBarsFromAccountToAlias(GENESIS, SECP_256K1_SOURCE_KEY, ONE_HUNDRED_HBARS)) + .via("autoAccount"), + getTxnRecord("autoAccount").andAllChildRecords(), + uploadInitCode(PAY_RECEIVABLE_CONTRACT), + contractCreate(PAY_RECEIVABLE_CONTRACT).adminKey(THRESHOLD), + ethereumCall(PAY_RECEIVABLE_CONTRACT, deposit, BigInteger.valueOf(depositAmount)) + .type(EthTransactionType.LEGACY_ETHEREUM) + .signingWith(SECP_256K1_SOURCE_KEY) + .payingWith(RELAYER) + .via("legacyBeforeEIP155") + .nonce(0) + .chainId(chainId) + .gasPrice(50L) + .maxPriorityGas(2L) + .gasLimit(1_000_000L) + .sending(depositAmount) + .hasKnownStatus(ResponseCodeEnum.SUCCESS), + withOpContext((spec, opLog) -> allRunFor( + spec, + getTxnRecord("legacyBeforeEIP155") + .logged() + .hasPriority(recordWith().status(SUCCESS))))); + } + // test legacy ethereum transactions after EIP155 // this tests the behaviour when the `v` field is 37 or 38 // in this case the passed chainId = 1 so ETX is after EIP155 From c4e3cf6ca55634f615cc5f2cffe338eac58da190 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Wed, 18 Dec 2024 16:22:49 +0100 Subject: [PATCH 20/39] build: use common Hiero Gradle config (#15282) Signed-off-by: Jendrik Johannes Co-authored-by: Matt Hess --- .github/dependabot.yml | 7 +- .../node-zxc-build-release-artifact.yaml | 17 +- .../zxc-publish-production-image.yaml | 17 +- .../build.gradle.kts | 24 +- gradle/aggregation/build.gradle.kts | 51 +-- gradle/development-branch.txt | 1 + gradle/plugins/build.gradle.kts | 42 --- .../com.hedera.gradle.application.gradle.kts | 56 --- ...hedera.gradle.feature.benchmark.gradle.kts | 56 --- ...ra.gradle.feature.test-fixtures.gradle.kts | 28 -- ...dera.gradle.feature.test-hammer.gradle.kts | 40 --- ...dle.feature.test-time-consuming.gradle.kts | 31 -- ...e.feature.test-timing-sensitive.gradle.kts | 39 --- .../kotlin/com.hedera.gradle.java.gradle.kts | 330 ------------------ .../com.hedera.gradle.jpms-modules.gradle.kts | 257 -------------- .../com.hedera.gradle.lifecycle.gradle.kts | 33 -- ...com.hedera.gradle.maven-publish.gradle.kts | 135 ------- ...com.hedera.gradle.nexus-publish.gradle.kts | 53 --- ....hedera.gradle.platform-publish.gradle.kts | 59 ---- .../com.hedera.gradle.platform.gradle.kts | 20 -- .../com.hedera.gradle.protobuf.gradle.kts | 68 ---- .../com.hedera.gradle.reports.gradle.kts | 27 -- .../com.hedera.gradle.repositories.gradle.kts | 35 -- .../kotlin/com.hedera.gradle.root.gradle.kts | 133 ------- ....hedera.gradle.services-publish.gradle.kts | 20 -- .../com.hedera.gradle.services.gradle.kts | 20 -- ...hedera.gradle.settings.settings.gradle.kts | 80 ----- .../com.hedera.gradle.shadow-jar.gradle.kts | 40 --- ...com.hedera.gradle.spotless-java.gradle.kts | 67 ---- ...m.hedera.gradle.spotless-kotlin.gradle.kts | 46 --- ...hedera.gradle.spotless-markdown.gradle.kts | 27 -- .../com.hedera.gradle.spotless.gradle.kts | 75 ---- .../com.hedera.gradle.versions.gradle.kts | 25 -- .../hedera/gradle/services/TaskLockService.kt | 22 -- .../RepairDashedCommentsFormatterStep.kt | 73 ---- .../spotless/SortModuleInfoRequiresStep.kt | 102 ------ .../spotless/StripOldLicenseFormatterStep.kt | 58 --- .../kotlin/com/hedera/gradle/utils/Utils.kt | 50 --- gradle/toolchain-versions.properties | 1 + hapi/build.gradle.kts | 35 +- hedera-dependency-versions/build.gradle.kts | 253 -------------- hedera-node/hapi-fees/build.gradle.kts | 22 +- hedera-node/hapi-utils/build.gradle.kts | 22 +- .../hapi-utils/src/main/java/module-info.java | 4 +- .../build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- hedera-node/hedera-app-spi/build.gradle.kts | 22 +- hedera-node/hedera-app/build.gradle.kts | 26 +- .../hedera-app/src/main/java/module-info.java | 8 +- hedera-node/hedera-config/build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../hedera-consensus-service/build.gradle.kts | 22 +- .../hedera-file-service-impl/build.gradle.kts | 22 +- .../hedera-file-service/build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../hedera-schedule-service/build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../src/main/java/module-info.java | 4 +- .../build.gradle.kts | 22 +- .../build.gradle.kts | 22 +- .../hedera-token-service/build.gradle.kts | 22 +- .../hedera-util-service-impl/build.gradle.kts | 22 +- .../hedera-util-service/build.gradle.kts | 22 +- hedera-node/test-clients/build.gradle.kts | 69 ++-- .../src/main/java/module-info.java | 58 +-- .../src/yahcli/java/module-info.java | 1 + hiero-dependency-versions/build.gradle.kts | 126 +++++++ platform-sdk/build.gradle.kts | 33 +- .../consensus-gossip-impl/build.gradle.kts | 21 +- .../consensus-gossip/build.gradle.kts | 21 +- .../event-creator-impl/build.gradle.kts | 21 +- platform-sdk/event-creator/build.gradle.kts | 21 +- .../demos/CryptocurrencyDemo/build.gradle.kts | 21 +- .../demos/HelloSwirldDemo/build.gradle.kts | 21 +- .../demos/StatsDemo/build.gradle.kts | 21 +- .../AddressBookTestingTool/build.gradle.kts | 21 +- .../ConsistencyTestingTool/build.gradle.kts | 21 +- .../tests/ISSTestingTool/build.gradle.kts | 21 +- .../MigrationTestingTool/build.gradle.kts | 21 +- .../PlatformTestingTool/build.gradle.kts | 22 +- .../StatsSigningTestingTool/build.gradle.kts | 21 +- .../tests/StressTestingTool/build.gradle.kts | 21 +- platform-sdk/swirlds-base/build.gradle.kts | 27 +- .../swirlds-benchmarks/build.gradle.kts | 21 +- platform-sdk/swirlds-cli/build.gradle.kts | 21 +- platform-sdk/swirlds-common/build.gradle.kts | 25 +- .../swirlds-config-api/build.gradle.kts | 23 +- .../build.gradle.kts | 23 +- .../swirlds-config-impl/build.gradle.kts | 23 +- .../swirlds-config-processor/build.gradle.kts | 21 +- .../swirlds-fchashmap/build.gradle.kts | 21 +- platform-sdk/swirlds-fcqueue/build.gradle.kts | 21 +- .../build.gradle.kts | 21 +- platform-sdk/swirlds-logging/build.gradle.kts | 29 +- platform-sdk/swirlds-merkle/build.gradle.kts | 25 +- .../swirlds-merkledb/build.gradle.kts | 29 +- .../src/testFixtures/java/module-info.java | 2 +- .../swirlds-metrics-api/build.gradle.kts | 21 +- .../swirlds-metrics-impl/build.gradle.kts | 21 +- .../swirlds-platform-core/build.gradle.kts | 27 +- .../swirlds-state-api/build.gradle.kts | 23 +- .../swirlds-state-impl/build.gradle.kts | 23 +- .../swirlds-platform-test/build.gradle.kts | 35 +- .../swirlds-virtualmap/build.gradle.kts | 29 +- platform-sdk/swirlds/build.gradle.kts | 79 +++-- settings.gradle.kts | 47 +-- 108 files changed, 514 insertions(+), 3740 deletions(-) create mode 100644 gradle/development-branch.txt delete mode 100644 gradle/plugins/build.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.benchmark.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-fixtures.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-hammer.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-time-consuming.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-timing-sensitive.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.nexus-publish.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-markdown.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts delete mode 100644 gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt delete mode 100644 gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt delete mode 100644 gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/SortModuleInfoRequiresStep.kt delete mode 100644 gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt delete mode 100644 gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt create mode 100644 gradle/toolchain-versions.properties delete mode 100644 hedera-dependency-versions/build.gradle.kts create mode 100644 hiero-dependency-versions/build.gradle.kts diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3aa14155bf85..9bca0086f871 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,7 @@ version: 2 updates: - package-ecosystem: "gradle" - directory: "/hedera-dependency-versions" - schedule: - interval: "daily" - open-pull-requests-limit: 10 - - package-ecosystem: "gradle" - directory: "/gradle/plugins" + directory: "/hiero-dependency-versions" schedule: interval: "daily" open-pull-requests-limit: 10 diff --git a/.github/workflows/node-zxc-build-release-artifact.yaml b/.github/workflows/node-zxc-build-release-artifact.yaml index 1534a2fb3e84..3d1fe85f9667 100644 --- a/.github/workflows/node-zxc-build-release-artifact.yaml +++ b/.github/workflows/node-zxc-build-release-artifact.yaml @@ -1,19 +1,4 @@ -## -# Copyright (C) 2022-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. -## - +# SPDX-License-Identifier: Apache-2.0 name: "ZXC: [Node] Deploy Release Artifacts" on: workflow_call: diff --git a/.github/workflows/zxc-publish-production-image.yaml b/.github/workflows/zxc-publish-production-image.yaml index 3fe6d8e0b53e..c9e24795a909 100644 --- a/.github/workflows/zxc-publish-production-image.yaml +++ b/.github/workflows/zxc-publish-production-image.yaml @@ -1,19 +1,4 @@ -## -# 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. -## - +# SPDX-License-Identifier: Apache-2.0 name: "ZXC: Publish Production Image" on: workflow_call: diff --git a/example-apps/swirlds-platform-base-example/build.gradle.kts b/example-apps/swirlds-platform-base-example/build.gradle.kts index 7f3e5f02a809..d7db028f8a6f 100644 --- a/example-apps/swirlds-platform-base-example/build.gradle.kts +++ b/example-apps/swirlds-platform-base-example/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("application") - id("com.hedera.gradle.platform") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } mainModuleInfo { annotationProcessor("com.swirlds.config.processor") @@ -25,4 +7,4 @@ mainModuleInfo { runtimeOnly("com.swirlds.config.impl") } -application.mainClass.set("com.swirlds.platform.base.example.Application") +application.mainClass = "com.swirlds.platform.base.example.Application" diff --git a/gradle/aggregation/build.gradle.kts b/gradle/aggregation/build.gradle.kts index c553de85fb04..e03445d5ff03 100644 --- a/gradle/aggregation/build.gradle.kts +++ b/gradle/aggregation/build.gradle.kts @@ -1,20 +1,10 @@ -/* - * 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. - */ - -plugins { id("com.hedera.gradle.reports") } +// SPDX-License-Identifier: Apache-2.0 +plugins { + id("org.hiero.gradle.base.lifecycle") + id("org.hiero.gradle.report.code-coverage") + id("org.hiero.gradle.check.spotless") + id("org.hiero.gradle.check.spotless-kotlin") +} dependencies { implementation(project(":app")) @@ -29,9 +19,26 @@ dependencies { implementation(project(":test-clients")) } -tasks.named("testCodeCoverageReport") { - val exclusions = listOf("test-clients", "testFixtures", "statedumpers", "example-apps") - classDirectories.setFrom( - classDirectories.files.filterNot { file -> exclusions.any { file.path.contains(it) } } - ) +tasks.testCodeCoverageReport { + // Redo the setup done in 'JacocoReportAggregationPlugin', but gather the class files in the + // file tree and filter out selected classes by path. + val filteredClassFiles = + configurations.aggregateCodeCoverageReportResults + .get() + .incoming + .artifactView { + componentFilter { id -> id is ProjectComponentIdentifier } + attributes.attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.CLASSES) + ) + } + .files + .asFileTree + .filter { file -> + listOf("test-clients", "testFixtures", "statedumpers", "example-apps").none { + file.path.contains(it) + } + } + classDirectories.setFrom(filteredClassFiles) } diff --git a/gradle/development-branch.txt b/gradle/development-branch.txt new file mode 100644 index 000000000000..ce57f6456319 --- /dev/null +++ b/gradle/development-branch.txt @@ -0,0 +1 @@ +develop \ No newline at end of file diff --git a/gradle/plugins/build.gradle.kts b/gradle/plugins/build.gradle.kts deleted file mode 100644 index fc93ac5a4da1..000000000000 --- a/gradle/plugins/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { - // Support convention plugins written in Kotlin. Convention plugins are build scripts in - // 'src/main' that automatically become available as plugins in the main build. - `kotlin-dsl` -} - -repositories { gradlePluginPortal() } - -dependencies { - implementation("com.adarshr:gradle-test-logger-plugin:4.0.0") - implementation("com.autonomousapps:dependency-analysis-gradle-plugin:2.6.1") - implementation("com.diffplug.spotless:spotless-plugin-gradle:6.25.0") - implementation("com.github.johnrengelman:shadow:8.1.1") - implementation("com.google.protobuf:protobuf-gradle-plugin:0.9.4") - implementation("com.gradle:develocity-gradle-plugin:3.18") - implementation( - "gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.2.2" - ) - implementation("io.freefair.gradle:maven-plugin:8.11") // for POM validation - implementation("io.github.gradle-nexus:publish-plugin:1.3.0") - implementation("me.champeau.jmh:jmh-gradle-plugin:0.7.2") - implementation("net.swiftzer.semver:semver:1.3.0") - implementation("org.gradlex:extra-java-module-info:1.9") - implementation("org.gradlex:jvm-dependency-conflict-resolution:2.1.2") - implementation("org.gradlex:java-module-dependencies:1.8") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts deleted file mode 100644 index b821df2958c4..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.application.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2016-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. - */ - -plugins { - id("application") - id("com.hedera.gradle.java") -} - -// Find the central SDK deployment dir by searching up the folder hierarchy -fun sdkDir(dir: Directory): Directory = - if (dir.dir("sdk").asFile.exists()) dir.dir("sdk") else sdkDir(dir.dir("..")) - -// Copy dependencies into `sdk/data/lib` -val copyLib = - tasks.register("copyLib") { - from(project.configurations.runtimeClasspath) - into(sdkDir(layout.projectDirectory).dir("data/lib")) - } - -// Copy built jar into `data/apps` and rename -val copyApp = - tasks.register("copyApp") { - inputs.property("projectName", project.name) - - from(tasks.jar) - into(sdkDir(layout.projectDirectory).dir("data/apps")) - rename { "${inputs.properties["projectName"]}.jar" } - } - -tasks.assemble { - dependsOn(copyLib) - dependsOn(copyApp) -} - -// The 'application' plugin activates the following tasks as part of 'assemble'. -// As we do not use these results right now, disable them: -tasks.startScripts { enabled = false } - -tasks.distTar { enabled = false } - -tasks.distZip { enabled = false } - -tasks.jar { manifest { attributes("Main-Class" to application.mainClass) } } diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.benchmark.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.benchmark.gradle.kts deleted file mode 100644 index 22388926226e..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.benchmark.gradle.kts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import me.champeau.jmh.JMHTask - -plugins { id("me.champeau.jmh") } - -jmh { - jmhVersion = "1.37" - includeTests = false - // Filter JMH tests from command line via -PjmhTests=... - val commandLineIncludes = providers.gradleProperty("jmhTests") - if (commandLineIncludes.isPresent) { - includes.add(commandLineIncludes.get()) - } -} - -dependencies { - // Required for the JMH IDEA plugin: - // https://plugins.jetbrains.com/plugin/7529-jmh-java-microbenchmark-harness - jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:${jmh.jmhVersion.get()}") -} - -tasks.jmh { outputs.upToDateWhen { false } } - -tasks.withType().configureEach { - group = "jmh" - jarArchive = tasks.jmhJar.flatMap { it.archiveFile } - jvm = javaToolchains.launcherFor(java.toolchain).map { it.executablePath }.get().asFile.path -} - -tasks.jmhJar { manifest { attributes(mapOf("Multi-Release" to true)) } } - -configurations { - // Disable module Jar patching for the JMH runtime classpath. - // The way the JMH plugin interacts with this in the 'jmhJar' task triggers this Gradle issue: - // https://github.com/gradle/gradle/issues/27372 - // And since 'jmhJar' builds a fat jar, module information is not needed here anyway. - val javaModule = Attribute.of("javaModule", Boolean::class.javaObjectType) - jmhRuntimeClasspath { attributes { attribute(javaModule, false) } } - jmhCompileClasspath { attributes { attribute(javaModule, false) } } - jmhAnnotationProcessor { attributes { attribute(javaModule, false) } } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-fixtures.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-fixtures.gradle.kts deleted file mode 100644 index dfb4f6cb1da8..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-fixtures.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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. - */ - -import org.gradle.api.component.AdhocComponentWithVariants - -plugins { id("java-test-fixtures") } - -tasks.testFixturesJar { setGroup(null) } - -// Disable publishing of test fixture if 'java-test-fixtures' plugin is used -// https://docs.gradle.org/current/userguide/java_testing.html#ex-disable-publishing-of-test-fixtures-variants -(components["java"] as AdhocComponentWithVariants).apply { - withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() } - withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-hammer.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-hammer.gradle.kts deleted file mode 100644 index 2d9aaba59527..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-hammer.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import com.hedera.gradle.services.TaskLockService - -plugins { id("java") } - -// Test functionally correct behavior under stress/loads with many repeated iterations. -@Suppress("UnstableApiUsage") -testing.suites { - register("hammer") { - testType = "hammer" - targets.all { - testTask { - group = "build" - usesService( - gradle.sharedServices.registerIfAbsent("lock", TaskLockService::class) { - maxParallelUsages = 1 - } - ) - maxHeapSize = "8g" - } - } - } -} - -tasks.assemble { dependsOn(tasks.named("hammerClasses")) } diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-time-consuming.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-time-consuming.gradle.kts deleted file mode 100644 index 156b2d85b2b1..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-time-consuming.gradle.kts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { id("java") } - -// Tests that could be in the default "test" set but take more than 100ms to execute. -@Suppress("UnstableApiUsage") -testing.suites { - register("timeConsuming") { - testType = "timing-consuming" - targets.all { - testTask { - group = "build" - maxHeapSize = "4g" - } - } - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-timing-sensitive.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-timing-sensitive.gradle.kts deleted file mode 100644 index c567609a0422..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.feature.test-timing-sensitive.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import com.hedera.gradle.services.TaskLockService - -plugins { id("java") } - -// Tests that are resource sensitive (e.g. use Thread.sleep()) and thus need to run without anything -// in parallel. -@Suppress("UnstableApiUsage") -testing.suites { - register("timingSensitive") { - testType = "timing-sensitive" - targets.all { - testTask { - group = "build" - maxHeapSize = "4g" - usesService( - gradle.sharedServices.registerIfAbsent("lock", TaskLockService::class) { - maxParallelUsages = 1 - } - ) - } - } - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts deleted file mode 100644 index 5a4e2fd4cd2c..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.java.gradle.kts +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import com.adarshr.gradle.testlogger.theme.ThemeType -import com.autonomousapps.DependencyAnalysisSubExtension -import com.hedera.gradle.services.TaskLockService -import com.hedera.gradle.utils.Utils.versionTxt -import org.gradlex.javamodule.dependencies.tasks.ModuleDirectivesOrderingCheck -import org.gradlex.javamodule.dependencies.tasks.ModuleDirectivesScopeCheck - -plugins { - id("java") - id("jacoco") - id("checkstyle") - id("com.adarshr.test-logger") - id("com.autonomousapps.dependency-analysis") - id("com.hedera.gradle.lifecycle") - id("com.hedera.gradle.jpms-modules") - id("com.hedera.gradle.repositories") - id("com.hedera.gradle.spotless-java") - id("com.hedera.gradle.spotless-kotlin") -} - -version = - providers.fileContents(rootProject.layout.projectDirectory.versionTxt()).asText.get().trim() - -val javaVersionMajor = JavaVersion.VERSION_21 -val javaVersionPatch = "0.4" - -val currentJavaVersionMajor = JavaVersion.current() -val currentJavaVersion = providers.systemProperty("java.version").get() -val expectedJavaVersion = "$javaVersionMajor.$javaVersionPatch" - -if (currentJavaVersion != expectedJavaVersion) { - val message = - "Gradle runs with Java $currentJavaVersion. This project works best running with Java $expectedJavaVersion. " + - "\n - From commandline: change JAVA_HOME and/or PATH to point at Java $expectedJavaVersion installation." + - "\n - From IntelliJ: change 'Gradle JVM' in 'Gradle Settings' to point at Java $expectedJavaVersion installation." - - if (currentJavaVersionMajor.ordinal < javaVersionMajor.ordinal) { // fail if version is too old - throw (RuntimeException(message)) - } else { - logger.lifecycle("WARN: $message") - } -} - -java { - sourceCompatibility = javaVersionMajor - targetCompatibility = javaVersionMajor -} - -configurations.all { - // In case published versions of a module are also available, always prefer the local one - resolutionStrategy.preferProjectModules() -} - -jvmDependencyConflicts { - consistentResolution { - providesVersions(":aggregation") - platform(":hedera-dependency-versions") - } -} - -val consistentResolutionAttribute = Attribute.of("consistent-resolution", String::class.java) - -configurations.create("allDependencies") { - isCanBeConsumed = true - isCanBeResolved = false - sourceSets.all { - extendsFrom( - configurations[this.implementationConfigurationName], - configurations[this.compileOnlyConfigurationName], - configurations[this.runtimeOnlyConfigurationName], - configurations[this.annotationProcessorConfigurationName] - ) - } - attributes { - attribute(consistentResolutionAttribute, "global") - attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) - attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) - attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR)) - attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) - } -} - -configurations.getByName("mainRuntimeClasspath") { - attributes.attribute(consistentResolutionAttribute, "global") -} - -tasks.buildDependents { setGroup(null) } - -tasks.buildNeeded { setGroup(null) } - -tasks.jar { setGroup(null) } - -sourceSets.all { - // Remove 'classes' tasks from 'build' group to keep it cleaned up - tasks.named(classesTaskName) { group = null } - - // 'assemble' compiles all sources, including all test sources - tasks.assemble { dependsOn(tasks.named(classesTaskName)) } -} - -val writeGitProperties = - tasks.register("writeGitProperties") { - property("git.build.version", project.version) - @Suppress("UnstableApiUsage") - property( - "git.commit.id", - providers - .exec { commandLine("git", "rev-parse", "HEAD") } - .standardOutput - .asText - .map { it.trim() } - ) - @Suppress("UnstableApiUsage") - property( - "git.commit.id.abbrev", - providers - .exec { commandLine("git", "rev-parse", "HEAD") } - .standardOutput - .asText - .map { it.trim().substring(0, 7) } - ) - - destinationFile = layout.buildDirectory.file("generated/git/git.properties") - } - -tasks.processResources { from(writeGitProperties) } - -// ignore the content of 'git.properties' when using a classpath as task input -normalization.runtimeClasspath { ignore("git.properties") } - -tasks.withType().configureEach { - isPreserveFileTimestamps = false - isReproducibleFileOrder = true - filePermissions { unix("0664") } - dirPermissions { unix("0775") } -} - -tasks.jar { exclude("**/classpath.index") } - -val deactivatedCompileLintOptions = - listOf( - // In Gradle, a module does not see the upstream (not-yet-compiled) modules. This could - // only be solved by calling 'javac' with '--source-module-path' to make other sources - // known. But this is at odds with how Gradle's incremental compilation calls the - // compiler for a subset of Java files for each project individually. - "module", // module not found when doing 'exports to ...' - "serial", // serializable class ... has no definition of serialVersionUID - "processing", // No processor claimed any of these annotations: ... - "try", // auto-closeable resource ignore is never referenced... (AutoClosableLock) - "missing-explicit-ctor", // class ... declares no explicit constructors - - // Needed because we use deprecation internally and do not fix all uses right away - "removal", - "deprecation", - - // The following checks could be activated and fixed: - "this-escape", // calling public/protected method in constructor - "overrides", // overrides equals, but neither it ... overrides hashCode method - "unchecked", - "rawtypes" - ) - -tasks.withType().configureEach { - // Track the full Java version as input (e.g. 17.0.3 vs. 17.0.9). - // By default, Gradle only tracks the major version as defined in the toolchain (e.g. 17). - // Since the full version is encoded in 'module-info.class' files, it should be tracked as - // it otherwise leads to wrong build cache hits. - inputs.property("fullJavaVersion", currentJavaVersion) - - options.encoding = "UTF-8" - options.isFork = true // run compiler in separate JVM process (independent of toolchain setup) - options.compilerArgs.add("-implicit:none") - options.compilerArgs.add("-Werror") - options.compilerArgs.add("-Xlint:all,-" + deactivatedCompileLintOptions.joinToString(",-")) - - doLast { - // Make sure consistent line ending are used in files generated by annotation processors by - // rewriting generated files. - // To fix this problem at the root, one of these issues needs to be addressed upstream: - // - https://github.com/google/auto/issues/1656 - // - https://github.com/gradle/gradle/issues/27385 - if (System.lineSeparator() != "\n") { - destinationDirectory - .get() - .asFileTree - .filter { it.extension != "class" } - .forEach { - val content = it.readText() - val normalizedContent = content.replace(System.lineSeparator(), "\n") - if (content != normalizedContent) { - it.writeText(normalizedContent) - } - } - } - } -} - -tasks.withType().configureEach { - options { - this as StandardJavadocDocletOptions - encoding = "UTF-8" - tags( - "apiNote:a:API Note:", - "implSpec:a:Implementation Requirements:", - "implNote:a:Implementation Note:" - ) - options.windowTitle = "Hedera Consensus Node" - options.memberLevel = JavadocMemberLevel.PACKAGE - addStringOption("Xdoclint:all,-missing", "-Xwerror") - } -} - -@Suppress("UnstableApiUsage") -testing.suites { - named("test") { - useJUnitJupiter() - targets.all { - testTask { - group = "build" - maxHeapSize = "4g" - // Some tests overlap due to using the same temp folders within one project - // maxParallelForks = 4 <- set this, once tests can run in parallel - - // Enable dynamic agent loading for tests - eg: Mockito, ByteBuddy - jvmArgs("-XX:+EnableDynamicAgentLoading") - } - } - } - // remove automatically added compile time dependencies, as we want to define them all - // explicitly - withType { - configurations.getByName(sources.implementationConfigurationName) { - withDependencies { - removeIf { it.group == "org.junit.jupiter" && it.name == "junit-jupiter" } - } - } - dependencies { runtimeOnly("org.junit.jupiter:junit-jupiter-engine") } - } -} - -// If user gave the argument '-PactiveProcessorCount', then do: -// - run all test tasks in sequence -// - give the -XX:ActiveProcessorCount argument to the test JVMs -val activeProcessorCount = providers.gradleProperty("activeProcessorCount") - -if (activeProcessorCount.isPresent) { - tasks.withType().configureEach { - usesService( - gradle.sharedServices.registerIfAbsent("lock", TaskLockService::class) { - maxParallelUsages = 1 - } - ) - jvmArgs("-XX:ActiveProcessorCount=${activeProcessorCount.get()}") - } -} - -tasks.jacocoTestReport { - // Configure Jacoco so it outputs XML reports (needed by SonarCloud) - reports { - xml.required = true - html.required = true - } -} - -testlogger { - theme = ThemeType.MOCHA_PARALLEL - slowThreshold = 10000 - showPassed = false - showSkipped = false - showStandardStreams = true - showPassedStandardStreams = false - showSkippedStandardStreams = false - showFailedStandardStreams = true -} - -tasks.assemble { dependsOn(tasks.javadoc) } - -tasks.check { dependsOn(tasks.jacocoTestReport) } - -tasks.named("qualityGate") { dependsOn(tasks.withType()) } - -// ordering check is done by SortModuleInfoRequiresStep -tasks.withType { enabled = false } - -tasks.withType().configureEach { - // When doing a 'qualityGate' run, make sure spotlessApply is done before doing compilation and - // other checks based on compiled code - mustRunAfter(tasks.spotlessApply) -} - -// Do not report dependencies from one source set to another as 'required'. -// In particular, in case of test fixtures, the analysis would suggest to -// add as testModuleInfo { require(...) } to the main module. This is -// conceptually wrong, because in whitebox testing the 'main' and 'test' -// module are conceptually considered one module (main module extended with tests) -configure { issues { onAny { exclude(project.path) } } } - -checkstyle { toolVersion = "10.12.7" } - -tasks.withType().configureEach { - reports { - xml.required = true - html.required = true - sarif.required = true - } -} - -tasks.withType().configureEach { - // Do not yet run things on the '--module-path' - modularity.inferModulePath = false - if (name.endsWith("main()")) { - notCompatibleWithConfigurationCache("JavaExec created by IntelliJ") - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts deleted file mode 100644 index 7a53b9644424..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.jpms-modules.gradle.kts +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2016-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. - */ - -plugins { - id("org.gradlex.jvm-dependency-conflict-resolution") - id("org.gradlex.extra-java-module-info") -} - -// Fix or enhance the metadata of third-party Modules. This is about the metadata in the -// repositories: '*.pom' and '*.module' files. -jvmDependencyConflicts.patch { - // These compile time annotation libraries are not of interest in our setup and are thus removed - // from the dependencies of all components that bring them in. - val annotationLibraries = - listOf( - "com.google.android:annotations", - "com.google.code.findbugs:annotations", - "com.google.code.findbugs:jsr305", - "com.google.errorprone:error_prone_annotations", - "com.google.guava:listenablefuture", - "org.checkerframework:checker-compat-qual", - "org.checkerframework:checker-qual", - "org.codehaus.mojo:animal-sniffer-annotations" - ) - - module("io.netty:netty-transport-native-epoll") { - addFeature("linux-x86_64") - addFeature("linux-aarch_64") - } - module("io.grpc:grpc-api") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-core") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-context") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-netty") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-protobuf") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-protobuf-lite") { - annotationLibraries.forEach { removeDependency(it) } - removeDependency("com.google.protobuf:protobuf-javalite") - addApiDependency("com.google.protobuf:protobuf-java") - } - module("io.grpc:grpc-services") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-stub") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-testing") { annotationLibraries.forEach { removeDependency(it) } } - module("io.grpc:grpc-util") { annotationLibraries.forEach { removeDependency(it) } } - module("com.github.ben-manes.caffeine:caffeine") { - annotationLibraries.forEach { removeDependency(it) } - } - module("com.google.dagger:dagger-compiler") { - annotationLibraries.forEach { removeDependency(it) } - } - module("com.google.dagger:dagger-producers") { - annotationLibraries.forEach { removeDependency(it) } - } - module("com.google.dagger:dagger-spi") { annotationLibraries.forEach { removeDependency(it) } } - module("com.google.guava:guava") { - (annotationLibraries - - "com.google.code.findbugs:jsr305" - - "com.google.errorprone:error_prone_annotations" - - "org.checkerframework:checker-qual") - .forEach { removeDependency(it) } - } - module("com.google.protobuf:protobuf-java-util") { - annotationLibraries.forEach { removeDependency(it) } - } - module("org.apache.tuweni:tuweni-bytes") { removeDependency("com.google.code.findbugs:jsr305") } - module("org.apache.tuweni:tuweni-units") { removeDependency("com.google.code.findbugs:jsr305") } - module("io.prometheus:simpleclient") { - removeDependency("io.prometheus:simpleclient_tracer_otel") - removeDependency("io.prometheus:simpleclient_tracer_otel_agent") - } - module("org.jetbrains.kotlin:kotlin-stdlib") { - removeDependency("org.jetbrains.kotlin:kotlin-stdlib-common") - } - module("junit:junit") { removeDependency("org.hamcrest:hamcrest-core") } - module("org.hyperledger.besu:secp256k1") { addApiDependency("net.java.dev.jna:jna") } -} - -// Fix or enhance the 'module-info.class' of third-party Modules. This is about the -// 'module-info.class' inside the Jar files. In our full Java Modules setup every -// Jar needs to have this file. If it is missing, it is added by what is configured here. -extraJavaModuleInfo { - failOnAutomaticModules = true // Only allow Jars with 'module-info' on all module paths - versionsProvidingConfiguration = "mainRuntimeClasspath" - - module("io.grpc:grpc-api", "io.grpc") - module("io.grpc:grpc-core", "io.grpc.internal") - module("io.grpc:grpc-context", "io.grpc.context") - module("io.grpc:grpc-netty", "io.grpc.netty") { - exportAllPackages() - requireAllDefinedDependencies() - requires("java.logging") - } - module("io.grpc:grpc-stub", "io.grpc.stub") { - exportAllPackages() - requireAllDefinedDependencies() - requires("java.logging") - } - module("io.grpc:grpc-util", "io.grpc.util") - module("io.grpc:grpc-protobuf", "io.grpc.protobuf") - module("io.grpc:grpc-protobuf-lite", "io.grpc.protobuf.lite") - module("io.helidon.common:helidon-common", "io.helidon.common") { - exportAllPackages() - patchRealModule() - } - module("io.helidon.webclient:helidon-webclient", "io.helidon.webclient") { - requireAllDefinedDependencies() - patchRealModule() - } - module("io.helidon.webclient:helidon-webclient-grpc", "io.helidon.webclient.grpc") { - exportAllPackages() - requireAllDefinedDependencies() - patchRealModule() - } - module("com.github.spotbugs:spotbugs-annotations", "com.github.spotbugs.annotations") - module("com.google.code.findbugs:jsr305", "java.annotation") - module("com.google.protobuf:protobuf-java", "com.google.protobuf") { - exportAllPackages() - requireAllDefinedDependencies() - requires("java.logging") - } - module("com.google.guava:guava", "com.google.common") { - exportAllPackages() - requireAllDefinedDependencies() - requires("java.logging") - } - module("com.google.guava:failureaccess", "com.google.common.util.concurrent.internal") - module("com.google.api.grpc:proto-google-common-protos", "com.google.api.grpc.common") - module("com.google.dagger:dagger", "dagger") - module("io.perfmark:perfmark-api", "io.perfmark") - module("javax.inject:javax.inject", "javax.inject") - module("commons-codec:commons-codec", "org.apache.commons.codec") - module("com.esaulpaugh:headlong", "headlong") - module("org.checkerframework:checker-qual", "org.checkerframework.checker.qual") - module("org.connid:framework", "org.connid.framework") - module("org.connid:framework-internal", "org.connid.framework.internal") { - exportAllPackages() - requires("org.connid.framework") // this is missing in POM - } - module("org.jetbrains:annotations", "org.jetbrains.annotations") - module("io.tmio:tuweni-units", "tuweni.units") - module("io.tmio:tuweni-bytes", "tuweni.bytes") - module("net.i2p.crypto:eddsa", "net.i2p.crypto.eddsa") - module("io.netty:netty-codec-http", "io.netty.codec.http") - module("io.netty:netty-codec-http2", "io.netty.codec.http2") - module("io.netty:netty-codec-socks", "io.netty.codec.socks") - module("io.netty:netty-handler-proxy", "io.netty.handler.proxy") - module("io.netty:netty-transport-native-unix-common", "io.netty.transport.unix.common") - module("io.netty:netty-buffer", "io.netty.buffer") - module("io.netty:netty-codec", "io.netty.codec") - module("io.netty:netty-common", "io.netty.common") { - exportAllPackages() - requireAllDefinedDependencies() - requires("java.logging") - requires("jdk.unsupported") - ignoreServiceProvider("reactor.blockhound.integration.BlockHoundIntegration") - } - module("io.netty:netty-handler", "io.netty.handler") - module("io.netty:netty-resolver", "io.netty.resolver") - module("io.netty:netty-transport", "io.netty.transport") - module("io.netty:netty-transport-classes-epoll", "io.netty.transport.classes.epoll") - module("org.antlr:antlr4-runtime", "org.antlr.antlr4.runtime") - module("org.hyperledger.besu.internal:algorithms", "org.hyperledger.besu.internal.crypto") - module("org.hyperledger.besu.internal:rlp", "org.hyperledger.besu.internal.rlp") - module("org.hyperledger.besu:arithmetic", "org.hyperledger.besu.nativelib.arithmetic") - module("org.hyperledger.besu:blake2bf", "org.hyperledger.besu.nativelib.blake2bf") - module("org.hyperledger.besu:bls12-381", "org.hyperledger.besu.nativelib.bls12_381") - module("org.hyperledger.besu:besu-datatypes", "org.hyperledger.besu.datatypes") - module("org.hyperledger.besu:evm", "org.hyperledger.besu.evm") { - exportAllPackages() - requireAllDefinedDependencies() - requiresStatic("com.fasterxml.jackson.annotation") - } - module("org.hyperledger.besu:secp256k1", "org.hyperledger.besu.nativelib.secp256k1") - module("org.hyperledger.besu:secp256r1", "org.hyperledger.besu.nativelib.secp256r1") - module("com.goterl:resource-loader", "resource.loader") - module("com.goterl:lazysodium-java", "lazysodium.java") - module("tech.pegasys:jc-kzg-4844", "tech.pegasys.jckzg4844") - module("net.java.dev.jna:jna", "com.sun.jna") { - exportAllPackages() - requires("java.logging") - } - module("org.eclipse.collections:eclipse-collections-api", "org.eclipse.collections.api") - module("org.eclipse.collections:eclipse-collections", "org.eclipse.collections.impl") - module("io.prometheus:simpleclient", "io.prometheus.simpleclient") - module("io.prometheus:simpleclient_common", "io.prometheus.simpleclient_common") - module("io.prometheus:simpleclient_httpserver", "io.prometheus.simpleclient.httpserver") { - exportAllPackages() - requireAllDefinedDependencies() - requires("jdk.httpserver") - } - - // Need to use Jar file names here as there is currently no other way to address Jar with - // classifier directly for patching - module( - "io.netty:netty-transport-native-epoll|linux-x86_64", - "io.netty.transport.epoll.linux.x86_64" - ) - module( - "io.netty:netty-transport-native-epoll|linux-aarch_64", - "io.netty.transport.epoll.linux.aarch_64" - ) - - // Annotation processing only - module("com.google.auto.service:auto-service-annotations", "com.google.auto.service") - module("com.google.auto.service:auto-service", "com.google.auto.service.processor") - module("com.google.auto:auto-common", "com.google.auto.common") - module("com.google.dagger:dagger-compiler", "dagger.compiler") - module("com.google.dagger:dagger-producers", "dagger.producers") - module("com.google.dagger:dagger-spi", "dagger.spi") - module( - "com.google.devtools.ksp:symbol-processing-api", - "com.google.devtools.ksp.symbolprocessingapi" - ) - module("com.google.errorprone:javac-shaded", "com.google.errorprone.javac.shaded") - module("com.google.googlejavaformat:google-java-format", "com.google.googlejavaformat") - module("net.ltgt.gradle.incap:incap", "net.ltgt.gradle.incap") - module("org.jetbrains.kotlinx:kotlinx-metadata-jvm", "kotlinx.metadata.jvm") - - // Testing only - module("com.google.jimfs:jimfs", "com.google.jimfs") - module("org.awaitility:awaitility", "awaitility") - module("uk.org.webcompere:system-stubs-core", "uk.org.webcompere.systemstubs.core") - module("uk.org.webcompere:system-stubs-jupiter", "uk.org.webcompere.systemstubs.jupiter") - - // Test clients only - module("com.github.docker-java:docker-java-api", "com.github.dockerjava.api") - module("com.github.docker-java:docker-java-transport", "com.github.docker.java.transport") - module( - "com.github.docker-java:docker-java-transport-zerodep", - "com.github.docker.transport.zerodep" - ) - module("com.google.protobuf:protobuf-java-util", "com.google.protobuf.util") - module("com.squareup:javapoet", "com.squareup.javapoet") { - exportAllPackages() - requires("java.compiler") - } - module("junit:junit", "junit") - module("org.hamcrest:hamcrest", "org.hamcrest") - module("org.json:json", "org.json") - module("org.mockito:mockito-core", "org.mockito") - module("org.objenesis:objenesis", "org.objenesis") - module("org.rnorth.duct-tape:duct-tape", "org.rnorth.ducttape") - module("org.testcontainers:testcontainers", "org.testcontainers") - module("org.mockito:mockito-junit-jupiter", "org.mockito.junit.jupiter") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts deleted file mode 100644 index 1c74e298998c..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.lifecycle.gradle.kts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2016-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. - */ - -plugins { - id("base") - id("com.diffplug.spotless") -} - -// Convenience for local development: when running './gradlew' without any parameters just show the -// tasks from the 'build' group -defaultTasks("tasks") - -tasks.register("qualityGate") { - group = "build" - description = "Apply spotless rules and run all quality checks." - dependsOn(tasks.spotlessApply) - dependsOn(tasks.assemble) -} - -tasks.register("releaseMavenCentral") diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts deleted file mode 100644 index 32e73b2f57b3..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.maven-publish.gradle.kts +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2016-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. - */ - -import java.util.Properties - -plugins { - id("java") - id("maven-publish") - id("signing") - id("io.freefair.maven-central.validate-poms") -} - -tasks.withType().configureEach { - // Publishing tasks are only enabled if we publish to the matching group. - // Otherwise, Nexus configuration and credentials do not fit. - val publishingPackageGroup = providers.gradleProperty("publishingPackageGroup").orNull - enabled = publishingPackageGroup == project.group -} - -java { - withJavadocJar() - withSourcesJar() -} - -tasks.withType().configureEach { setGroup(null) } - -tasks.named("releaseMavenCentral") { - group = "release" - dependsOn(tasks.named("publishToSonatype")) -} - -val maven = - publishing.publications.create("maven") { - from(components["java"]) - versionMapping { - // Everything published takes the versions from the resolution result. - // These are the versions we define in 'hedera-dependency-versions' - // and use consistently in all modules. - allVariants { fromResolutionResult() } - } - - suppressAllPomMetadataWarnings() - - pom { - val devGroups = Properties() - val developerProperties = layout.projectDirectory.file("../developers.properties") - devGroups.load( - providers - .fileContents(developerProperties) - .asText - .orElse( - provider { - throw RuntimeException("${developerProperties.asFile} does not exist") - } - ) - .get() - .reader() - ) - - name.set(project.name) - url = "https://www.hashgraph.com/" - inceptionYear = "2016" - - // this field must be present. Default to empty string. - description = - providers - .fileContents(layout.projectDirectory.file("../description.txt")) - .asText - .orElse(provider(project::getDescription)) - .map { it.replace("\n", " ").trim() } - .orElse("") - - organization { - name = "Hedera Hashgraph, LLC" - url = "https://www.hedera.com" - } - - val repoName = isolated.rootProject.name - - issueManagement { - system = "GitHub" - url = "https://github.com/hashgraph/$repoName/issues" - } - - licenses { - license { - name = "Apache License, Version 2.0" - url = "https://raw.githubusercontent.com/hashgraph/$repoName/main/LICENSE" - } - } - - scm { - connection = "scm:git:git://github.com/hashgraph/$repoName.git" - developerConnection = "scm:git:ssh://github.com:hashgraph/$repoName.git" - url = "https://github.com/hashgraph/$repoName" - } - - developers { - devGroups.forEach { mail, team -> - developer { - id = team as String - name = team as String - email = mail as String - organization = "Hedera Hashgraph" - organizationUrl = "https://www.hedera.com" - } - } - } - } - } - -val publishSigningEnabled = - providers.gradleProperty("publishSigningEnabled").getOrElse("false").toBoolean() - -if (publishSigningEnabled) { - signing { - sign(maven) - useGpgCmd() - } -} - -tasks.named("qualityGate") { dependsOn(tasks.validatePomFiles) } diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.nexus-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.nexus-publish.gradle.kts deleted file mode 100644 index 429566cb793a..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.nexus-publish.gradle.kts +++ /dev/null @@ -1,53 +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. - */ - -plugins { - id("com.hedera.gradle.lifecycle") - id("io.github.gradle-nexus.publish-plugin") -} - -nexusPublishing { - val s01SonatypeHost = providers.gradleProperty("s01SonatypeHost").getOrElse("false").toBoolean() - packageGroup = providers.gradleProperty("publishingPackageGroup").getOrElse("") - - repositories { - sonatype { - username = System.getenv("NEXUS_USERNAME") - password = System.getenv("NEXUS_PASSWORD") - if (s01SonatypeHost) { - nexusUrl = uri("https://s01.oss.sonatype.org/service/local/") - snapshotRepositoryUrl = - uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") - } - } - } -} - -tasks.named("closeSonatypeStagingRepository") { - // The publishing of all components to Maven Central is automatically done before close (which - // is done before release). - dependsOn(subprojects.map { ":${it.name}:releaseMavenCentral" }) -} - -tasks.named("releaseMavenCentral") { - group = "release" - dependsOn(tasks.closeAndReleaseStagingRepository) -} - -tasks.register("releaseMavenCentralSnapshot") { - group = "release" - dependsOn(subprojects.map { ":${it.name}:releaseMavenCentral" }) -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts deleted file mode 100644 index e2983169da5f..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform-publish.gradle.kts +++ /dev/null @@ -1,59 +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. - */ - -plugins { - id("java") - id("com.hedera.gradle.maven-publish") -} - -if ( - gradle.startParameter.taskNames.any { it.startsWith("release") || it.contains("MavenCentral") } -) { - // We apply the 'artifactregistry' plugin conditionally, as there are two issues: - // 1. It does not support configuration cache. - // https://github.com/GoogleCloudPlatform/artifact-registry-maven-tools/issues/85 - // 2. It does not interact well with the 'gradle-nexus.publish-plugin' plugin, causing: - // 'No staging repository with name sonatype created' during IDE import - publishing.repositories.remove(publishing.repositories.getByName("sonatype")) - apply(plugin = "com.google.cloud.artifactregistry.gradle-plugin") -} - -publishing.repositories { - maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-prerelease-channel") { - name = "prereleaseChannel" - } - maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-snapshots") { - name = "developSnapshot" - } - maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-daily-snapshots") { - name = "developDailySnapshot" - } - maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-develop-commits") { - name = "developCommit" - } - maven("artifactregistry://us-maven.pkg.dev/swirlds-registry/maven-adhoc-commits") { - name = "adhocCommit" - } -} - -// Register one 'release*' task for each publishing repository -publishing.repositories.all { - val ucName = name.replaceFirstChar { it.titlecase() } - tasks.register("release$ucName") { - group = "release" - dependsOn(tasks.named("publishMavenPublicationTo${ucName}Repository")) - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts deleted file mode 100644 index b12ff59f1ee7..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.platform.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { - id("java-library") - id("com.hedera.gradle.java") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts deleted file mode 100644 index 1ef21c4c2ea0..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.protobuf.gradle.kts +++ /dev/null @@ -1,68 +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. - */ - -import com.google.protobuf.gradle.id - -plugins { - id("com.hedera.gradle.services") - id("com.google.protobuf") -} - -// Configure Protobuf Plugin to download protoc executable rather than using local installed version -protobuf { - protoc { artifact = "com.google.protobuf:protoc" } - plugins { - // Add GRPC plugin as we need to generate GRPC services - id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java" } - } - generateProtoTasks { - all().configureEach { plugins { id("grpc") { option("@generated=omit") } } } - } -} - -configurations.configureEach { - if (name.startsWith("protobufToolsLocator") || name.endsWith("ProtoPath")) { - @Suppress("UnstableApiUsage") - shouldResolveConsistentlyWith(configurations.getByName("mainRuntimeClasspath")) - attributes { attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_API)) } - exclude(group = project.group.toString(), module = project.name) - withDependencies { - isTransitive = true - extendsFrom(configurations["internal"]) - } - } -} - -tasks.javadoc { - options { - this as StandardJavadocDocletOptions - // There are violations in the generated protobuf code - addStringOption("Xdoclint:-reference,-html", "-quiet") - } -} - -// Give JUnit more ram and make it execute tests in parallel -tasks.test { - // We are running a lot of tests 10s of thousands, so they need to run in parallel. Make each - // class run in parallel. - systemProperties["junit.jupiter.execution.parallel.enabled"] = true - systemProperties["junit.jupiter.execution.parallel.mode.default"] = "concurrent" - // limit amount of threads, so we do not use all CPU - systemProperties["junit.jupiter.execution.parallel.config.dynamic.factor"] = "0.9" - // us parallel GC to keep up with high temporary garbage creation, - // and allow GC to use 40% of CPU if needed - jvmArgs("-XX:+UseParallelGC", "-XX:GCTimeRatio=90") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts deleted file mode 100644 index 3cef369974a5..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.reports.gradle.kts +++ /dev/null @@ -1,27 +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. - */ - -import org.gradlex.javamodule.dependencies.tasks.ModuleDirectivesScopeCheck - -plugins { - id("com.hedera.gradle.java") - id("jacoco-report-aggregation") -} - -tasks.withType { enabled = false } - -// Make aggregation "classpath" use the platform for versions (gradle/versions) -configurations.aggregateCodeCoverageReportResults { extendsFrom(configurations["internal"]) } diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts deleted file mode 100644 index 734cf240f7eb..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.repositories.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2016-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. - */ - -repositories { - mavenCentral() - maven { url = uri("https://us-maven.pkg.dev/swirlds-registry/maven-prerelease-channel") } - maven { url = uri("https://us-maven.pkg.dev/swirlds-registry/maven-develop-commits") } - maven { url = uri("https://us-maven.pkg.dev/swirlds-registry/maven-adhoc-commits") } - maven { url = uri("https://us-maven.pkg.dev/swirlds-registry/maven-develop-daily-snapshots") } - maven { url = uri("https://us-maven.pkg.dev/swirlds-registry/maven-develop-snapshots") } - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } - maven { - url = uri("https://hyperledger.jfrog.io/artifactory/besu-maven") - content { includeGroupByRegex("org\\.hyperledger\\..*") } - } - maven { - url = uri("https://artifacts.consensys.net/public/maven/maven/") - content { includeGroupByRegex("tech\\.pegasys(\\..*)?") } - } - maven { url = uri("https://oss.sonatype.org/content/repositories/comhederahashgraph-1502") } - maven { url = uri("https://oss.sonatype.org/content/repositories/comhederahashgraph-1531") } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts deleted file mode 100644 index a429661c9721..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.root.gradle.kts +++ /dev/null @@ -1,133 +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. - */ - -import com.hedera.gradle.utils.Utils.generateProjectVersionReport -import com.hedera.gradle.utils.Utils.versionTxt -import net.swiftzer.semver.SemVer - -plugins { - id("com.hedera.gradle.lifecycle") - id("com.hedera.gradle.repositories") - id("com.hedera.gradle.nexus-publish") - id("com.hedera.gradle.spotless-kotlin") - id("com.hedera.gradle.spotless-markdown") -} - -spotless { - kotlinGradle { target("gradle/plugins/**/*.gradle.kts") } - kotlin { - // For the Kotlin classes (*.kt files) - ktfmt().kotlinlangStyle() - target("gradle/plugins/**/*.kt") - } -} - -val productVersion = layout.projectDirectory.versionTxt().asFile.readText().trim() - -tasks.register("githubVersionSummary") { - group = "github" - - inputs.property("version", productVersion) - - if (!providers.environmentVariable("GITHUB_STEP_SUMMARY").isPresent) { - // Do not throw an exception if running the `gradlew tasks` task - if (project.gradle.startParameter.taskNames.contains("githubVersionSummary")) { - throw IllegalArgumentException( - "This task may only be run in a Github Actions CI environment! " + - "Unable to locate the GITHUB_STEP_SUMMARY environment variable." - ) - } - } - outputs.file(providers.environmentVariable("GITHUB_STEP_SUMMARY")) - - doLast { - generateProjectVersionReport( - inputs.properties["version"] as String, - outputs.files.singleFile.outputStream().buffered() - ) - } -} - -tasks.register("showVersion") { - group = "versioning" - - inputs.property("version", productVersion) - - doLast { println(inputs.properties["version"]) } -} - -tasks.register("versionAsPrefixedCommit") { - group = "versioning" - - @Suppress("UnstableApiUsage") - inputs.property( - "commit", - providers - .exec { commandLine("git", "rev-parse", "HEAD") } - .standardOutput - .asText - .map { it.trim().substring(0, 7) } - ) - inputs.property("commitPrefix", providers.gradleProperty("commitPrefix").orElse("adhoc")) - inputs.property("version", productVersion) - outputs.file(layout.projectDirectory.versionTxt()) - - doLast { - val newPrerel = - inputs.properties["commitPrefix"].toString() + - ".x" + - inputs.properties["commit"].toString().take(8) - val currVer = SemVer.parse(inputs.properties["version"] as String) - val newVer = SemVer(currVer.major, currVer.minor, currVer.patch, newPrerel) - outputs.files.singleFile.writeText(newVer.toString()) - } -} - -tasks.register("versionAsSnapshot") { - group = "versioning" - - inputs.property("version", productVersion) - outputs.file(layout.projectDirectory.versionTxt()) - - doLast { - val currVer = SemVer.parse(inputs.properties["version"] as String) - val newVer = SemVer(currVer.major, currVer.minor, currVer.patch, "SNAPSHOT") - - outputs.files.singleFile.writeText(newVer.toString()) - } -} - -tasks.register("versionAsSpecified") { - group = "versioning" - - inputs.property("newVersion", providers.gradleProperty("newVersion").orNull) - - if (inputs.properties["newVersion"] == null) { - // Do not throw an exception if running the `gradlew tasks` task - if (project.gradle.startParameter.taskNames.contains("versionAsSpecified")) { - throw IllegalArgumentException( - "No newVersion property provided! " + - "Please add the parameter -PnewVersion= when running this task." - ) - } - } - outputs.file(layout.projectDirectory.versionTxt()) - - doLast { - val newVer = SemVer.parse(inputs.properties["newVersion"] as String) - outputs.files.singleFile.writeText(newVer.toString()) - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts deleted file mode 100644 index 57ed149ed3de..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.services-publish.gradle.kts +++ /dev/null @@ -1,20 +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. - */ - -plugins { - id("java") - id("com.hedera.gradle.maven-publish") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts deleted file mode 100644 index b12ff59f1ee7..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.services.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { - id("java-library") - id("com.hedera.gradle.java") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts deleted file mode 100644 index 5da00ecb2321..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.settings.settings.gradle.kts +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import org.gradlex.javamodule.dependencies.initialization.JavaModulesExtension -import org.gradlex.javamodule.dependencies.initialization.RootPluginsExtension - -pluginManagement { - repositories { - gradlePluginPortal() - mavenCentral() - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } - } -} - -plugins { - id("com.gradle.develocity") - id("org.gradlex.java-module-dependencies") -} - -// Plugins that are global, but are applied to the "root project" instead of settings. -// by having this block here, we do not require a "build.gradle.kts" in the repository roots. -configure { id("com.hedera.gradle.root") } - -// Enable Gradle Build Scan -develocity { - buildScan { - termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" - termsOfUseAgree = "yes" - publishing.onlyIf { false } // only publish with explicit '--scan' - } -} - -val isCiServer = System.getenv().containsKey("CI") -val gradleCacheUsername: String? = System.getenv("GRADLE_CACHE_USERNAME") -val gradleCachePassword: String? = System.getenv("GRADLE_CACHE_PASSWORD") -val gradleCacheAuthorized = - (gradleCacheUsername?.isNotEmpty() ?: false) && (gradleCachePassword?.isNotEmpty() ?: false) - -buildCache { - remote { - url = uri("https://cache.gradle.hedera.svcs.eng.swirldslabs.io/cache/") - isPush = isCiServer && gradleCacheAuthorized - - isUseExpectContinue = true - isEnabled = !gradle.startParameter.isOffline - - if (isCiServer && gradleCacheAuthorized) { - credentials { - username = gradleCacheUsername - password = gradleCachePassword - } - } - } -} - -// Allow projects inside a build to be addressed by dependency coordinates notation. -// https://docs.gradle.org/current/userguide/composite_builds.html#included_build_declaring_substitutions -// Some functionality of the 'java-module-dependencies' plugin relies on this. -includeBuild(".") - -configure { - // Project to aggregate code coverage data for the whole repository into one report - module("gradle/aggregation") - - // "BOM" with versions of 3rd party dependencies - versions("hedera-dependency-versions") -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts deleted file mode 100644 index d2bf1148e4d5..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.shadow-jar.gradle.kts +++ /dev/null @@ -1,40 +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. - */ - -import com.github.jengelman.gradle.plugins.shadow.internal.DefaultDependencyFilter -import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar - -plugins { - id("java") - id("com.github.johnrengelman.shadow") -} - -tasks.withType().configureEach { - group = "shadow" - from(sourceSets.main.get().output) - mergeServiceFiles() - - // There is an issue in the shadow plugin that it automatically accesses the - // files in 'runtimeClasspath' while Gradle is building the task graph. - // See: https://github.com/johnrengelman/shadow/issues/882 - dependencyFilter = NoResolveDependencyFilter() -} - -class NoResolveDependencyFilter : DefaultDependencyFilter(project) { - override fun resolve(configuration: FileCollection): FileCollection { - return configuration - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts deleted file mode 100644 index 984b2cece001..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-java.gradle.kts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -import com.hedera.gradle.spotless.RepairDashedCommentsFormatterStep -import com.hedera.gradle.spotless.SortModuleInfoRequiresStep -import com.hedera.gradle.spotless.StripOldLicenseFormatterStep - -plugins { id("com.hedera.gradle.spotless") } - -spotless { - java { - targetExclude("build/generated/sources/**/*.java", "build/generated/source/**/*.java") - - // fix errors due to dashed comment blocks (eg: /*-, /*--, etc) - addStep(RepairDashedCommentsFormatterStep.create()) - // Remove the old license headers as the spotless licenseHeader formatter - // cannot find them if they are located between the package and import statements. - addStep(StripOldLicenseFormatterStep.create()) - // Sort the 'requires' entries in Module Info files - addStep(SortModuleInfoRequiresStep.create()) - // enable toggle comment support - toggleOffOn() - // don't need to set target, it is inferred from java - // apply a specific flavor of google-java-format - palantirJavaFormat() - // make sure every file has the following copyright header. - // optionally, Spotless can set copyright years by digging - // through git history (see "license" section below). - // The delimiter override below is required to support some - // of our test classes which are in the default package. - licenseHeader( - """ - /* - * Copyright (C) ${'$'}YEAR 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. - */${"\n\n"} - """ - .trimIndent(), - "(package|import)" - ) - .updateYearWithLatest(true) - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts deleted file mode 100644 index 68fcb3923015..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-kotlin.gradle.kts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { id("com.hedera.gradle.spotless") } - -spotless { - kotlinGradle { - ktfmt().kotlinlangStyle() - - licenseHeader( - """ - /* - * Copyright (C) ${'$'}YEAR 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. - */${"\n\n"} - """ - .trimIndent(), - "(import|plugins|pluginManagement|dependencyResolutionManagement|repositories|tasks|allprojects|subprojects)" - ) - .updateYearWithLatest(true) - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-markdown.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-markdown.gradle.kts deleted file mode 100644 index 1505c1a7c80e..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless-markdown.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { id("com.hedera.gradle.spotless") } - -spotless { - flexmark { - target("**/*.md") - flexmark() - trimTrailingWhitespace() - indentWithSpaces() - endWithNewline() - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts deleted file mode 100644 index 4c027596fe37..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.spotless.gradle.kts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2022-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. - */ - -plugins { id("com.diffplug.spotless") } - -spotless { - // Disable the automatic application of Spotless to all source sets when the check task is run. - isEnforceCheck = false - - // optional: limit format enforcement to just the files changed by this feature branch - ratchetFrom("origin/develop") - - format("misc") { - // define the files to apply `misc` to - target(".gitignore") - - // define the steps to apply to those files - trimTrailingWhitespace() - indentWithSpaces() - endWithNewline() - } - - format("actionYaml") { - target(".github/workflows/*.yaml") - /* - * Prettier requires NodeJS and NPM installed; however, the NodeJS Gradle plugin and Spotless do not yet - * integrate with each other. Currently there is an open issue report against spotless. - * - * *** Please see for more information: https://github.com/diffplug/spotless/issues/728 *** - * - * The workaround provided in the above issue does not work in Gradle 7.5+ and therefore is not a viable solution. - */ - // prettier() - - trimTrailingWhitespace() - indentWithSpaces() - endWithNewline() - - licenseHeader( - """ - ## - # Copyright (C) ${'$'}YEAR 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. - ##${"\n\n"} - """ - .trimIndent(), - "(name)" - ) - .updateYearWithLatest(true) - } -} diff --git a/gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts b/gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts deleted file mode 100644 index 661037d92d75..000000000000 --- a/gradle/plugins/src/main/kotlin/com.hedera.gradle.versions.gradle.kts +++ /dev/null @@ -1,25 +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. - */ - -plugins { - id("java-platform") - id("com.hedera.gradle.jpms-modules") - id("org.gradlex.java-module-versions") -} - -javaPlatform { allowDependencies() } - -tasks.register("releaseMavenCentral") diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt deleted file mode 100644 index 7b9b5c77aee0..000000000000 --- a/gradle/plugins/src/main/kotlin/com/hedera/gradle/services/TaskLockService.kt +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (C) 2022-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.gradle.services - -import org.gradle.api.services.BuildService -import org.gradle.api.services.BuildServiceParameters - -abstract class TaskLockService : BuildService diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt deleted file mode 100644 index 5a3efd96c1c2..000000000000 --- a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/RepairDashedCommentsFormatterStep.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2022-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.gradle.spotless - -import com.diffplug.spotless.FormatterFunc -import com.diffplug.spotless.FormatterStep - -/** - * Adds self-correcting behavior as spotless step which properly removes the comments which causes - * the google-java-formatter plugin to rupture (eg: \/\*-). - */ -class RepairDashedCommentsFormatterStep { - companion object { - private const val NAME = "RepairDashedComments" - private const val OPENING_COMMENT_REGEX = "/\\*-+" - private const val CLOSING_COMMENT_REGEX = "-+\\*/" - - fun create(): FormatterStep { - val openingCommentRegex = Regex(OPENING_COMMENT_REGEX, setOf(RegexOption.IGNORE_CASE)) - val closingCommentRegex = Regex(CLOSING_COMMENT_REGEX, setOf(RegexOption.IGNORE_CASE)) - return FormatterStep.create( - NAME, - State(openingCommentRegex, closingCommentRegex), - State::toFormatter - ) - } - } - - private class State(val openingCommentRegex: Regex, val closingCommentRegex: Regex) : - java.io.Serializable { - - fun toFormatter(): FormatterFunc { - return FormatterFunc { unixStr -> - val lines = unixStr.split('\n') - val result = ArrayList(lines.size) - var inLicenseBlock = false - - lines.forEach { s -> - if (!inLicenseBlock && s.trim().equals("/*-")) { - inLicenseBlock = true - } else if (inLicenseBlock && s.trim().equals("*/")) { - inLicenseBlock = false - } - - if (inLicenseBlock) { - result.add(s) - } else { - result.add( - s.replace(openingCommentRegex, "/*").replace(closingCommentRegex, "*/") - ) - } - } - - val finalStr = result.joinToString("\n") - finalStr - } - } - } -} diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/SortModuleInfoRequiresStep.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/SortModuleInfoRequiresStep.kt deleted file mode 100644 index e744208eb681..000000000000 --- a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/SortModuleInfoRequiresStep.kt +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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.gradle.spotless - -import com.diffplug.spotless.FormatterFunc -import com.diffplug.spotless.FormatterStep - -class SortModuleInfoRequiresStep { - companion object { - private const val NAME = "SortModuleInfoRequires" - private val OWN_PACKAGES = listOf("com.swirlds.", "com.hedera.node.", "com.hedera.storage.") - - fun create(): FormatterStep { - return FormatterStep.create(NAME, State(), State::toFormatter) - } - } - - private class State : java.io.Serializable { - - fun toFormatter(): FormatterFunc { - return FormatterFunc { unixStr -> - val lines = unixStr.split('\n') - val blockStartIndex = lines.indexOfFirst { it.trim().startsWith("requires") } - val blockEndIndex = lines.indexOfLast { it.trim().startsWith("requires") } - - if (blockStartIndex == -1) { - unixStr // not a module-info.java or no 'requires' - } else { - val nonRequiresLines = mutableListOf() - - val requiresTransitive = mutableListOf() - val requires = mutableListOf() - val requiresStaticTransitive = mutableListOf() - val requiresStatic = mutableListOf() - - lines.subList(blockStartIndex, blockEndIndex + 1).forEach { line -> - when { - line.trim().startsWith("requires static transitive") -> - requiresStaticTransitive.add(line) - line.trim().startsWith("requires static") -> requiresStatic.add(line) - line.trim().startsWith("requires transitive") -> - requiresTransitive.add(line) - line.trim().startsWith("requires") -> requires.add(line) - line.isNotBlank() && !line.trim().startsWith("requires") -> - nonRequiresLines.add(line) - } - } - - val comparator = - Comparator { a, b -> - val nameA = a.split(" ").first { it.endsWith(";") } - val nameB = b.split(" ").first { it.endsWith(";") } - if ( - OWN_PACKAGES.any { nameA.startsWith(it) } && - OWN_PACKAGES.none { nameB.startsWith(it) } - ) { - -1 - } else if ( - OWN_PACKAGES.none { nameA.startsWith(it) } && - OWN_PACKAGES.any { nameB.startsWith(it) } - ) { - 1 - } else { - nameA.compareTo(nameB) - } - } - - requiresTransitive.sortWith(comparator) - requires.sortWith(comparator) - requiresStaticTransitive.sortWith(comparator) - requiresStatic.sortWith(comparator) - - val blockStart = lines.subList(0, blockStartIndex) - val blockEnd = lines.subList(blockEndIndex + 1, lines.size) - - (blockStart + - nonRequiresLines + - requiresTransitive + - requires + - requiresStaticTransitive + - requiresStatic + - blockEnd) - .joinToString("\n") - } - } - } - } -} diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt deleted file mode 100644 index e9aedfac0bdb..000000000000 --- a/gradle/plugins/src/main/kotlin/com/hedera/gradle/spotless/StripOldLicenseFormatterStep.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2022-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.gradle.spotless - -import com.diffplug.spotless.FormatterFunc -import com.diffplug.spotless.FormatterStep - -/** - * Removes the old copyright statements which were incorrectly located between the package and - * import statements. These legacy copyright blocks also uses with an unexpected opening comment - * tag. This FormatterStep removes those comment blocks using a very conservative approach to avoid - * mutilating actual code. - */ -class StripOldLicenseFormatterStep { - companion object { - private const val NAME = "StripOldLicense" - - fun create(): FormatterStep { - return FormatterStep.create(NAME, State(), State::toFormatter) - } - } - - private class State : java.io.Serializable { - - fun toFormatter(): FormatterFunc { - return FormatterFunc { unixStr -> - val lines = unixStr.split('\n') - val result = ArrayList(lines.size) - var inComment = false - lines.forEach { s -> - if (!inComment && s.trim().startsWith("/*-")) { - inComment = true - } else if (inComment && s.trim().startsWith("*/")) { - inComment = false - } else if (!inComment) { - result.add(s) - } - } - - result.joinToString("\n") - } - } - } -} diff --git a/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt b/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt deleted file mode 100644 index 948ce171e8b6..000000000000 --- a/gradle/plugins/src/main/kotlin/com/hedera/gradle/utils/Utils.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2022-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.gradle.utils - -import java.io.OutputStream -import java.io.PrintStream -import org.gradle.api.file.Directory -import org.gradle.api.file.RegularFile - -object Utils { - // Find the version.txt in the root of the repository, independent of - // which build is started from where. - @JvmStatic - fun Directory.versionTxt(): RegularFile = - file("version.txt").let { if (it.asFile.exists()) it else this.dir("..").versionTxt() } - - @JvmStatic - fun generateProjectVersionReport(version: String, ostream: OutputStream) { - val writer = PrintStream(ostream, false, Charsets.UTF_8) - - ostream.use { - writer.use { - // Writer headers - writer.println("### Deployed Version Information") - writer.println() - writer.println("| Artifact Name | Version Number |") - writer.println("| --- | --- |") - // Write table rows - writer.printf("| %s | %s |\n", "hedera-node", version) - writer.printf("| %s | %s |\n", "platform-sdk", version) - writer.flush() - ostream.flush() - } - } - } -} diff --git a/gradle/toolchain-versions.properties b/gradle/toolchain-versions.properties new file mode 100644 index 000000000000..04e458faeff5 --- /dev/null +++ b/gradle/toolchain-versions.properties @@ -0,0 +1 @@ +jdk=21.0.4 diff --git a/hapi/build.gradle.kts b/hapi/build.gradle.kts index 4f27c86b57eb..812f5752b0ea 100644 --- a/hapi/build.gradle.kts +++ b/hapi/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * Copyright (C) 2022-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.protobuf") - id("com.hedera.gradle.services-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.protobuf") + id("org.hiero.gradle.feature.test-fixtures") id("com.hedera.pbj.pbj-compiler") version "0.9.2" } @@ -56,3 +41,15 @@ testModuleInfo { requires("org.junit.jupiter.api") requires("org.junit.jupiter.params") } + +tasks.test { + // We are running a lot of tests (10s of thousands), so they need to run in parallel. Make each + // class run in parallel. + systemProperties["junit.jupiter.execution.parallel.enabled"] = true + systemProperties["junit.jupiter.execution.parallel.mode.default"] = "concurrent" + // limit amount of threads, so we do not use all CPU + systemProperties["junit.jupiter.execution.parallel.config.dynamic.factor"] = "0.9" + // us parallel GC to keep up with high temporary garbage creation, + // and allow GC to use 40% of CPU if needed + jvmArgs("-XX:+UseParallelGC", "-XX:GCTimeRatio=90") +} diff --git a/hedera-dependency-versions/build.gradle.kts b/hedera-dependency-versions/build.gradle.kts deleted file mode 100644 index 5b08c4be413f..000000000000 --- a/hedera-dependency-versions/build.gradle.kts +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (C) 2023 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. - */ - -plugins { - id("com.hedera.gradle.versions") -} - -dependencies { - api(enforcedPlatform("io.netty:netty-bom:4.1.110.Final")) - - // forward logging from modules using SLF4J (e.g. 'org.hyperledger.besu.evm') to Log4J - runtime("org.apache.logging.log4j:log4j-slf4j2-impl") { - because("org.apache.logging.log4j.slf4j2.impl") - } -} - -dependencies.constraints { - api("io.helidon.common:helidon-common:4.1.1") { - because("io.helidon.common") - } - api("io.helidon.webclient:helidon-webclient:4.1.1") { - because("io.helidon.webclient") - } - api("io.helidon.webclient:helidon-webclient-grpc:4.1.1") { - because("io.helidon.webclient.grpc") - } - api("org.awaitility:awaitility:4.2.0") { - because("awaitility") - } - api("com.fasterxml.jackson.core:jackson-core:2.16.0") { - because("com.fasterxml.jackson.core") - } - api("com.fasterxml.jackson.core:jackson-databind:2.16.0") { - because("com.fasterxml.jackson.databind") - } - api("com.github.ben-manes.caffeine:caffeine:3.1.1") { - because("com.github.benmanes.caffeine") - } - api("com.github.docker-java:docker-java-api:3.2.13") { - because("com.github.dockerjava.api") - } - api("com.github.spotbugs:spotbugs-annotations:4.7.3") { - because("com.github.spotbugs.annotations") - } - api("com.google.auto.service:auto-service-annotations:1.1.1") { - because("com.google.auto.service") - } - api("com.google.auto.service:auto-service:1.1.1") { - because("com.google.auto.service.processor") - } - api("com.google.guava:guava:33.1.0-jre") { - because("com.google.common") - } - api("com.google.j2objc:j2objc-annotations:3.0.0") { - because("com.google.j2objc.annotations") - } - api("com.google.jimfs:jimfs:1.2") { - because("com.google.jimfs") - } - api("com.google.protobuf:protobuf-java:4.28.2") { - because("com.google.protobuf") - } - api("com.google.protobuf:protobuf-java-util:4.28.2") { - because("com.google.protobuf.util") - } - api("com.hedera.pbj:pbj-runtime:0.9.2") { - because("com.hedera.pbj.runtime") - } - api("com.squareup:javapoet:1.13.0") { - because("com.squareup.javapoet") - } - api("net.java.dev.jna:jna:5.12.1") { - because("com.sun.jna") - } - api("com.google.dagger:dagger:2.42") { - because("dagger") - } - api("com.google.dagger:dagger-compiler:2.42") { - because("dagger.compiler") - } - api("io.grpc:grpc-netty:1.64.0") { - because("io.grpc.netty") - } - api("io.grpc:grpc-protobuf:1.64.0") { - because("io.grpc.protobuf") - } - api("io.grpc:grpc-stub:1.64.0") { - because("io.grpc.stub") - } - api("com.esaulpaugh:headlong:6.1.1") { - because("headlong") - } - api("info.picocli:picocli:4.6.3") { - because("info.picocli") - } - api("io.github.classgraph:classgraph:4.8.65") { - because("io.github.classgraph") - } - api("io.netty:netty-handler:4.1.115.Final") { - because("io.netty.handler") - } - api("io.netty:netty-transport:4.1.111.Final") { - because("io.netty.transport") - } - api("io.netty:netty-transport-classes-epoll:4.1.111.Final") { - because("io.netty.transport.classes.epoll") - } - api("io.perfmark:perfmark-api:0.25.0") { - because("io.perfmark") - } - api("io.prometheus:simpleclient:0.16.0") { - because("io.prometheus.simpleclient") - } - api("io.prometheus:simpleclient_httpserver:0.16.0") { - because("io.prometheus.simpleclient.httpserver") - } - api("jakarta.inject:jakarta.inject-api:2.0.1") { - because("jakarta.inject") - } - api("javax.inject:javax.inject:1") { - because("javax.inject") - } - api("com.goterl:lazysodium-java:5.1.4") { - because("lazysodium.java") - } - api("net.i2p.crypto:eddsa:0.3.0") { - because("net.i2p.crypto.eddsa") - } - api("org.antlr:antlr4-runtime:4.13.1") { - because("org.antlr.antlr4.runtime") - } - api("commons-codec:commons-codec:1.15") { - because("org.apache.commons.codec") - } - api("org.apache.commons:commons-collections4:4.4") { - because("org.apache.commons.collections4") - } - api("commons-io:commons-io:2.15.1") { - because("org.apache.commons.io") - } - api("org.apache.commons:commons-lang3:3.14.0") { - because("org.apache.commons.lang3") - } - api("org.apache.commons:commons-compress:1.26.0") { - because("org.apache.commons.compress") - } - api("org.apache.logging.log4j:log4j-api:2.21.1") { - because("org.apache.logging.log4j") - } - api("org.apache.logging.log4j:log4j-core:2.21.1") { - because("org.apache.logging.log4j.core") - } - api("org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1") { - because("org.apache.logging.log4j.slf4j2.impl") - } - api("org.assertj:assertj-core:3.23.1") { - because("org.assertj.core") - } - api("org.bouncycastle:bcpkix-jdk18on:1.79") { - because("org.bouncycastle.pkix") - } - api("org.bouncycastle:bcprov-jdk18on:1.78") { - because("org.bouncycastle.provider") - } - api("org.eclipse.collections:eclipse-collections-api:10.4.0") { - because("org.eclipse.collections.api") - } - api("org.eclipse.collections:eclipse-collections:10.4.0") { - because("org.eclipse.collections.impl") - } - api("org.hamcrest:hamcrest:2.2") { - because("org.hamcrest") - } - api("org.hyperledger.besu:besu-datatypes:24.3.3") { - because("org.hyperledger.besu.datatypes") - } - api("org.hyperledger.besu:evm:24.3.3") { - because("org.hyperledger.besu.evm") - } - api("org.hyperledger.besu:secp256k1:0.8.2") { - because("org.hyperledger.besu.nativelib.secp256k1") - } - api("org.json:json:20231013") { - because("org.json") - } - api("org.junit.jupiter:junit-jupiter-api:5.10.2") { - because("org.junit.jupiter.api") - } - api("org.junit.jupiter:junit-jupiter-engine:5.10.2") { - because("org.junit.jupiter.engine") - } - api("org.junit-pioneer:junit-pioneer:2.0.1") { - because("org.junitpioneer") - } - api("org.mockito:mockito-core:5.8.0") { - because("org.mockito") - } - api("org.mockito:mockito-junit-jupiter:5.8.0") { - because("org.mockito.junit.jupiter") - } - api("org.opentest4j:opentest4j:1.2.0") { - because("org.opentest4j") - } - api("org.testcontainers:testcontainers:1.17.2") { - because("org.testcontainers") - } - api("org.testcontainers:junit-jupiter:1.17.2") { - because("org.testcontainers.junit.jupiter") - } - api("org.yaml:snakeyaml:2.2") { - because("org.yaml.snakeyaml") - } - api("io.tmio:tuweni-bytes:2.4.2") { - because("tuweni.bytes") - } - api("io.tmio:tuweni-units:2.4.2") { - because("tuweni.units") - } - api("uk.org.webcompere:system-stubs-core:2.1.5") { - because("uk.org.webcompere.systemstubs.core") - } - api("uk.org.webcompere:system-stubs-jupiter:2.1.5") { - because("uk.org.webcompere.systemstubs.jupiter") - } - api("com.google.protobuf:protoc:3.25.4") - api("io.grpc:protoc-gen-grpc-java:1.66.0") - - api("com.hedera.cryptography:hedera-cryptography-pairings-api:0.1.1-SNAPSHOT") { - because("com.hedera.cryptography.pairings.api") - } - api("com.hedera.cryptography:hedera-cryptography-altbn128:0.1.1-SNAPSHOT") { - because("com.hedera.cryptography.altbn128") - } - api("com.hedera.cryptography:hedera-cryptography-bls:0.1.1-SNAPSHOT") { - because("com.hedera.cryptography.bls") - } - api("com.hedera.cryptography:hedera-cryptography-tss:0.1.1-SNAPSHOT") { - because("com.hedera.cryptography.tss") - } -} diff --git a/hedera-node/hapi-fees/build.gradle.kts b/hedera-node/hapi-fees/build.gradle.kts index 05a8a548c047..46e7dc30a6c9 100644 --- a/hedera-node/hapi-fees/build.gradle.kts +++ b/hedera-node/hapi-fees/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Services API Fees" diff --git a/hedera-node/hapi-utils/build.gradle.kts b/hedera-node/hapi-utils/build.gradle.kts index 8efcebff1949..c5a12228e970 100644 --- a/hedera-node/hapi-utils/build.gradle.kts +++ b/hedera-node/hapi-utils/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Services API Utilities" diff --git a/hedera-node/hapi-utils/src/main/java/module-info.java b/hedera-node/hapi-utils/src/main/java/module-info.java index d1e778ce3a49..c86fcb960ade 100644 --- a/hedera-node/hapi-utils/src/main/java/module-info.java +++ b/hedera-node/hapi-utils/src/main/java/module-info.java @@ -16,11 +16,11 @@ exports com.hedera.node.app.hapi.utils.sysfiles.validation; requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common; + requires transitive com.esaulpaugh.headlong; requires transitive com.google.protobuf; - requires transitive com.hedera.pbj.runtime; requires transitive dagger; - requires transitive headlong; requires transitive java.compiler; requires transitive javax.inject; requires transitive net.i2p.crypto.eddsa; diff --git a/hedera-node/hedera-addressbook-service-impl/build.gradle.kts b/hedera-node/hedera-addressbook-service-impl/build.gradle.kts index 6c467b8d103b..2488f678ea55 100644 --- a/hedera-node/hedera-addressbook-service-impl/build.gradle.kts +++ b/hedera-node/hedera-addressbook-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera AddressBook Service Implementation" diff --git a/hedera-node/hedera-addressbook-service/build.gradle.kts b/hedera-node/hedera-addressbook-service/build.gradle.kts index 2d0fd03761ac..4a6257b4d836 100644 --- a/hedera-node/hedera-addressbook-service/build.gradle.kts +++ b/hedera-node/hedera-addressbook-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera AddressBook Service API" diff --git a/hedera-node/hedera-app-spi/build.gradle.kts b/hedera-node/hedera-app-spi/build.gradle.kts index 15c75ed4f248..4b9eb3f1c42c 100644 --- a/hedera-node/hedera-app-spi/build.gradle.kts +++ b/hedera-node/hedera-app-spi/build.gradle.kts @@ -1,23 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.test-fixtures") } description = "Hedera Application - SPI" diff --git a/hedera-node/hedera-app/build.gradle.kts b/hedera-node/hedera-app/build.gradle.kts index 6fed947beaa7..ebcf3e45a546 100644 --- a/hedera-node/hedera-app/build.gradle.kts +++ b/hedera-node/hedera-app/build.gradle.kts @@ -1,24 +1,8 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") } description = "Hedera Application - Implementation" @@ -48,7 +32,7 @@ testModuleInfo { requires("com.swirlds.state.api.test.fixtures") requires("com.swirlds.state.impl.test.fixtures") requires("com.swirlds.base.test.fixtures") - requires("headlong") + requires("com.esaulpaugh.headlong") requires("org.assertj.core") requires("org.bouncycastle.provider") requires("org.junit.jupiter.api") 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 d8f36a06acf0..4a1d2c813bbe 100644 --- a/hedera-node/hedera-app/src/main/java/module-info.java +++ b/hedera-node/hedera-app/src/main/java/module-info.java @@ -2,6 +2,9 @@ import com.swirlds.config.api.ConfigurationExtension; module com.hedera.node.app { + requires transitive com.hedera.cryptography.bls; + requires transitive com.hedera.cryptography.pairings.api; + requires transitive com.hedera.cryptography.tss; requires transitive com.hedera.node.app.hapi.utils; requires transitive com.hedera.node.app.service.addressbook.impl; requires transitive com.hedera.node.app.service.addressbook; @@ -17,16 +20,13 @@ requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.config; requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common; requires transitive com.swirlds.config.api; requires transitive com.swirlds.metrics.api; requires transitive com.swirlds.platform.core; requires transitive com.swirlds.state.api; requires transitive com.swirlds.state.impl; - requires transitive com.hedera.cryptography.bls; - requires transitive com.hedera.cryptography.pairings.api; - requires transitive com.hedera.cryptography.tss; - requires transitive com.hedera.pbj.runtime; requires transitive dagger; requires transitive io.grpc.stub; requires transitive javax.inject; diff --git a/hedera-node/hedera-config/build.gradle.kts b/hedera-node/hedera-config/build.gradle.kts index 7485155dc32f..2c631f903e67 100644 --- a/hedera-node/hedera-config/build.gradle.kts +++ b/hedera-node/hedera-config/build.gradle.kts @@ -1,23 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.test-fixtures") } description = "Hedera Configuration" diff --git a/hedera-node/hedera-consensus-service-impl/build.gradle.kts b/hedera-node/hedera-consensus-service-impl/build.gradle.kts index 67f55402d6f2..6b62f1b9378d 100644 --- a/hedera-node/hedera-consensus-service-impl/build.gradle.kts +++ b/hedera-node/hedera-consensus-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Consensus Service Implementation" diff --git a/hedera-node/hedera-consensus-service/build.gradle.kts b/hedera-node/hedera-consensus-service/build.gradle.kts index 04a3aa43ffc0..b9cb9549942e 100644 --- a/hedera-node/hedera-consensus-service/build.gradle.kts +++ b/hedera-node/hedera-consensus-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Consensus Service API" diff --git a/hedera-node/hedera-file-service-impl/build.gradle.kts b/hedera-node/hedera-file-service-impl/build.gradle.kts index 0eeaea14bdac..70616c177b01 100644 --- a/hedera-node/hedera-file-service-impl/build.gradle.kts +++ b/hedera-node/hedera-file-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera File Service Implementation" diff --git a/hedera-node/hedera-file-service/build.gradle.kts b/hedera-node/hedera-file-service/build.gradle.kts index 8a4a2c69c71a..4052e8cbec2f 100644 --- a/hedera-node/hedera-file-service/build.gradle.kts +++ b/hedera-node/hedera-file-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera File Service API" 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 07590e13359a..1ee96862d441 100644 --- a/hedera-node/hedera-network-admin-service-impl/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Network Admin Service Implementation" diff --git a/hedera-node/hedera-network-admin-service/build.gradle.kts b/hedera-node/hedera-network-admin-service/build.gradle.kts index e4844ed09c55..a68487744e76 100644 --- a/hedera-node/hedera-network-admin-service/build.gradle.kts +++ b/hedera-node/hedera-network-admin-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera NetworkAdmin Service API" diff --git a/hedera-node/hedera-schedule-service-impl/build.gradle.kts b/hedera-node/hedera-schedule-service-impl/build.gradle.kts index 149f263fe93f..5a076bb811e5 100644 --- a/hedera-node/hedera-schedule-service-impl/build.gradle.kts +++ b/hedera-node/hedera-schedule-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Schedule Service Implementation" diff --git a/hedera-node/hedera-schedule-service/build.gradle.kts b/hedera-node/hedera-schedule-service/build.gradle.kts index b187f3d2a100..7e5424f5e6e9 100644 --- a/hedera-node/hedera-schedule-service/build.gradle.kts +++ b/hedera-node/hedera-schedule-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Schedule Service API" diff --git a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts index 694111ddf40e..55fad9948bde 100644 --- a/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts +++ b/hedera-node/hedera-smart-contract-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Smart Contract Service Implementation" diff --git a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java index 67f84fa6aea4..a8fd4cac7bfa 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/main/java/module-info.java @@ -11,12 +11,12 @@ requires transitive com.hedera.node.app.spi; requires transitive com.hedera.node.config; requires transitive com.hedera.node.hapi; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.config.api; requires transitive com.swirlds.metrics.api; requires transitive com.swirlds.state.api; - requires transitive com.hedera.pbj.runtime; + requires transitive com.esaulpaugh.headlong; requires transitive dagger; - requires transitive headlong; requires transitive javax.inject; requires transitive org.apache.logging.log4j; requires transitive org.hyperledger.besu.datatypes; diff --git a/hedera-node/hedera-smart-contract-service/build.gradle.kts b/hedera-node/hedera-smart-contract-service/build.gradle.kts index d855b687dbf0..408ec1528a01 100644 --- a/hedera-node/hedera-smart-contract-service/build.gradle.kts +++ b/hedera-node/hedera-smart-contract-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Smart Contract Service API" diff --git a/hedera-node/hedera-token-service-impl/build.gradle.kts b/hedera-node/hedera-token-service-impl/build.gradle.kts index 7c29af184a48..57f0dc52559c 100644 --- a/hedera-node/hedera-token-service-impl/build.gradle.kts +++ b/hedera-node/hedera-token-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Token Service Implementation" diff --git a/hedera-node/hedera-token-service/build.gradle.kts b/hedera-node/hedera-token-service/build.gradle.kts index f8e09e6d5f3f..aa46421f1e9c 100644 --- a/hedera-node/hedera-token-service/build.gradle.kts +++ b/hedera-node/hedera-token-service/build.gradle.kts @@ -1,23 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.test-fixtures") } description = "Hedera Token Service API" diff --git a/hedera-node/hedera-util-service-impl/build.gradle.kts b/hedera-node/hedera-util-service-impl/build.gradle.kts index e76f4f360ac1..203d36538fff 100644 --- a/hedera-node/hedera-util-service-impl/build.gradle.kts +++ b/hedera-node/hedera-util-service-impl/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Default Hedera Util Service Implementation" diff --git a/hedera-node/hedera-util-service/build.gradle.kts b/hedera-node/hedera-util-service/build.gradle.kts index 3ce441d52601..ed5583a0b20b 100644 --- a/hedera-node/hedera-util-service/build.gradle.kts +++ b/hedera-node/hedera-util-service/build.gradle.kts @@ -1,23 +1,5 @@ -/* - * 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. - */ - -plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") -} +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.library") } description = "Hedera Util Service API" diff --git a/hedera-node/test-clients/build.gradle.kts b/hedera-node/test-clients/build.gradle.kts index d1917f4b9be6..85d1781bb14d 100644 --- a/hedera-node/test-clients/build.gradle.kts +++ b/hedera-node/test-clients/build.gradle.kts @@ -1,24 +1,10 @@ -/* - * Copyright (C) 2022-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. - */ - +// SPDX-License-Identifier: Apache-2.0 +import com.github.jengelman.gradle.plugins.shadow.internal.DefaultDependencyFilter import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.shadow-jar") + id("org.hiero.gradle.module.application") + id("com.gradleup.shadow") version "8.3.0" } description = "Hedera Services Test Clients for End to End Tests (EET)" @@ -37,6 +23,8 @@ sourceSets { create("yahcli") } +tasks.withType().configureEach { options.compilerArgs.add("-Xlint:-exports") } + tasks.register("runTestClient") { group = "build" description = "Run a test client via -PtestClient=" @@ -271,17 +259,23 @@ tasks.register("testRepeatable") { modularity.inferModulePath.set(false) } -tasks.shadowJar { - archiveFileName.set("SuiteRunner.jar") +tasks.withType().configureEach { + group = "shadow" + from(sourceSets.main.get().output) + mergeServiceFiles() - manifest { - attributes( - "Main-Class" to "com.hedera.services.bdd.suites.SuiteRunner", - "Multi-Release" to "true" - ) - } + manifest { attributes("Multi-Release" to "true") } + + // There is an issue in the shadow plugin that it automatically accesses the + // files in 'runtimeClasspath' while Gradle is building the task graph. + // See: https://github.com/GradleUp/shadow/issues/882 + dependencyFilter = NoResolveDependencyFilter() } +application.mainClass = "com.hedera.services.bdd.suites.SuiteRunner" + +tasks.shadowJar { archiveFileName.set("SuiteRunner.jar") } + val yahCliJar = tasks.register("yahCliJar") { exclude(listOf("META-INF/*.DSA", "META-INF/*.RSA", "META-INF/*.SF", "META-INF/INDEX.LIST")) @@ -289,12 +283,7 @@ val yahCliJar = archiveClassifier.set("yahcli") configurations = listOf(project.configurations.getByName("yahcliRuntimeClasspath")) - manifest { - attributes( - "Main-Class" to "com.hedera.services.yahcli.Yahcli", - "Multi-Release" to "true" - ) - } + manifest { attributes("Main-Class" to "com.hedera.services.yahcli.Yahcli") } } val rcdiffJar = @@ -306,12 +295,7 @@ val rcdiffJar = archiveFileName.set("rcdiff.jar") configurations = listOf(project.configurations.getByName("rcdiffRuntimeClasspath")) - manifest { - attributes( - "Main-Class" to "com.hedera.services.rcdiff.RcDiffCmdWrapper", - "Multi-Release" to "true" - ) - } + manifest { attributes("Main-Class" to "com.hedera.services.rcdiff.RcDiffCmdWrapper") } } val validationJar = @@ -323,8 +307,7 @@ val validationJar = manifest { attributes( "Main-Class" to - "com.hedera.services.bdd.suites.utils.validation.ValidationScenarios", - "Multi-Release" to "true" + "com.hedera.services.bdd.suites.utils.validation.ValidationScenarios" ) } } @@ -360,3 +343,9 @@ tasks.clean { dependsOn(cleanYahCli) dependsOn(cleanValidation) } + +class NoResolveDependencyFilter : DefaultDependencyFilter(project) { + override fun resolve(configuration: FileCollection): FileCollection { + return configuration + } +} diff --git a/hedera-node/test-clients/src/main/java/module-info.java b/hedera-node/test-clients/src/main/java/module-info.java index 9a86ac44db27..3ccf526dd253 100644 --- a/hedera-node/test-clients/src/main/java/module-info.java +++ b/hedera-node/test-clients/src/main/java/module-info.java @@ -64,33 +64,11 @@ exports com.hedera.services.bdd.utils; exports com.hedera.services.bdd.junit.restart; - requires transitive com.hedera.node.app.hapi.fees; - requires transitive com.hedera.node.app.hapi.utils; - requires transitive com.hedera.node.app.spi; - requires transitive com.hedera.node.app.test.fixtures; - requires transitive com.hedera.node.app; - requires transitive com.hedera.node.config; - requires transitive com.hedera.node.hapi; - requires transitive com.swirlds.base; - requires transitive com.swirlds.common; - requires transitive com.swirlds.config.api; - requires transitive com.swirlds.metrics.api; - requires transitive com.swirlds.platform.core; - requires transitive com.swirlds.state.api; - requires transitive com.fasterxml.jackson.annotation; - requires transitive com.google.common; - requires transitive com.google.protobuf; - requires transitive com.hedera.cryptography.bls; - requires transitive com.hedera.cryptography.tss; - requires transitive headlong; - requires transitive io.grpc; - requires transitive net.i2p.crypto.eddsa; - requires transitive org.apache.commons.io; - requires transitive org.apache.logging.log4j; - requires transitive org.junit.jupiter.api; - requires transitive org.junit.platform.launcher; - requires transitive org.testcontainers; - requires transitive tuweni.bytes; + requires com.hedera.cryptography.bls; + requires com.hedera.cryptography.pairings.api; + requires com.hedera.cryptography.tss; + requires com.hedera.node.app.hapi.fees; + requires com.hedera.node.app.hapi.utils; requires com.hedera.node.app.service.addressbook.impl; requires com.hedera.node.app.service.addressbook; requires com.hedera.node.app.service.contract.impl; @@ -98,34 +76,56 @@ requires com.hedera.node.app.service.schedule; requires com.hedera.node.app.service.token.impl; requires com.hedera.node.app.service.token; + requires com.hedera.node.app.spi; + requires com.hedera.node.app.test.fixtures; + requires com.hedera.node.app; + requires com.hedera.node.config; + requires com.hedera.node.hapi; requires com.swirlds.base.test.fixtures; + requires com.swirlds.base; + requires com.swirlds.common; + requires com.swirlds.config.api; requires com.swirlds.merkledb; + requires com.swirlds.metrics.api; requires com.swirlds.platform.core.test.fixtures; + requires com.swirlds.platform.core; + requires com.swirlds.state.api; requires com.swirlds.state.impl; requires com.swirlds.virtualmap; + requires com.esaulpaugh.headlong; + requires com.fasterxml.jackson.annotation; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; requires com.github.dockerjava.api; - requires com.hedera.cryptography.pairings.api; + requires com.google.common; + requires com.google.protobuf; requires com.sun.jna; requires io.grpc.netty; requires io.grpc.stub; + requires io.grpc; requires io.netty.handler; requires java.desktop; requires java.net.http; + requires net.i2p.crypto.eddsa; + requires org.apache.commons.io; requires org.apache.commons.lang3; requires org.apache.logging.log4j.core; + requires org.apache.logging.log4j; requires org.assertj.core; requires org.bouncycastle.provider; requires org.hyperledger.besu.datatypes; requires org.hyperledger.besu.internal.crypto; requires org.hyperledger.besu.nativelib.secp256k1; requires org.json; + requires org.junit.jupiter.api; requires org.junit.platform.commons; + requires org.junit.platform.launcher; requires org.opentest4j; + requires org.testcontainers; requires org.yaml.snakeyaml; + requires tuweni.bytes; requires tuweni.units; - requires static com.github.spotbugs.annotations; requires static com.hedera.pbj.runtime; + requires static com.github.spotbugs.annotations; requires static org.junit.platform.engine; } diff --git a/hedera-node/test-clients/src/yahcli/java/module-info.java b/hedera-node/test-clients/src/yahcli/java/module-info.java index c0e5cc0e5bd3..be9118c3c2ad 100644 --- a/hedera-node/test-clients/src/yahcli/java/module-info.java +++ b/hedera-node/test-clients/src/yahcli/java/module-info.java @@ -7,6 +7,7 @@ requires com.swirlds.common; requires com.github.spotbugs.annotations; requires com.google.common; + requires com.google.protobuf; requires info.picocli; requires net.i2p.crypto.eddsa; requires org.apache.logging.log4j; diff --git a/hiero-dependency-versions/build.gradle.kts b/hiero-dependency-versions/build.gradle.kts new file mode 100644 index 000000000000..540699572d2e --- /dev/null +++ b/hiero-dependency-versions/build.gradle.kts @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: Apache-2.0 +plugins { + id("org.hiero.gradle.base.lifecycle") + id("org.hiero.gradle.base.jpms-modules") + id("org.hiero.gradle.check.spotless") + id("org.hiero.gradle.check.spotless-kotlin") +} + +dependencies { + api(enforcedPlatform("io.netty:netty-bom:4.1.110.Final")) + + // forward logging from modules using SLF4J (e.g. 'org.hyperledger.besu.evm') to Log4J + runtime("org.apache.logging.log4j:log4j-slf4j2-impl") { + because("org.apache.logging.log4j.slf4j2.impl") + } +} + +dependencies.constraints { + api("io.helidon.common:helidon-common:4.1.1") { because("io.helidon.common") } + api("io.helidon.webclient:helidon-webclient:4.1.1") { because("io.helidon.webclient") } + api("io.helidon.webclient:helidon-webclient-grpc:4.1.1") { + because("io.helidon.webclient.grpc") + } + api("org.awaitility:awaitility:4.2.0") { because("awaitility") } + api("com.fasterxml.jackson.core:jackson-core:2.16.0") { because("com.fasterxml.jackson.core") } + api("com.fasterxml.jackson.core:jackson-databind:2.16.0") { + because("com.fasterxml.jackson.databind") + } + api("com.github.ben-manes.caffeine:caffeine:3.1.1") { because("com.github.benmanes.caffeine") } + api("com.github.docker-java:docker-java-api:3.2.13") { because("com.github.dockerjava.api") } + api("com.github.spotbugs:spotbugs-annotations:4.7.3") { + because("com.github.spotbugs.annotations") + } + api("com.google.auto.service:auto-service-annotations:1.1.1") { + because("com.google.auto.service") + } + api("com.google.auto.service:auto-service:1.1.1") { + because("com.google.auto.service.processor") + } + api("com.google.guava:guava:33.3.1-jre") { because("com.google.common") } + api("com.google.j2objc:j2objc-annotations:3.0.0") { because("com.google.j2objc.annotations") } + api("com.google.jimfs:jimfs:1.2") { because("com.google.jimfs") } + api("com.google.protobuf:protobuf-java:4.28.2") { because("com.google.protobuf") } + api("com.google.protobuf:protobuf-java-util:4.28.2") { because("com.google.protobuf.util") } + api("com.hedera.pbj:pbj-runtime:0.9.2") { because("com.hedera.pbj.runtime") } + api("com.squareup:javapoet:1.13.0") { because("com.squareup.javapoet") } + api("net.java.dev.jna:jna:5.12.1") { because("com.sun.jna") } + api("com.google.dagger:dagger:2.42") { because("dagger") } + api("com.google.dagger:dagger-compiler:2.42") { because("dagger.compiler") } + api("io.grpc:grpc-netty:1.64.0") { because("io.grpc.netty") } + api("io.grpc:grpc-protobuf:1.64.0") { because("io.grpc.protobuf") } + api("io.grpc:grpc-stub:1.64.0") { because("io.grpc.stub") } + api("com.esaulpaugh:headlong:6.1.1") { because("com.esaulpaugh.headlong") } + api("info.picocli:picocli:4.6.3") { because("info.picocli") } + api("io.github.classgraph:classgraph:4.8.65") { because("io.github.classgraph") } + api("io.perfmark:perfmark-api:0.25.0") { because("io.perfmark") } + api("io.prometheus:simpleclient:0.16.0") { because("io.prometheus.simpleclient") } + api("io.prometheus:simpleclient_httpserver:0.16.0") { + because("io.prometheus.simpleclient.httpserver") + } + api("jakarta.inject:jakarta.inject-api:2.0.1") { because("jakarta.inject") } + api("javax.inject:javax.inject:1") { because("javax.inject") } + api("com.goterl:lazysodium-java:5.1.4") { because("lazysodium.java") } + api("net.i2p.crypto:eddsa:0.3.0") { because("net.i2p.crypto.eddsa") } + api("org.antlr:antlr4-runtime:4.13.1") { because("org.antlr.antlr4.runtime") } + api("commons-codec:commons-codec:1.15") { because("org.apache.commons.codec") } + api("org.apache.commons:commons-collections4:4.4") { + because("org.apache.commons.collections4") + } + api("commons-io:commons-io:2.15.1") { because("org.apache.commons.io") } + api("org.apache.commons:commons-lang3:3.14.0") { because("org.apache.commons.lang3") } + api("org.apache.commons:commons-compress:1.26.0") { because("org.apache.commons.compress") } + api("org.apache.logging.log4j:log4j-api:2.21.1") { because("org.apache.logging.log4j") } + api("org.apache.logging.log4j:log4j-core:2.21.1") { because("org.apache.logging.log4j.core") } + api("org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1") { + because("org.apache.logging.log4j.slf4j2.impl") + } + api("org.assertj:assertj-core:3.23.1") { because("org.assertj.core") } + api("org.bouncycastle:bcpkix-jdk18on:1.79") { because("org.bouncycastle.pkix") } + api("org.bouncycastle:bcprov-jdk18on:1.78") { because("org.bouncycastle.provider") } + api("org.eclipse.collections:eclipse-collections-api:10.4.0") { + because("org.eclipse.collections.api") + } + api("org.eclipse.collections:eclipse-collections:10.4.0") { + because("org.eclipse.collections.impl") + } + api("org.hamcrest:hamcrest:2.2") { because("org.hamcrest") } + api("org.hyperledger.besu:besu-datatypes:24.3.3") { because("org.hyperledger.besu.datatypes") } + api("org.hyperledger.besu:evm:24.3.3") { because("org.hyperledger.besu.evm") } + api("org.hyperledger.besu:secp256k1:0.8.2") { + because("org.hyperledger.besu.nativelib.secp256k1") + } + api("org.json:json:20231013") { because("org.json") } + api("org.junit.jupiter:junit-jupiter-api:5.10.2") { because("org.junit.jupiter.api") } + api("org.junit.jupiter:junit-jupiter-engine:5.10.2") { because("org.junit.jupiter.engine") } + api("org.junit-pioneer:junit-pioneer:2.0.1") { because("org.junitpioneer") } + api("org.mockito:mockito-core:5.8.0") { because("org.mockito") } + api("org.mockito:mockito-junit-jupiter:5.8.0") { because("org.mockito.junit.jupiter") } + api("org.opentest4j:opentest4j:1.2.0") { because("org.opentest4j") } + api("org.testcontainers:testcontainers:1.17.2") { because("org.testcontainers") } + api("org.testcontainers:junit-jupiter:1.17.2") { because("org.testcontainers.junit.jupiter") } + api("org.yaml:snakeyaml:2.2") { because("org.yaml.snakeyaml") } + api("io.tmio:tuweni-bytes:2.4.2") { because("tuweni.bytes") } + api("io.tmio:tuweni-units:2.4.2") { because("tuweni.units") } + api("uk.org.webcompere:system-stubs-core:2.1.5") { + because("uk.org.webcompere.systemstubs.core") + } + api("uk.org.webcompere:system-stubs-jupiter:2.1.5") { + because("uk.org.webcompere.systemstubs.jupiter") + } + api("com.google.protobuf:protoc:3.25.4") + api("io.grpc:protoc-gen-grpc-java:1.66.0") + + api("com.hedera.cryptography:hedera-cryptography-pairings-api:0.1.1-SNAPSHOT") { + because("com.hedera.cryptography.pairings.api") + } + api("com.hedera.cryptography:hedera-cryptography-altbn128:0.1.1-SNAPSHOT") { + because("com.hedera.cryptography.altbn128") + } + api("com.hedera.cryptography:hedera-cryptography-bls:0.1.1-SNAPSHOT") { + because("com.hedera.cryptography.bls") + } + api("com.hedera.cryptography:hedera-cryptography-tss:0.1.1-SNAPSHOT") { + because("com.hedera.cryptography.tss") + } +} diff --git a/platform-sdk/build.gradle.kts b/platform-sdk/build.gradle.kts index c88872a30022..9a0d74b3c0c4 100644 --- a/platform-sdk/build.gradle.kts +++ b/platform-sdk/build.gradle.kts @@ -1,38 +1,17 @@ -/* - * 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. - */ - -plugins { id("com.hedera.gradle.java") } +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } val sdkDir = layout.projectDirectory.dir("sdk") -tasks.register("run") { - group = "application" +tasks.named("run") { workingDir = sdkDir.asFile - mainClass.set("com.swirlds.platform.Browser") + mainClass = "com.swirlds.platform.Browser" classpath = sdkDir.asFileTree.matching { include("*.jar") } jvmArgs = listOf("-agentlib:jdwp=transport=dt_socket,address=8888,server=y,suspend=n") maxHeapSize = "8g" - // Running ':assemble' of all 'platform-sdk' subprojects before, will trigger - // copyLib/copyApp, of all projects that provide an application. - dependsOn( - rootProject.subprojects - .filter { it.projectDir.absolutePath.contains("/platform-sdk/") } - .map { "${it.path}:assemble" } - ) + // Build everything for the 'sdk' folder + dependsOn(":swirlds:assemble") } val cleanRun = diff --git a/platform-sdk/consensus-gossip-impl/build.gradle.kts b/platform-sdk/consensus-gossip-impl/build.gradle.kts index 3310d8eeef45..0769a89aa6e0 100644 --- a/platform-sdk/consensus-gossip-impl/build.gradle.kts +++ b/platform-sdk/consensus-gossip-impl/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } description = "Default Consensus Gossip Implementation" diff --git a/platform-sdk/consensus-gossip/build.gradle.kts b/platform-sdk/consensus-gossip/build.gradle.kts index d00919743eeb..eb94da04c3b7 100644 --- a/platform-sdk/consensus-gossip/build.gradle.kts +++ b/platform-sdk/consensus-gossip/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } description = "Consensus Gossip API" diff --git a/platform-sdk/event-creator-impl/build.gradle.kts b/platform-sdk/event-creator-impl/build.gradle.kts index a8896b79d773..405e48608336 100644 --- a/platform-sdk/event-creator-impl/build.gradle.kts +++ b/platform-sdk/event-creator-impl/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/event-creator/build.gradle.kts b/platform-sdk/event-creator/build.gradle.kts index 004e5295ff57..eeb057d77da7 100644 --- a/platform-sdk/event-creator/build.gradle.kts +++ b/platform-sdk/event-creator/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.services") - id("com.hedera.gradle.services-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } description = "Event Creator API" diff --git a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts index 96c6c136e38b..cb18a268ed98 100644 --- a/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/CryptocurrencyDemo/build.gradle.kts @@ -1,19 +1,4 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.crypto.CryptocurrencyDemoMain") +application.mainClass = "com.swirlds.demo.crypto.CryptocurrencyDemoMain" diff --git a/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts index 076aa930d40c..8b5bb9e58f29 100644 --- a/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/HelloSwirldDemo/build.gradle.kts @@ -1,19 +1,4 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.hello.HelloSwirldDemoMain") +application.mainClass = "com.swirlds.demo.hello.HelloSwirldDemoMain" diff --git a/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts b/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts index 2a6bd33eb2c3..a5c9f650e67d 100644 --- a/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts +++ b/platform-sdk/platform-apps/demos/StatsDemo/build.gradle.kts @@ -1,19 +1,4 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.stats.StatsDemoMain") +application.mainClass = "com.swirlds.demo.stats.StatsDemoMain" diff --git a/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts index 6bb16a68e867..39ec1c99fb74 100644 --- a/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/AddressBookTestingTool/build.gradle.kts @@ -1,19 +1,4 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.addressbook.AddressBookTestingToolMain") +application.mainClass = "com.swirlds.demo.addressbook.AddressBookTestingToolMain" diff --git a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts index f5c63ad195c7..963444f299ae 100644 --- a/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/ConsistencyTestingTool/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.consistency.ConsistencyTestingToolMain") +application.mainClass = "com.swirlds.demo.consistency.ConsistencyTestingToolMain" mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts index 0ccf911efacb..25eac23b95bf 100644 --- a/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/ISSTestingTool/build.gradle.kts @@ -1,21 +1,6 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.iss.ISSTestingToolMain") +application.mainClass = "com.swirlds.demo.iss.ISSTestingToolMain" mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts index faf0295e6b51..e3fca20be0ff 100644 --- a/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/MigrationTestingTool/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.migration.MigrationTestingToolMain") +application.mainClass = "com.swirlds.demo.migration.MigrationTestingToolMain" testModuleInfo { requires("org.junit.jupiter.api") diff --git a/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts index b579310c4b1a..20931d097e0c 100644 --- a/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/PlatformTestingTool/build.gradle.kts @@ -1,22 +1,8 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.application") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.application") + id("org.hiero.gradle.feature.test-timing-sensitive") + id("com.google.protobuf") } diff --git a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/build.gradle.kts index 110a05eb2d7a..2a4f4cf630fa 100644 --- a/platform-sdk/platform-apps/tests/StatsSigningTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/StatsSigningTestingTool/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * 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. - */ - -plugins { id("com.hedera.gradle.application") } +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default // and then fix the reported issues. tasks.withType().configureEach { options.compilerArgs.add("-Xlint:-cast") } -application.mainClass.set("com.swirlds.demo.stats.signing.StatsSigningTestingToolMain") +application.mainClass = "com.swirlds.demo.stats.signing.StatsSigningTestingToolMain" diff --git a/platform-sdk/platform-apps/tests/StressTestingTool/build.gradle.kts b/platform-sdk/platform-apps/tests/StressTestingTool/build.gradle.kts index 780a202ff27d..240c46a781f5 100644 --- a/platform-sdk/platform-apps/tests/StressTestingTool/build.gradle.kts +++ b/platform-sdk/platform-apps/tests/StressTestingTool/build.gradle.kts @@ -1,21 +1,6 @@ -/* - * 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. - */ +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } -plugins { id("com.hedera.gradle.application") } - -application.mainClass.set("com.swirlds.demo.stress.StressTestingToolMain") +application.mainClass = "com.swirlds.demo.stress.StressTestingToolMain" mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/swirlds-base/build.gradle.kts b/platform-sdk/swirlds-base/build.gradle.kts index 0393e4a78e6b..75f05600c980 100644 --- a/platform-sdk/swirlds-base/build.gradle.kts +++ b/platform-sdk/swirlds-base/build.gradle.kts @@ -1,25 +1,10 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-benchmarks/build.gradle.kts b/platform-sdk/swirlds-benchmarks/build.gradle.kts index 28429d4ebed1..301cc838c712 100644 --- a/platform-sdk/swirlds-benchmarks/build.gradle.kts +++ b/platform-sdk/swirlds-benchmarks/build.gradle.kts @@ -1,24 +1,9 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 import me.champeau.jmh.JMHTask plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.feature.benchmark") + id("org.hiero.gradle.module.application") + id("org.hiero.gradle.feature.benchmark") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-cli/build.gradle.kts b/platform-sdk/swirlds-cli/build.gradle.kts index 6daacca0a6da..162e49c8825d 100644 --- a/platform-sdk/swirlds-cli/build.gradle.kts +++ b/platform-sdk/swirlds-cli/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } testModuleInfo { requires("org.junit.jupiter.api") } diff --git a/platform-sdk/swirlds-common/build.gradle.kts b/platform-sdk/swirlds-common/build.gradle.kts index 7a3984f114ef..57efac79eec7 100644 --- a/platform-sdk/swirlds-common/build.gradle.kts +++ b/platform-sdk/swirlds-common/build.gradle.kts @@ -1,24 +1,9 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-config-api/build.gradle.kts b/platform-sdk/swirlds-config-api/build.gradle.kts index 673cff9e1397..8220dbff3885 100644 --- a/platform-sdk/swirlds-config-api/build.gradle.kts +++ b/platform-sdk/swirlds-config-api/build.gradle.kts @@ -1,21 +1,6 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") } diff --git a/platform-sdk/swirlds-config-extensions/build.gradle.kts b/platform-sdk/swirlds-config-extensions/build.gradle.kts index f11a3d013c15..3c70a61a4b56 100644 --- a/platform-sdk/swirlds-config-extensions/build.gradle.kts +++ b/platform-sdk/swirlds-config-extensions/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") } testModuleInfo { diff --git a/platform-sdk/swirlds-config-impl/build.gradle.kts b/platform-sdk/swirlds-config-impl/build.gradle.kts index 8b88116a6706..759600f40c22 100644 --- a/platform-sdk/swirlds-config-impl/build.gradle.kts +++ b/platform-sdk/swirlds-config-impl/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") } mainModuleInfo { annotationProcessor("com.google.auto.service.processor") } diff --git a/platform-sdk/swirlds-config-processor/build.gradle.kts b/platform-sdk/swirlds-config-processor/build.gradle.kts index 19b3d4822b37..55c249de0db4 100644 --- a/platform-sdk/swirlds-config-processor/build.gradle.kts +++ b/platform-sdk/swirlds-config-processor/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } mainModuleInfo { annotationProcessor("com.google.auto.service.processor") } diff --git a/platform-sdk/swirlds-fchashmap/build.gradle.kts b/platform-sdk/swirlds-fchashmap/build.gradle.kts index 2ea867132dcc..c1580956b106 100644 --- a/platform-sdk/swirlds-fchashmap/build.gradle.kts +++ b/platform-sdk/swirlds-fchashmap/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } mainModuleInfo { annotationProcessor("com.swirlds.config.processor") } diff --git a/platform-sdk/swirlds-fcqueue/build.gradle.kts b/platform-sdk/swirlds-fcqueue/build.gradle.kts index e1675a00ef13..bc0e4af8b62d 100644 --- a/platform-sdk/swirlds-fcqueue/build.gradle.kts +++ b/platform-sdk/swirlds-fcqueue/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } testModuleInfo { diff --git a/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts b/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts index 6c7b688ba995..b6cabb30d2b4 100644 --- a/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts +++ b/platform-sdk/swirlds-logging-log4j-appender/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } mainModuleInfo { diff --git a/platform-sdk/swirlds-logging/build.gradle.kts b/platform-sdk/swirlds-logging/build.gradle.kts index fdaa4647d989..551625be0364 100644 --- a/platform-sdk/swirlds-logging/build.gradle.kts +++ b/platform-sdk/swirlds-logging/build.gradle.kts @@ -1,26 +1,11 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-time-consuming") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-time-consuming") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-merkle/build.gradle.kts b/platform-sdk/swirlds-merkle/build.gradle.kts index 5db6bc803b11..17603b2ce2c2 100644 --- a/platform-sdk/swirlds-merkle/build.gradle.kts +++ b/platform-sdk/swirlds-merkle/build.gradle.kts @@ -1,24 +1,9 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-timing-sensitive") } testModuleInfo { diff --git a/platform-sdk/swirlds-merkledb/build.gradle.kts b/platform-sdk/swirlds-merkledb/build.gradle.kts index 654d938c0731..00bf7837ba81 100644 --- a/platform-sdk/swirlds-merkledb/build.gradle.kts +++ b/platform-sdk/swirlds-merkledb/build.gradle.kts @@ -1,26 +1,11 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-hammer") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-hammer") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-merkledb/src/testFixtures/java/module-info.java b/platform-sdk/swirlds-merkledb/src/testFixtures/java/module-info.java index 2c00d64fde8f..cf9f9e9d2939 100644 --- a/platform-sdk/swirlds-merkledb/src/testFixtures/java/module-info.java +++ b/platform-sdk/swirlds-merkledb/src/testFixtures/java/module-info.java @@ -1,11 +1,11 @@ module com.swirlds.merkledb.test.fixtures { exports com.swirlds.merkledb.test.fixtures; + requires transitive com.hedera.pbj.runtime; requires transitive com.swirlds.common; requires transitive com.swirlds.config.api; requires transitive com.swirlds.metrics.api; requires transitive com.swirlds.virtualmap; - requires transitive com.hedera.pbj.runtime; requires transitive org.apache.logging.log4j.core; requires com.swirlds.base; requires com.swirlds.config.extensions.test.fixtures; diff --git a/platform-sdk/swirlds-metrics-api/build.gradle.kts b/platform-sdk/swirlds-metrics-api/build.gradle.kts index 278ebab31a3a..6f1b6f2acce6 100644 --- a/platform-sdk/swirlds-metrics-api/build.gradle.kts +++ b/platform-sdk/swirlds-metrics-api/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } testModuleInfo { diff --git a/platform-sdk/swirlds-metrics-impl/build.gradle.kts b/platform-sdk/swirlds-metrics-impl/build.gradle.kts index 278ebab31a3a..6f1b6f2acce6 100644 --- a/platform-sdk/swirlds-metrics-impl/build.gradle.kts +++ b/platform-sdk/swirlds-metrics-impl/build.gradle.kts @@ -1,22 +1,7 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") } testModuleInfo { diff --git a/platform-sdk/swirlds-platform-core/build.gradle.kts b/platform-sdk/swirlds-platform-core/build.gradle.kts index 3297b008d327..7a0d06d2ea97 100644 --- a/platform-sdk/swirlds-platform-core/build.gradle.kts +++ b/platform-sdk/swirlds-platform-core/build.gradle.kts @@ -1,25 +1,10 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-state-api/build.gradle.kts b/platform-sdk/swirlds-state-api/build.gradle.kts index 5a247350d816..d147c770b11e 100644 --- a/platform-sdk/swirlds-state-api/build.gradle.kts +++ b/platform-sdk/swirlds-state-api/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") } testModuleInfo { diff --git a/platform-sdk/swirlds-state-impl/build.gradle.kts b/platform-sdk/swirlds-state-impl/build.gradle.kts index 399424faf0df..bf6a5d6bd5ad 100644 --- a/platform-sdk/swirlds-state-impl/build.gradle.kts +++ b/platform-sdk/swirlds-state-impl/build.gradle.kts @@ -1,23 +1,8 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.test-fixtures") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.test-fixtures") } testModuleInfo { diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts index 01ce14f8f2de..5e3e64c3dda1 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/build.gradle.kts @@ -1,22 +1,21 @@ -/* - * Copyright (C) 2016-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. - */ - +// SPDX-License-Identifier: Apache-2.0 plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.feature.benchmark") + id("java-library") + id("jacoco") + id("org.hiero.gradle.base.jpms-modules") + id("org.hiero.gradle.base.lifecycle") + id("org.hiero.gradle.base.version") + id("org.hiero.gradle.check.dependencies") + id("org.hiero.gradle.check.javac-lint") + id("org.hiero.gradle.check.spotless") + id("org.hiero.gradle.check.spotless-java") + id("org.hiero.gradle.check.spotless-kotlin") + id("org.hiero.gradle.feature.git-properties-file") + id("org.hiero.gradle.feature.java-compile") + id("org.hiero.gradle.feature.java-doc") + id("org.hiero.gradle.feature.java-execute") + id("org.hiero.gradle.feature.test") + id("org.hiero.gradle.report.test-logger") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds-virtualmap/build.gradle.kts b/platform-sdk/swirlds-virtualmap/build.gradle.kts index 83239810a946..7c4fee24e8aa 100644 --- a/platform-sdk/swirlds-virtualmap/build.gradle.kts +++ b/platform-sdk/swirlds-virtualmap/build.gradle.kts @@ -1,28 +1,13 @@ -/* - * 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. - */ - +// SPDX-License-Identifier: Apache-2.0 import me.champeau.jmh.JMHTask plugins { - id("com.hedera.gradle.platform") - id("com.hedera.gradle.platform-publish") - id("com.hedera.gradle.feature.benchmark") - id("com.hedera.gradle.feature.test-fixtures") - id("com.hedera.gradle.feature.test-hammer") - id("com.hedera.gradle.feature.test-timing-sensitive") + id("org.hiero.gradle.module.library") + id("org.hiero.gradle.feature.publish-artifactregistry") + id("org.hiero.gradle.feature.benchmark") + id("org.hiero.gradle.feature.test-fixtures") + id("org.hiero.gradle.feature.test-hammer") + id("org.hiero.gradle.feature.test-timing-sensitive") } // Remove the following line to enable all 'javac' lint checks that we have turned on by default diff --git a/platform-sdk/swirlds/build.gradle.kts b/platform-sdk/swirlds/build.gradle.kts index f2726a41736f..ff8165738a19 100644 --- a/platform-sdk/swirlds/build.gradle.kts +++ b/platform-sdk/swirlds/build.gradle.kts @@ -1,20 +1,5 @@ -/* - * 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. - */ - -plugins { id("com.hedera.gradle.application") } +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.module.application") } mainModuleInfo { runtimeOnly("com.swirlds.platform.core") @@ -24,16 +9,12 @@ mainModuleInfo { application.mainClass.set("com.swirlds.platform.Browser") -tasks.copyApp { - // Adjust configuration from 'com.hedera.hashgraph.application': - // Copy directly into 'sdk' and not 'sdk/data/apps' - into(layout.projectDirectory.dir("../sdk")) -} - tasks.jar { // Gradle fails to track 'configurations.runtimeClasspath' as an input to the task if it is // only used in the 'manifest.attributes'. Hence, we explicitly add it as input. inputs.files(configurations.runtimeClasspath) + + archiveVersion.convention(null as String?) doFirst { manifest { attributes( @@ -47,3 +28,55 @@ tasks.jar { } } } + +// Copy this app () and the demo apps into 'sdk' folder +val demoApp = configurations.dependencyScope("demoApp") + +dependencies { + demoApp(project(":AddressBookTestingTool")) + demoApp(project(":ConsistencyTestingTool")) + demoApp(project(":CryptocurrencyDemo")) + demoApp(project(":HelloSwirldDemo")) + demoApp(project(":ISSTestingTool")) + demoApp(project(":MigrationTestingTool")) + demoApp(project(":PlatformTestingTool")) + demoApp(project(":StatsDemo")) + demoApp(project(":StatsSigningTestingTool")) + demoApp(project(":StressTestingTool")) +} + +val demoAppsRuntimeClasspath = + configurations.resolvable("demoAppsRuntimeClasspath") { + extendsFrom(demoApp.get()) + shouldResolveConsistentlyWith(configurations.mainRuntimeClasspath.get()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) + attributes.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) + attributes.attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements.JAR) + ) + attributes.attribute(Attribute.of("javaModule", Boolean::class.javaObjectType), true) + } +val demoAppsJars = + configurations.resolvable("demoAppsJars") { + extendsFrom(demoApp.get(), configurations.internal.get()) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) + isTransitive = false // only the application Jars, not the dependencies + } + +tasks.register("copyApps") { + destinationDir = layout.projectDirectory.dir("../sdk").asFile + from(tasks.jar) // 'swirlds.jar' goes in directly into 'sdk' + into("data/apps") { + // Copy built jar into `data/apps` and rename + from(demoAppsJars) + rename { "${it.substring(0, it.indexOf("-"))}.jar" } + } + into("data/lib") { + // Copy dependencies into `sdk/data/lib` + from(project.configurations.runtimeClasspath) + from(demoAppsRuntimeClasspath.get().minus(demoAppsJars.get())) + } +} + +tasks.assemble { dependsOn(tasks.named("copyApps")) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 26e94be8c18f..1ad8b9c04676 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,49 +1,28 @@ -/* - * Copyright (C) 2022-2023 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. - */ - -pluginManagement { includeBuild("gradle/plugins") } - -plugins { id("com.hedera.gradle.settings") } +// SPDX-License-Identifier: Apache-2.0 +plugins { id("org.hiero.gradle.build") version "0.1.1" } javaModules { // This "intermediate parent project" should be removed module("platform-sdk") { artifact = "swirlds-platform" } // The Hedera API module - module("hapi") { - group = "com.hedera.hashgraph" - } + module("hapi") { group = "com.hedera.hashgraph" } // The Hedera platform modules directory("platform-sdk") { group = "com.swirlds" module("swirlds") // not actually a Module as it has no module-info.java module("swirlds-benchmarks") // not actually a Module as it has no module-info.java - module("swirlds-unit-tests/core/swirlds-platform-test") // nested module is not found automatically - module("consensus-gossip") { artifact = "consensus-gossip" } - module("consensus-gossip-impl") { artifact = "consensus-gossip-impl" } - module("event-creator") { artifact = "event-creator" } - module("event-creator-impl") { artifact = "event-creator-impl" } + module( + "swirlds-unit-tests/core/swirlds-platform-test" + ) // nested module is not found automatically } // The Hedera services modules directory("hedera-node") { group = "com.hedera.hashgraph" - // Configure 'artifact' for projects where the folder does not correspond to the artifact name + // Configure 'artifact' for projects where folder does not correspond to artifact name module("hapi-fees") { artifact = "app-hapi-fees" } module("hapi-utils") { artifact = "app-hapi-utils" } module("hedera-addressbook-service") { artifact = "app-service-addressbook" } @@ -68,17 +47,11 @@ javaModules { } // Platform-base demo applications - directory("example-apps") { - group = "com.swirlds" - } + directory("example-apps") { group = "com.swirlds" } // Platform demo applications - directory("platform-sdk/platform-apps/demos") { - group = "com.swirlds" - } + directory("platform-sdk/platform-apps/demos") { group = "com.swirlds" } // Platform test applications - directory("platform-sdk/platform-apps/tests") { - group = "com.swirlds" - } + directory("platform-sdk/platform-apps/tests") { group = "com.swirlds" } } From 959c06f22731031833b54b95c44cdd3202b86073 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Wed, 18 Dec 2024 20:20:31 +0100 Subject: [PATCH 21/39] build: remove duplicated dependency versions (#17116) Signed-off-by: Jendrik Johannes --- hiero-dependency-versions/build.gradle.kts | 95 +++++++++++++--------- 1 file changed, 58 insertions(+), 37 deletions(-) diff --git a/hiero-dependency-versions/build.gradle.kts b/hiero-dependency-versions/build.gradle.kts index 540699572d2e..4b3257429d57 100644 --- a/hiero-dependency-versions/build.gradle.kts +++ b/hiero-dependency-versions/build.gradle.kts @@ -15,15 +15,34 @@ dependencies { } } +val autoService = "1.1.1" +val besu = "24.3.3" +val bouncycastle = "1.79" +val dagger = "2.42" +val eclipseCollections = "10.4.0" +val grpc = "1.64.0" +val hederaCryptography = "0.1.1-SNAPSHOT" +val helidon = "4.1.1" +val jackson = "2.16.0" +val junit5 = "5.10.2" +val log4j = "2.21.1" +val mockito = "5.8.0" +val protobuf = "4.28.2" +val testContainers = "1.17.2" +val tuweni = "2.4.2" +val webcompare = "2.1.5" + dependencies.constraints { - api("io.helidon.common:helidon-common:4.1.1") { because("io.helidon.common") } - api("io.helidon.webclient:helidon-webclient:4.1.1") { because("io.helidon.webclient") } - api("io.helidon.webclient:helidon-webclient-grpc:4.1.1") { + api("io.helidon.common:helidon-common:$helidon") { because("io.helidon.common") } + api("io.helidon.webclient:helidon-webclient:$helidon") { because("io.helidon.webclient") } + api("io.helidon.webclient:helidon-webclient-grpc:$helidon") { because("io.helidon.webclient.grpc") } api("org.awaitility:awaitility:4.2.0") { because("awaitility") } - api("com.fasterxml.jackson.core:jackson-core:2.16.0") { because("com.fasterxml.jackson.core") } - api("com.fasterxml.jackson.core:jackson-databind:2.16.0") { + api("com.fasterxml.jackson.core:jackson-core:$jackson") { + because("com.fasterxml.jackson.core") + } + api("com.fasterxml.jackson.core:jackson-databind:$jackson") { because("com.fasterxml.jackson.databind") } api("com.github.ben-manes.caffeine:caffeine:3.1.1") { because("com.github.benmanes.caffeine") } @@ -31,25 +50,25 @@ dependencies.constraints { api("com.github.spotbugs:spotbugs-annotations:4.7.3") { because("com.github.spotbugs.annotations") } - api("com.google.auto.service:auto-service-annotations:1.1.1") { + api("com.google.auto.service:auto-service-annotations:$autoService") { because("com.google.auto.service") } - api("com.google.auto.service:auto-service:1.1.1") { + api("com.google.auto.service:auto-service:$autoService") { because("com.google.auto.service.processor") } api("com.google.guava:guava:33.3.1-jre") { because("com.google.common") } api("com.google.j2objc:j2objc-annotations:3.0.0") { because("com.google.j2objc.annotations") } api("com.google.jimfs:jimfs:1.2") { because("com.google.jimfs") } - api("com.google.protobuf:protobuf-java:4.28.2") { because("com.google.protobuf") } - api("com.google.protobuf:protobuf-java-util:4.28.2") { because("com.google.protobuf.util") } + api("com.google.protobuf:protobuf-java:$protobuf") { because("com.google.protobuf") } + api("com.google.protobuf:protobuf-java-util:$protobuf") { because("com.google.protobuf.util") } api("com.hedera.pbj:pbj-runtime:0.9.2") { because("com.hedera.pbj.runtime") } api("com.squareup:javapoet:1.13.0") { because("com.squareup.javapoet") } api("net.java.dev.jna:jna:5.12.1") { because("com.sun.jna") } - api("com.google.dagger:dagger:2.42") { because("dagger") } - api("com.google.dagger:dagger-compiler:2.42") { because("dagger.compiler") } - api("io.grpc:grpc-netty:1.64.0") { because("io.grpc.netty") } - api("io.grpc:grpc-protobuf:1.64.0") { because("io.grpc.protobuf") } - api("io.grpc:grpc-stub:1.64.0") { because("io.grpc.stub") } + api("com.google.dagger:dagger:$dagger") { because("dagger") } + api("com.google.dagger:dagger-compiler:$dagger") { because("dagger.compiler") } + api("io.grpc:grpc-netty:$grpc") { because("io.grpc.netty") } + api("io.grpc:grpc-protobuf:$grpc") { because("io.grpc.protobuf") } + api("io.grpc:grpc-stub:$grpc") { because("io.grpc.stub") } api("com.esaulpaugh:headlong:6.1.1") { because("com.esaulpaugh.headlong") } api("info.picocli:picocli:4.6.3") { because("info.picocli") } api("io.github.classgraph:classgraph:4.8.65") { because("io.github.classgraph") } @@ -70,57 +89,59 @@ dependencies.constraints { api("commons-io:commons-io:2.15.1") { because("org.apache.commons.io") } api("org.apache.commons:commons-lang3:3.14.0") { because("org.apache.commons.lang3") } api("org.apache.commons:commons-compress:1.26.0") { because("org.apache.commons.compress") } - api("org.apache.logging.log4j:log4j-api:2.21.1") { because("org.apache.logging.log4j") } - api("org.apache.logging.log4j:log4j-core:2.21.1") { because("org.apache.logging.log4j.core") } - api("org.apache.logging.log4j:log4j-slf4j2-impl:2.21.1") { + api("org.apache.logging.log4j:log4j-api:$log4j") { because("org.apache.logging.log4j") } + api("org.apache.logging.log4j:log4j-core:$log4j") { because("org.apache.logging.log4j.core") } + api("org.apache.logging.log4j:log4j-slf4j2-impl:$log4j") { because("org.apache.logging.log4j.slf4j2.impl") } api("org.assertj:assertj-core:3.23.1") { because("org.assertj.core") } - api("org.bouncycastle:bcpkix-jdk18on:1.79") { because("org.bouncycastle.pkix") } - api("org.bouncycastle:bcprov-jdk18on:1.78") { because("org.bouncycastle.provider") } - api("org.eclipse.collections:eclipse-collections-api:10.4.0") { + api("org.bouncycastle:bcpkix-jdk18on:$bouncycastle") { because("org.bouncycastle.pkix") } + api("org.bouncycastle:bcprov-jdk18on:$bouncycastle") { because("org.bouncycastle.provider") } + api("org.eclipse.collections:eclipse-collections-api:$eclipseCollections") { because("org.eclipse.collections.api") } - api("org.eclipse.collections:eclipse-collections:10.4.0") { + api("org.eclipse.collections:eclipse-collections:$eclipseCollections") { because("org.eclipse.collections.impl") } api("org.hamcrest:hamcrest:2.2") { because("org.hamcrest") } - api("org.hyperledger.besu:besu-datatypes:24.3.3") { because("org.hyperledger.besu.datatypes") } - api("org.hyperledger.besu:evm:24.3.3") { because("org.hyperledger.besu.evm") } + api("org.hyperledger.besu:besu-datatypes:$besu") { because("org.hyperledger.besu.datatypes") } + api("org.hyperledger.besu:evm:$besu") { because("org.hyperledger.besu.evm") } api("org.hyperledger.besu:secp256k1:0.8.2") { because("org.hyperledger.besu.nativelib.secp256k1") } api("org.json:json:20231013") { because("org.json") } - api("org.junit.jupiter:junit-jupiter-api:5.10.2") { because("org.junit.jupiter.api") } - api("org.junit.jupiter:junit-jupiter-engine:5.10.2") { because("org.junit.jupiter.engine") } + api("org.junit.jupiter:junit-jupiter-api:$junit5") { because("org.junit.jupiter.api") } + api("org.junit.jupiter:junit-jupiter-engine:$junit5") { because("org.junit.jupiter.engine") } api("org.junit-pioneer:junit-pioneer:2.0.1") { because("org.junitpioneer") } - api("org.mockito:mockito-core:5.8.0") { because("org.mockito") } - api("org.mockito:mockito-junit-jupiter:5.8.0") { because("org.mockito.junit.jupiter") } + api("org.mockito:mockito-core:$mockito") { because("org.mockito") } + api("org.mockito:mockito-junit-jupiter:$mockito") { because("org.mockito.junit.jupiter") } api("org.opentest4j:opentest4j:1.2.0") { because("org.opentest4j") } - api("org.testcontainers:testcontainers:1.17.2") { because("org.testcontainers") } - api("org.testcontainers:junit-jupiter:1.17.2") { because("org.testcontainers.junit.jupiter") } + api("org.testcontainers:testcontainers:$testContainers") { because("org.testcontainers") } + api("org.testcontainers:junit-jupiter:$testContainers") { + because("org.testcontainers.junit.jupiter") + } api("org.yaml:snakeyaml:2.2") { because("org.yaml.snakeyaml") } - api("io.tmio:tuweni-bytes:2.4.2") { because("tuweni.bytes") } - api("io.tmio:tuweni-units:2.4.2") { because("tuweni.units") } - api("uk.org.webcompere:system-stubs-core:2.1.5") { + api("io.tmio:tuweni-bytes:$tuweni") { because("tuweni.bytes") } + api("io.tmio:tuweni-units:$tuweni") { because("tuweni.units") } + api("uk.org.webcompere:system-stubs-core:$webcompare") { because("uk.org.webcompere.systemstubs.core") } - api("uk.org.webcompere:system-stubs-jupiter:2.1.5") { + api("uk.org.webcompere:system-stubs-jupiter:$webcompare") { because("uk.org.webcompere.systemstubs.jupiter") } api("com.google.protobuf:protoc:3.25.4") api("io.grpc:protoc-gen-grpc-java:1.66.0") - api("com.hedera.cryptography:hedera-cryptography-pairings-api:0.1.1-SNAPSHOT") { + api("com.hedera.cryptography:hedera-cryptography-pairings-api:$hederaCryptography") { because("com.hedera.cryptography.pairings.api") } - api("com.hedera.cryptography:hedera-cryptography-altbn128:0.1.1-SNAPSHOT") { + api("com.hedera.cryptography:hedera-cryptography-altbn128:$hederaCryptography") { because("com.hedera.cryptography.altbn128") } - api("com.hedera.cryptography:hedera-cryptography-bls:0.1.1-SNAPSHOT") { + api("com.hedera.cryptography:hedera-cryptography-bls:$hederaCryptography") { because("com.hedera.cryptography.bls") } - api("com.hedera.cryptography:hedera-cryptography-tss:0.1.1-SNAPSHOT") { + api("com.hedera.cryptography:hedera-cryptography-tss:$hederaCryptography") { because("com.hedera.cryptography.tss") } } From 1128c741d05838a27711f143deaca9b851358dc8 Mon Sep 17 00:00:00 2001 From: Kim Rader Date: Wed, 18 Dec 2024 12:26:28 -0800 Subject: [PATCH 22/39] fix: tokenClaimAirdrop throws NPE on null sender or receiver (cherry-pick) (#17097) Signed-off-by: Kim Rader --- .../handlers/TokenClaimAirdropHandler.java | 6 ++++ .../TokenClaimAirdropHandlerTest.java | 28 +++++++++++++++++++ .../utilops/mod/BodyIdClearingStrategy.java | 8 +++++- .../suites/hip904/TokenClaimAirdropTest.java | 19 +++++++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenClaimAirdropHandler.java b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenClaimAirdropHandler.java index 5d0b1bc95a9a..59059b4233d1 100644 --- a/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenClaimAirdropHandler.java +++ b/hedera-node/hedera-token-service-impl/src/main/java/com/hedera/node/app/service/token/impl/handlers/TokenClaimAirdropHandler.java @@ -18,6 +18,7 @@ import static com.hedera.hapi.node.base.ResponseCodeEnum.EMPTY_PENDING_AIRDROP_ID_LIST; import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_ACCOUNT_ID; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_PENDING_AIRDROP_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.PENDING_AIRDROP_ID_LIST_TOO_LONG; import static com.hedera.hapi.node.base.ResponseCodeEnum.PENDING_AIRDROP_ID_REPEATED; import static com.hedera.hapi.node.base.ResponseCodeEnum.TOKEN_AIRDROP_WITH_FALLBACK_ROYALTY; @@ -114,6 +115,11 @@ public void pureChecks(@NonNull TransactionBody txn) throws PreCheckException { final var uniqueAirdrops = Set.copyOf(pendingAirdrops); validateTruePreCheck(pendingAirdrops.size() == uniqueAirdrops.size(), PENDING_AIRDROP_ID_REPEATED); + + validateTruePreCheck( + pendingAirdrops.stream().allMatch(PendingAirdropId::hasSenderId), INVALID_PENDING_AIRDROP_ID); + validateTruePreCheck( + pendingAirdrops.stream().allMatch(PendingAirdropId::hasReceiverId), INVALID_PENDING_AIRDROP_ID); } @Override diff --git a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenClaimAirdropHandlerTest.java b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenClaimAirdropHandlerTest.java index 20ef4eff705e..7c7a06999642 100644 --- a/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenClaimAirdropHandlerTest.java +++ b/hedera-node/hedera-token-service-impl/src/test/java/com/hedera/node/app/service/token/impl/test/handlers/TokenClaimAirdropHandlerTest.java @@ -169,6 +169,34 @@ void pureChecksHasValidPath() { .doesNotThrowAnyException(); } + @Test + void pureChecksEmptySenderThrows() { + final List pendingAirdropIds = new ArrayList<>(); + pendingAirdropIds.add(PendingAirdropId.newBuilder() + .receiverId(ACCOUNT_ID_3333) + .fungibleTokenType(TOKEN_2468) + .build()); + final var txn = newTokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder() + .pendingAirdrops(pendingAirdropIds) + .build()); + Assertions.assertThatThrownBy(() -> tokenClaimAirdropHandler.pureChecks(txn)) + .isInstanceOf(PreCheckException.class); + } + + @Test + void pureChecksEmptyReceiverThrows() { + final List pendingAirdropIds = new ArrayList<>(); + pendingAirdropIds.add(PendingAirdropId.newBuilder() + .senderId(ACCOUNT_ID_4444) + .fungibleTokenType(TOKEN_2468) + .build()); + final var txn = newTokenClaimAirdrop(TokenClaimAirdropTransactionBody.newBuilder() + .pendingAirdrops(pendingAirdropIds) + .build()); + Assertions.assertThatThrownBy(() -> tokenClaimAirdropHandler.pureChecks(txn)) + .isInstanceOf(PreCheckException.class); + } + @Test void preHandleAccountNotExistPath() throws PreCheckException { final List pendingAirdropIds = new ArrayList<>(); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/BodyIdClearingStrategy.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/BodyIdClearingStrategy.java index 86dbf7ea35af..3a6d1a6cac38 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/BodyIdClearingStrategy.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/mod/BodyIdClearingStrategy.java @@ -25,6 +25,7 @@ import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_ETHEREUM_TRANSACTION; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_FILE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_NODE_ACCOUNT; +import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_PENDING_AIRDROP_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_SIGNATURE; import static com.hederahashgraph.api.proto.java.ResponseCodeEnum.INVALID_TOKEN_ID; @@ -141,7 +142,12 @@ public class BodyIdClearingStrategy extends IdClearingStrategy entry( "proto.EthereumTransactionBody.call_data", ExpectedResponse.atConsensus(INVALID_ETHEREUM_TRANSACTION)), - entry("proto.TokenUpdateNftsTransactionBody.token", ExpectedResponse.atIngest(INVALID_TOKEN_ID))); + entry("proto.TokenUpdateNftsTransactionBody.token", ExpectedResponse.atIngest(INVALID_TOKEN_ID)), + entry("proto.PendingAirdropId.receiver_id", ExpectedResponse.atIngest(INVALID_PENDING_AIRDROP_ID)), + entry( + "proto.PendingAirdropId.fungible_token_type", + ExpectedResponse.atConsensus(INVALID_PENDING_AIRDROP_ID)), + entry("proto.PendingAirdropId.sender_id", ExpectedResponse.atIngest(INVALID_PENDING_AIRDROP_ID))); private static final Map SCHEDULED_CLEARED_ID_RESPONSES = Map.ofEntries( entry("proto.AccountAmount.accountID", ExpectedResponse.atConsensusOneOf(INVALID_ACCOUNT_ID))); diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java index 45e9e72f226e..c910db2239d2 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/hip904/TokenClaimAirdropTest.java @@ -57,8 +57,10 @@ import static com.hedera.services.bdd.spec.utilops.UtilVerbs.logIt; 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.submitModified; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.validateChargedUsd; import static com.hedera.services.bdd.spec.utilops.UtilVerbs.withOpContext; +import static com.hedera.services.bdd.spec.utilops.mod.ModificationUtils.withSuccessivelyVariedBodyIds; import static com.hedera.services.bdd.suites.HapiSuite.DEFAULT_PAYER; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HBAR; import static com.hedera.services.bdd.suites.HapiSuite.ONE_HUNDRED_HBARS; @@ -187,6 +189,23 @@ final Stream claimTokenAirdropAfterDissociation() { getAccountInfo(RECEIVER).hasToken(relationshipWith(NON_FUNGIBLE_TOKEN)))); } + @HapiTest + @DisplayName("fails gracefully with null parameters") + final Stream idVariantsTreatedAsExpected() { + return hapiTest( + cryptoCreate(OWNER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(RECEIVER_WITH_0_AUTO_ASSOCIATIONS) + .balance(ONE_HUNDRED_HBARS) + .maxAutomaticTokenAssociations(0), + createFT(FUNGIBLE_TOKEN_1, OWNER, 1000L), + tokenAirdrop(moving(1, FUNGIBLE_TOKEN_1).between(OWNER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS)) + .payingWith(OWNER), + submitModified(withSuccessivelyVariedBodyIds(), () -> tokenClaimAirdrop( + pendingAirdrop(OWNER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS, FUNGIBLE_TOKEN_1)) + .signedBy(DEFAULT_PAYER, RECEIVER_WITH_0_AUTO_ASSOCIATIONS) + .payingWith(RECEIVER_WITH_0_AUTO_ASSOCIATIONS))); + } + @HapiTest @DisplayName("single token claim success that receiver paying for it") final Stream singleTokenClaimSuccessThatReceiverPayingForIt() { From 0bb55fe8dd7e8c291966da34e3e9eba46ad9d8c8 Mon Sep 17 00:00:00 2001 From: Jendrik Johannes Date: Thu, 19 Dec 2024 09:45:55 +0100 Subject: [PATCH 23/39] build: do not change license header to SPDX format (#17132) Signed-off-by: Jendrik Johannes --- gradle/license-header.txt | 13 +++++++++++++ settings.gradle.kts | 19 +++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 gradle/license-header.txt diff --git a/gradle/license-header.txt b/gradle/license-header.txt new file mode 100644 index 000000000000..2e707c37f3d4 --- /dev/null +++ b/gradle/license-header.txt @@ -0,0 +1,13 @@ +Copyright (C) $YEAR 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. \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1ad8b9c04676..0630c1461051 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,20 @@ -// SPDX-License-Identifier: Apache-2.0 -plugins { id("org.hiero.gradle.build") version "0.1.1" } +/* + * 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. + */ + +plugins { id("org.hiero.gradle.build") version "0.1.2" } javaModules { // This "intermediate parent project" should be removed From 1f6a473b4c550e20d7b1bf104196b95bc146cdcd Mon Sep 17 00:00:00 2001 From: IvanKavaldzhiev Date: Thu, 19 Dec 2024 16:11:09 +0200 Subject: [PATCH 24/39] fix: unit tests in IssDetectorTests (#17128) Signed-off-by: Ivan Kavaldzhiev --- .../platform/wiring/PlatformWiring.java | 5 +- .../platform/test/state/IssDetectorTests.java | 129 ++++++++---------- 2 files changed, 59 insertions(+), 75 deletions(-) diff --git a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java index a93ec07ed782..d615e0735408 100644 --- a/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java +++ b/platform-sdk/swirlds-platform-core/src/main/java/com/swirlds/platform/wiring/PlatformWiring.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -616,9 +616,6 @@ private void wire() { final OutputWire hashedStateOutputWire = hashedStateAndRoundOutputWire.buildAdvancedTransformer( new StateAndRoundToStateReserver("postHasher_stateReserver")); - final OutputWire hashedConsensusRoundOutput = stateHasherWiring - .getOutputWire() - .buildTransformer("postHasher_getConsensusRound", "stateAndRound", StateAndRound::round); transactionHandlerWiring .getOutputWire() diff --git a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java index 61527bacf9df..7e75693d7691 100644 --- a/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java +++ b/platform-sdk/swirlds-unit-tests/core/swirlds-platform-test/src/test/java/com/swirlds/platform/test/state/IssDetectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022-2024 Hedera Hashgraph, LLC + * 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. @@ -37,7 +37,7 @@ import com.swirlds.common.crypto.Hash; import com.swirlds.common.platform.NodeId; import com.swirlds.common.test.fixtures.Randotron; -import com.swirlds.platform.components.transaction.system.ScopedSystemTransaction; +import com.swirlds.platform.components.transaction.system.SystemTransactionExtractionUtils; import com.swirlds.platform.consensus.ConsensusConfig; import com.swirlds.platform.internal.ConsensusRound; import com.swirlds.platform.internal.EventImpl; @@ -203,14 +203,16 @@ void noIss() { final List eventsToInclude = selectRandomEvents(random, signatureEvents); final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, eventsToInclude); - final var systemTransactions = getScopedSystemTransactions(currentRound, roundHash); + final var systemTransactions = + SystemTransactionExtractionUtils.extractFromRound(consensusRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound( new StateAndRound(mockState(currentRound, roundHash), consensusRound, systemTransactions)); } // Add all remaining unsubmitted signature events final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, signatureEvents); - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var systemTransactions = + SystemTransactionExtractionUtils.extractFromRound(consensusRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound( new StateAndRound(mockState(currentRound, randomHash(random)), consensusRound, systemTransactions)); @@ -227,22 +229,6 @@ void noIss() { assertMarkerFile(IssType.OTHER_ISS.toString(), false); } - private List> getScopedSystemTransactions( - long currentRound, Hash roundHash) { - final var semanticVersion = new SemanticVersion(1, 0, 0, null, null); - final var stateSignatureTransaction = StateSignatureTransaction.newBuilder() - .round(currentRound) - .signature(Bytes.EMPTY) - .hash(roundHash.getBytes()) - .build(); - - final var scopedSystemTransaction = - new ScopedSystemTransaction(NodeId.of(1), semanticVersion, stateSignatureTransaction); - final var systemTransactions = new ArrayList>(); - systemTransactions.add(scopedSystemTransaction); - return systemTransactions; - } - /** * This test goes through a series of rounds, some of which experience ISSes. The test verifies that the expected * number of ISSes are registered by the ISS detector. @@ -340,16 +326,16 @@ void mixedOrderTest() { final List eventsToInclude = selectRandomEvents(random, signatureEvents); final ConsensusRound consensusRound = createRoundWithSignatureEvents(currentRound, eventsToInclude); - final var systemTransactions = currentRound % 2 == 1 - ? getScopedSystemTransactions(currentRound, randomHash(random)) - : new ArrayList>(); + final var systemTransactions = + SystemTransactionExtractionUtils.extractFromRound(consensusRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashes.get((int) currentRound)), consensusRound, systemTransactions)); } // Add all remaining signature events final ConsensusRound consensusRound = createRoundWithSignatureEvents(roundsNonAncient, signatureEvents); - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var systemTransactions = + SystemTransactionExtractionUtils.extractFromRound(consensusRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound( new StateAndRound(mockState(roundsNonAncient, randomHash(random)), consensusRound, systemTransactions)); @@ -430,22 +416,22 @@ void decideForCatastrophicIss() { generateEventsContainingSignatures(random, currentRound, catastrophicHashData); // handle the catastrophic round, but don't submit any signatures yet, so it won't be detected - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var catastrophicRound = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactionsForCatastrophicRound = + SystemTransactionExtractionUtils.extractFromRound(catastrophicRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactions)); + catastrophicRound, + systemTransactionsForCatastrophicRound)); // handle some more rounds on top of the catastrophic round for (currentRound++; currentRound < 10; currentRound++) { // don't include any signatures - final var systemTransactionsForNonCatastrophicRound = currentRound % 2 == 1 - ? getScopedSystemTransactions(currentRound, randomHash(random)) - : new ArrayList>(); + final var anotherRound = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactionsForRoundWithoutSignatures = + SystemTransactionExtractionUtils.extractFromRound(anotherRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactionsForNonCatastrophicRound)); + mockState(currentRound, randomHash()), anotherRound, systemTransactionsForRoundWithoutSignatures)); } // submit signatures on the ISS round that represent a minority of the weight @@ -462,13 +448,11 @@ void decideForCatastrophicIss() { signaturesToSubmit.add(signatureEvent); } - final var moreSystemTransactions = currentRound % 2 == 1 - ? getScopedSystemTransactions(currentRound, randomHash(random)) - : new ArrayList>(); + final var roundWithMajority = createRoundWithSignatureEvents(currentRound, signaturesToSubmit); + final var systemTransactionsForRoundWithMajority = + SystemTransactionExtractionUtils.extractFromRound(roundWithMajority, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit), - moreSystemTransactions)); + mockState(currentRound, randomHash()), roundWithMajority, systemTransactionsForRoundWithMajority)); assertEquals( 0, issDetectorTestHelper.getIssNotificationList().size(), @@ -477,12 +461,12 @@ void decideForCatastrophicIss() { currentRound++; // submit the remaining signatures in the next round - final var remainingSystemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var remainingRound = createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound); + final var remainingSystemTransactions = + SystemTransactionExtractionUtils.extractFromRound(remainingRound, StateSignatureTransaction.class); - issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound), - remainingSystemTransactions)); + issDetectorTestHelper.handleStateAndRound( + new StateAndRound(mockState(currentRound, randomHash()), remainingRound, remainingSystemTransactions)); assertEquals( 1, issDetectorTestHelper.getCatastrophicIssCount(), "the catastrophic round should have caused an ISS"); @@ -577,24 +561,23 @@ void catastrophicShiftBeforeCompleteTest() { } // handle the catastrophic round, but it won't be decided yet, since there aren't enough signatures + final var catastrophicRound = createRoundWithSignatureEvents(currentRound, signaturesToSubmit); final var systemTransactionsForCatastrophicRound = - getScopedSystemTransactions(currentRound, randomHash(random)); + SystemTransactionExtractionUtils.extractFromRound(catastrophicRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit), + catastrophicRound, systemTransactionsForCatastrophicRound)); // shift through until the catastrophic round is almost ready to be cleaned up for (currentRound++; currentRound < roundsNonAncient; currentRound++) { - final var systemTransactions = currentRound % 2 == 1 - ? getScopedSystemTransactions(currentRound, randomHash(random)) - : new ArrayList>(); + final var round = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactions = + SystemTransactionExtractionUtils.extractFromRound(round, StateSignatureTransaction.class); - issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactions)); + issDetectorTestHelper.handleStateAndRound( + new StateAndRound(mockState(currentRound, randomHash()), round, systemTransactions)); } assertEquals( @@ -604,12 +587,12 @@ void catastrophicShiftBeforeCompleteTest() { // Shift the window. Even though we have not added enough data for a decision, we will have added enough to lead // to a catastrophic ISS when the timeout is triggered. - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var remainingRound = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactionsForRemainingRound = + SystemTransactionExtractionUtils.extractFromRound(remainingRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactions)); + mockState(currentRound, randomHash()), remainingRound, systemTransactionsForRemainingRound)); assertEquals(1, issDetectorTestHelper.getIssNotificationList().size(), "shifting should have caused an ISS"); assertEquals( @@ -664,12 +647,14 @@ void bigShiftTest() { random, currentRound, new RoundHashValidatorTests.HashGenerationData(catastrophicData, null)); // handle the catastrophic round, but don't submit any signatures yet, so it won't be detected - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); + final var catastrophicRound = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactionsForCatastrophicRound = + SystemTransactionExtractionUtils.extractFromRound(catastrophicRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, selfHashForCatastrophicRound), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactions)); + catastrophicRound, + systemTransactionsForCatastrophicRound)); long submittedWeight = 0; final List signaturesToSubmit = new ArrayList<>(); @@ -688,12 +673,14 @@ void bigShiftTest() { currentRound++; // submit the supermajority of signatures + final var roundWithSupermajority = createRoundWithSignatureEvents(currentRound, signaturesToSubmit); final var systemTransactionsForRoundWithSupermajorityOfSignatures = - getScopedSystemTransactions(currentRound, randomHash(random)); + SystemTransactionExtractionUtils.extractFromRound( + roundWithSupermajority, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesToSubmit), + roundWithSupermajority, systemTransactionsForRoundWithSupermajorityOfSignatures)); // Shifting the window a great distance should not trigger the ISS. @@ -743,21 +730,21 @@ void ignoredRoundTest() { // handle the round and all signatures. // The round has a catastrophic ISS, but should be ignored - final var systemTransactionsForISSRound = getScopedSystemTransactions(currentRound, randomHash(random)); + final var catastrophicRound = createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound); + final var systemTransactionsForCatastrophicRound = + SystemTransactionExtractionUtils.extractFromRound(catastrophicRound, StateSignatureTransaction.class); issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, signaturesOnCatastrophicRound), - systemTransactionsForISSRound)); + mockState(currentRound, randomHash()), catastrophicRound, systemTransactionsForCatastrophicRound)); // shift through some rounds, to make sure nothing unexpected happens - final var systemTransactions = getScopedSystemTransactions(currentRound, randomHash(random)); - for (currentRound++; currentRound <= roundsNonAncient; currentRound++) { + final var anotherRound = createRoundWithSignatureEvents(currentRound, List.of()); + final var systemTransactionsForAnotherRound = + SystemTransactionExtractionUtils.extractFromRound(anotherRound, StateSignatureTransaction.class); + issDetectorTestHelper.handleStateAndRound(new StateAndRound( - mockState(currentRound, randomHash()), - createRoundWithSignatureEvents(currentRound, List.of()), - systemTransactions)); + mockState(currentRound, randomHash()), anotherRound, systemTransactionsForAnotherRound)); } assertEquals(0, issDetectorTestHelper.getIssNotificationList().size(), "ISS should have been ignored"); From d0888fc879a8ff49089f41e80a4227a5bf568e0f Mon Sep 17 00:00:00 2001 From: lukelee-sl <109538178+lukelee-sl@users.noreply.github.com> Date: Thu, 19 Dec 2024 08:56:40 -0800 Subject: [PATCH 25/39] test: add tests for HIP 755 and enable feature flag (#16983) Signed-off-by: Luke Lee --- .../node/config/data/ContractsConfig.java | 4 +- .../contract/impl/test/TestHelpers.java | 4 +- .../HandleHederaNativeOperationsTest.java | 17 +- .../HandleSystemContractOperationsTest.java | 7 +- .../QueryHederaNativeOperationsTest.java | 8 +- .../systemcontracts/CallAttemptHelpers.java | 22 +++ .../DispatchForResponseCodeHssCallTest.java | 23 ++- .../hss/HssCallAttemptTest.java | 20 ++- .../SignScheduleTranslatorTest.java | 79 +++++++++- .../schedule/ContractSignScheduleTest.java | 146 +++++++++++++++--- 10 files changed, 299 insertions(+), 31 deletions(-) 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 f9f0fdc31c6b..13050c750396 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -83,7 +83,7 @@ public record ContractsConfig( @ConfigProperty(value = "systemContract.scheduleService.signSchedule.enabled", defaultValue = "true") @NetworkProperty boolean systemContractSignScheduleEnabled, - @ConfigProperty(value = "systemContract.scheduleService.authorizeSchedule.enabled", defaultValue = "false") + @ConfigProperty(value = "systemContract.scheduleService.authorizeSchedule.enabled", defaultValue = "true") @NetworkProperty boolean systemContractAuthorizeScheduleEnabled, @ConfigProperty(value = "systemContract.accountService.isAuthorizedRawEnabled", defaultValue = "true") 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 b45e18cb658c..923f178e91fc 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -499,6 +499,8 @@ public class TestHelpers { .key(AN_ED25519_KEY) .alias(tuweniToPbjBytes(EIP_1014_ADDRESS)) .build(); + public static final Account B_CONTRACT = + Account.newBuilder().accountId(B_NEW_ACCOUNT_ID).smartContract(true).build(); public static final TokenRelation A_FUNGIBLE_RELATION = TokenRelation.newBuilder() .tokenId(FUNGIBLE_TOKEN_ID) .accountId(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/scope/HandleHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java index 5bff45786716..c4bf09d6bcb9 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleHederaNativeOperationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -64,11 +64,13 @@ import com.hedera.node.app.service.token.ReadableTokenStore; import com.hedera.node.app.service.token.api.TokenServiceApi; import com.hedera.node.app.service.token.records.CryptoCreateStreamBuilder; +import com.hedera.node.app.spi.key.KeyVerifier; import com.hedera.node.app.spi.store.StoreFactory; import com.hedera.node.app.spi.workflows.HandleContext; import com.hedera.node.app.spi.workflows.record.DeleteCapableTransactionStreamBuilder; import java.util.ArrayDeque; import java.util.Deque; +import java.util.SortedSet; import java.util.function.Predicate; import org.hyperledger.besu.evm.frame.MessageFrame; import org.junit.jupiter.api.BeforeEach; @@ -112,6 +114,12 @@ class HandleHederaNativeOperationsTest { @Mock private ReadableNftStore nftStore; + @Mock + private KeyVerifier keyVerifier; + + @Mock + private SortedSet keys; + private final Deque stack = new ArrayDeque<>(); private HandleHederaNativeOperations subject; @@ -335,4 +343,11 @@ void customFeesCheckUsesApi() { final var result = subject.checkForCustomFees(CryptoTransferTransactionBody.DEFAULT); assertTrue(result); } + + @Test + void authorizingSimpleKeysTest() { + given(context.keyVerifier()).willReturn(keyVerifier); + given(keyVerifier.authorizingSimpleKeys()).willReturn(keys); + assertSame(keys, subject.authorizingSimpleKeys()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java index 11e96bc2f9c1..467bc9112bf7 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/HandleSystemContractOperationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -229,4 +229,9 @@ void currentExchangeRateTest() { verify(context).exchangeRateInfo(); verify(exchangeRateInfo).activeRate(any()); } + + @Test + void maybeEthSenderKeyTest() { + assertSame(A_SECP256K1_KEY, subject.maybeEthSenderKey()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java index 6d2d5557fa22..7d9306e2535a 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/scope/QueryHederaNativeOperationsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -28,6 +28,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_FUNGIBLE_TOKEN_ID; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_ACCOUNT_ID; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.tuweniToPbjBytes; +import static com.hedera.node.app.spi.key.KeyVerifier.NO_AUTHORIZING_KEYS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -146,4 +147,9 @@ void getNftUsesStore() { given(nftStore.get(CIVILIAN_OWNED_NFT.nftIdOrThrow())).willReturn(CIVILIAN_OWNED_NFT); assertSame(CIVILIAN_OWNED_NFT, subject.getNft(NON_FUNGIBLE_TOKEN_ID.tokenNum(), NFT_SERIAL_NO)); } + + @Test + void authorizingSimpleKeysTest() { + assertSame(NO_AUTHORIZING_KEYS, subject.authorizingSimpleKeys()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java index feb1f391b644..6bec60d40a74 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/CallAttemptHelpers.java @@ -323,4 +323,26 @@ public static HssCallAttempt prepareHssAttemptWithBytesAndCustomConfig( List.of(translator), false); } + + public static HssCallAttempt prepareHssAttemptWithBytesAndCustomConfigAndDelegatableContractKeys( + final Bytes input, + final CallTranslator translator, + final HederaWorldUpdater.Enhancement enhancement, + final AddressIdConverter addressIdConverter, + final VerificationStrategies verificationStrategies, + final SystemContractGasCalculator gasCalculator, + final Configuration config) { + + return new HssCallAttempt( + input, + OWNER_BESU_ADDRESS, + true, + enhancement, + config, + addressIdConverter, + verificationStrategies, + gasCalculator, + List.of(translator), + false); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java index d19c833793ae..03ec2959a90f 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/DispatchForResponseCodeHssCallTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -16,6 +16,7 @@ package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hss; +import static com.hedera.hapi.node.base.ResponseCodeEnum.INVALID_SCHEDULE_ID; import static com.hedera.hapi.node.base.ResponseCodeEnum.SUCCESS; import static com.hedera.node.app.service.contract.impl.exec.failure.CustomExceptionalHaltReason.ERROR_DECODING_PRECOMPILE_INPUT; import static com.hedera.node.app.service.contract.impl.exec.utils.FrameUtils.CONFIG_CONTEXT_VARIABLE; @@ -116,4 +117,24 @@ void haltsImmediatelyWithNullDispatch() { fullResult.result().getHaltReason()); assertEquals(DEFAULT_CONTRACTS_CONFIG.precompileHtsDefaultGasCost(), fullResult.gasRequirement()); } + + @Test + void failureResultCustomized() { + given(systemContractOperations.dispatch( + TransactionBody.DEFAULT, + verificationStrategy, + AccountID.DEFAULT, + ContractCallStreamBuilder.class, + emptySet(), + DispatchOptions.UsePresetTxnId.NO)) + .willReturn(recordBuilder); + given(dispatchGasCalculator.gasRequirement( + TransactionBody.DEFAULT, gasCalculator, mockEnhancement(), AccountID.DEFAULT)) + .willReturn(123L); + given(recordBuilder.status()).willReturn(INVALID_SCHEDULE_ID); + + final var pricedResult = subject.execute(frame); + final var contractResult = pricedResult.fullResult().result().getOutput(); + assertArrayEquals(ReturnTypes.encodedRc(INVALID_SCHEDULE_ID).array(), contractResult.toArray()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java index 01227c9d8038..a27bc394ee6c 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/HssCallAttemptTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023-2024 Hedera Hashgraph, LLC + * 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. @@ -21,6 +21,7 @@ import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; import static com.hedera.node.app.service.contract.impl.utils.ConversionUtils.numberOfLongZero; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.BDDMockito.given; import com.hedera.node.app.service.contract.impl.exec.scope.VerificationStrategies; @@ -84,4 +85,21 @@ void invalidSelectorLeadsToMissingCall() { false); assertNull(subject.asExecutableCall()); } + + @Test + void isOnlyDelegatableContractKeysActiveTest() { + final var input = TestHelpers.bytesForRedirectAccount(new byte[4], NON_SYSTEM_LONG_ZERO_ADDRESS); + final var subject = new HssCallAttempt( + input, + EIP_1014_ADDRESS, + true, + mockEnhancement(), + DEFAULT_CONFIG, + addressIdConverter, + verificationStrategies, + gasCalculator, + callTranslators, + false); + assertTrue(subject.isOnlyDelegatableContractKeysActive()); + } } diff --git a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java index 421f8dffb082..e3efea5d87ec 100644 --- a/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java +++ b/hedera-node/hedera-smart-contract-service-impl/src/test/java/com/hedera/node/app/service/contract/impl/test/exec/systemcontracts/hss/signschedule/SignScheduleTranslatorTest.java @@ -17,15 +17,18 @@ package com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.hss.signschedule; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.APPROVED_HEADLONG_ADDRESS; +import static com.hedera.node.app.service.contract.impl.test.TestHelpers.B_CONTRACT; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.NON_SYSTEM_LONG_ZERO_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.OWNER_BESU_ADDRESS; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.SOMEBODY; import static com.hedera.node.app.service.contract.impl.test.TestHelpers.bytesForRedirectScheduleTxn; import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHssAttemptWithBytesAndCustomConfig; +import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHssAttemptWithBytesAndCustomConfigAndDelegatableContractKeys; import static com.hedera.node.app.service.contract.impl.test.exec.systemcontracts.CallAttemptHelpers.prepareHssAttemptWithSelectorAndCustomConfig; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.given; @@ -33,6 +36,7 @@ import com.esaulpaugh.headlong.abi.Tuple; import com.hedera.hapi.node.base.AccountID; +import com.hedera.hapi.node.base.Key; import com.hedera.hapi.node.base.ScheduleID; import com.hedera.hapi.node.state.schedule.Schedule; import com.hedera.hapi.node.transaction.TransactionBody; @@ -102,6 +106,9 @@ class SignScheduleTranslatorTest { @Mock private ScheduleID scheduleID; + @Mock + private Key key; + private SignScheduleTranslator subject; @BeforeEach @@ -243,11 +250,59 @@ void testScheduleIdForSignScheduleProxy() { } @Test - void testScheduleIdForAuthorizeScheduleProxy() { + void testScheduleIdForSignScheduleProxyEthSender() { given(enhancement.nativeOperations()).willReturn(nativeOperations); given(enhancement.systemOperations()).willReturn(systemContractOperations); given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(schedule.scheduleId()).willReturn(scheduleID); given(nativeOperations.getAccount(payerId)).willReturn(SOMEBODY); + given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); + given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations)) + .willReturn(verificationStrategy); + given(systemContractOperations.maybeEthSenderKey()).willReturn(key); + + // when: + attempt = prepareHssAttemptWithBytesAndCustomConfig( + bytesForRedirectScheduleTxn( + SignScheduleTranslator.SIGN_SCHEDULE_PROXY.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS), + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration); + + // then: + final var call = subject.callFrom(attempt); + + assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class); + } + + @Test + void testScheduleIdForWrongSelectorThrows() { + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); + + // when: + attempt = prepareHssAttemptWithBytesAndCustomConfig( + bytesForRedirectScheduleTxn(MintTranslator.MINT.selector(), NON_SYSTEM_LONG_ZERO_ADDRESS), + subject, + enhancement, + addressIdConverter, + verificationStrategies, + gasCalculator, + configuration); + + // then: + assertThrows(IllegalStateException.class, () -> subject.callFrom(attempt)); + } + + @Test + void testScheduleIdForAuthorizeSchedule() { + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(nativeOperations.getAccount(payerId)).willReturn(B_CONTRACT); given(schedule.scheduleId()).willReturn(scheduleID); given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, false, nativeOperations)) @@ -265,6 +320,28 @@ void testScheduleIdForAuthorizeScheduleProxy() { assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class); } + @Test + void testScheduleIdForAuthorizeScheduleDelegatableContractKeys() { + given(enhancement.nativeOperations()).willReturn(nativeOperations); + given(nativeOperations.getSchedule(anyLong())).willReturn(schedule); + given(nativeOperations.getAccount(payerId)).willReturn(B_CONTRACT); + given(schedule.scheduleId()).willReturn(scheduleID); + given(addressIdConverter.convertSender(OWNER_BESU_ADDRESS)).willReturn(payerId); + given(verificationStrategies.activatingOnlyContractKeysFor(OWNER_BESU_ADDRESS, true, nativeOperations)) + .willReturn(verificationStrategy); + + // when: + final var input = Bytes.wrapByteBuffer( + SignScheduleTranslator.AUTHORIZE_SCHEDULE.encodeCall(Tuple.of(APPROVED_HEADLONG_ADDRESS))); + attempt = prepareHssAttemptWithBytesAndCustomConfigAndDelegatableContractKeys( + input, subject, enhancement, addressIdConverter, verificationStrategies, gasCalculator, configuration); + + // then: + final var call = subject.callFrom(attempt); + + assertThat(call).isInstanceOf(DispatchForResponseCodeHssCall.class); + } + @Test void testGasRequirement() { long expectedGas = 1000L; diff --git a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java index 44afd799cfa1..c2f7c6012f79 100644 --- a/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java +++ b/hedera-node/test-clients/src/main/java/com/hedera/services/bdd/suites/contract/precompile/schedule/ContractSignScheduleTest.java @@ -37,78 +37,180 @@ import com.hedera.services.bdd.junit.HapiTest; import com.hedera.services.bdd.junit.HapiTestLifecycle; +import com.hedera.services.bdd.junit.OrderedInIsolation; import com.hedera.services.bdd.junit.support.TestLifecycle; +import com.hedera.services.bdd.spec.keys.KeyShape; import com.hedera.services.bdd.spec.transactions.token.TokenMovement; import com.hederahashgraph.api.proto.java.ScheduleID; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Tag; @Tag(SMART_CONTRACT) -@DisplayName("Contract Sign Schedule") +@OrderedInIsolation +@DisplayName("Contract sign schedule") @HapiTestLifecycle public class ContractSignScheduleTest { - - private static final String A_SCHEDULE = "testSchedule"; private static final String CONTRACT = "HRC755Contract"; + private static final String AUTHORIZE_SCHEDULE_CALL = "authorizeScheduleCall"; @Nested - @DisplayName("Authorize Schedule") - class AuthorizeScheduleTest { - private static final AtomicReference scheduleID = new AtomicReference<>(); + @DisplayName("Authorize schedule from EOA controlled by contract") + class AuthorizeScheduleFromEOATest { + private static final String SCHEDULE_A = "testScheduleA"; + private static final String SCHEDULE_B = "testScheduleB"; + private static final String CONTRACT_CONTROLLED = "contractControlled"; + private static final AtomicReference scheduleIDA = new AtomicReference<>(); + private static final AtomicReference scheduleIDB = new AtomicReference<>(); @BeforeAll static void beforeAll(final TestLifecycle testLifecycle) { testLifecycle.doAdhoc( + overriding("contracts.systemContract.scheduleService.enabled", "true"), + overriding("contracts.systemContract.scheduleService.authorizeSchedule.enabled", "true"), + cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), cryptoCreate(RECEIVER), uploadInitCode(CONTRACT), contractCreate(CONTRACT), - cryptoTransfer(TokenMovement.movingHbar(ONE_HUNDRED_HBARS).between(GENESIS, CONTRACT)), - scheduleCreate(A_SCHEDULE, cryptoTransfer(tinyBarsFromTo(CONTRACT, RECEIVER, 1))) - .exposingCreatedIdTo(scheduleID::set)); + cryptoCreate(CONTRACT_CONTROLLED).keyShape(KeyShape.CONTRACT.signedWith(CONTRACT)), + cryptoTransfer(TokenMovement.movingHbar(ONE_HUNDRED_HBARS).between(GENESIS, CONTRACT_CONTROLLED)), + scheduleCreate(SCHEDULE_A, cryptoTransfer(tinyBarsFromTo(CONTRACT_CONTROLLED, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDA::set), + scheduleCreate(SCHEDULE_B, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDB::set)); } @HapiTest - @Disabled + @DisplayName("Signature executes schedule transaction") final Stream authorizeScheduleWithContract() { return hapiTest( + getScheduleInfo(SCHEDULE_A).isNotExecuted(), + contractCall( + CONTRACT, + AUTHORIZE_SCHEDULE_CALL, + mirrorAddrWith(scheduleIDA.get().getScheduleNum())) + .gas(1_000_000L), + getScheduleInfo(SCHEDULE_A).isExecuted()); + } + + @HapiTest + @DisplayName("Signature does not executes schedule transaction") + final Stream authorizeScheduleWithContractNoExec() { + return hapiTest( + getScheduleInfo(SCHEDULE_B).isNotExecuted(), + contractCall( + CONTRACT, + AUTHORIZE_SCHEDULE_CALL, + mirrorAddrWith(scheduleIDB.get().getScheduleNum())) + .gas(1_000_000L), + getScheduleInfo(SCHEDULE_B).isNotExecuted()); + } + } + + @Nested + @DisplayName("Authorize schedule from contract") + class AuthorizeScheduleFromContractTest { + private static final String SCHEDULE_C = "testScheduleC"; + private static final String SCHEDULE_D = "testScheduleD"; + private static final AtomicReference scheduleIDC = new AtomicReference<>(); + private static final AtomicReference scheduleIDD = new AtomicReference<>(); + + @BeforeAll + static void beforeAll(final TestLifecycle testLifecycle) { + testLifecycle.doAdhoc( overriding("contracts.systemContract.scheduleService.enabled", "true"), overriding("contracts.systemContract.scheduleService.authorizeSchedule.enabled", "true"), + cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(RECEIVER), + uploadInitCode(CONTRACT), + // For whatever reason, omitting the admin key sets the admin key to the contract key + contractCreate(CONTRACT).omitAdminKey(), + cryptoTransfer(TokenMovement.movingHbar(ONE_HUNDRED_HBARS).between(GENESIS, CONTRACT)), + scheduleCreate(SCHEDULE_C, cryptoTransfer(tinyBarsFromTo(CONTRACT, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDC::set), + scheduleCreate(SCHEDULE_D, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDD::set)); + } + + @HapiTest + @DisplayName("Signature executes schedule transaction") + final Stream authorizeScheduleWithContract() { + return hapiTest( + getScheduleInfo(SCHEDULE_C).isNotExecuted(), + contractCall( + CONTRACT, + AUTHORIZE_SCHEDULE_CALL, + mirrorAddrWith(scheduleIDC.get().getScheduleNum())) + .gas(1_000_000L), + getScheduleInfo(SCHEDULE_C).isExecuted()); + } + + @HapiTest + @DisplayName("Signature does not executes schedule transaction") + final Stream authorizeScheduleWithContractNoExec() { + return hapiTest( + getScheduleInfo(SCHEDULE_D).isNotExecuted(), contractCall( - CONTRACT, - "authorizeScheduleCall", - mirrorAddrWith(scheduleID.get().getScheduleNum()))); + CONTRACT, + AUTHORIZE_SCHEDULE_CALL, + mirrorAddrWith(scheduleIDD.get().getScheduleNum())) + .gas(1_000_000L), + getScheduleInfo(SCHEDULE_D).isNotExecuted()); } } @Nested @DisplayName("Sign Schedule From EOA") class SignScheduleFromEOATest { - private static final AtomicReference scheduleID = new AtomicReference<>(); + private static final AtomicReference scheduleIDE = new AtomicReference<>(); + private static final AtomicReference scheduleIDF = new AtomicReference<>(); + private static final String OTHER_SENDER = "otherSender"; private static final String SIGN_SCHEDULE = "signSchedule"; private static final String IHRC755 = "IHRC755"; + private static final String SCHEDULE_E = "testScheduleE"; + private static final String SCHEDULE_F = "testScheduleF"; @BeforeAll static void beforeAll(final TestLifecycle testLifecycle) { testLifecycle.doAdhoc( + overriding("contracts.systemContract.scheduleService.enabled", "true"), + overriding("contracts.systemContract.scheduleService.signSchedule.enabled", "true"), cryptoCreate(SENDER).balance(ONE_HUNDRED_HBARS), + cryptoCreate(OTHER_SENDER).balance(ONE_HUNDRED_HBARS), cryptoCreate(RECEIVER).balance(ONE_HUNDRED_HBARS), - scheduleCreate(A_SCHEDULE, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) - .exposingCreatedIdTo(scheduleID::set)); + scheduleCreate(SCHEDULE_E, cryptoTransfer(tinyBarsFromTo(SENDER, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDE::set), + scheduleCreate(SCHEDULE_F, cryptoTransfer(tinyBarsFromTo(OTHER_SENDER, RECEIVER, 1))) + .exposingCreatedIdTo(scheduleIDF::set)); } @HapiTest + @DisplayName("Signature executes schedule transaction") final Stream authorizeScheduleWithContract() { - var scheduleAddress = "0.0." + scheduleID.get().getScheduleNum(); + var scheduleAddress = "0.0." + scheduleIDE.get().getScheduleNum(); return hapiTest( - overriding("contracts.systemContract.scheduleService.enabled", "true"), - overriding("contracts.systemContract.scheduleService.signSchedule.enabled", "true"), - getScheduleInfo(A_SCHEDULE).isNotExecuted(), + getScheduleInfo(SCHEDULE_E).isNotExecuted(), + contractCallWithFunctionAbi( + scheduleAddress, + getABIFor( + com.hedera.services.bdd.suites.contract.Utils.FunctionType.FUNCTION, + SIGN_SCHEDULE, + IHRC755)) + .payingWith(SENDER) + .gas(1_000_000), + getScheduleInfo(SCHEDULE_E).isExecuted()); + } + + @HapiTest + @DisplayName("Signature does not executes schedule transaction") + final Stream authorizeScheduleWithContractNoExec() { + var scheduleAddress = "0.0." + scheduleIDF.get().getScheduleNum(); + return hapiTest( + getScheduleInfo(SCHEDULE_F).isNotExecuted(), contractCallWithFunctionAbi( scheduleAddress, getABIFor( @@ -117,7 +219,7 @@ final Stream authorizeScheduleWithContract() { IHRC755)) .payingWith(SENDER) .gas(1_000_000), - getScheduleInfo(A_SCHEDULE).isExecuted()); + getScheduleInfo(SCHEDULE_F).isNotExecuted()); } } } From a4636f8814d49311d721bff7f52100d1e7e7b3ef Mon Sep 17 00:00:00 2001 From: "Joseph S." <121976561+jsync-swirlds@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:05:26 -0700 Subject: [PATCH 26/39] chore: Merge protobuf changes back to services (#17037) Signed-off-by: Joseph Sinclair <121976561+jsync-swirlds@users.noreply.github.com> --- .../output/smart_contract_service.proto | 2 +- .../stream/output/transaction_output.proto | 2 +- .../block/stream/record_file_item.proto | 1 - .../services/address_book_service.proto | 31 +- .../services/basic_types.proto | 1789 +++++++++++------ .../services/consensus_create_topic.proto | 117 +- .../services/consensus_delete_topic.proto | 36 +- .../services/consensus_get_topic_info.proto | 47 +- .../services/consensus_service.proto | 155 +- .../services/consensus_submit_message.proto | 67 +- .../services/consensus_topic_info.proto | 114 +- .../services/consensus_update_topic.proto | 108 +- .../services/contract_call.proto | 62 +- .../services/contract_call_local.proto | 247 +-- .../services/contract_create.proto | 288 ++- .../services/contract_delete.proto | 88 +- .../services/contract_get_bytecode.proto | 43 +- .../services/contract_get_info.proto | 123 +- .../services/contract_get_records.proto | 47 +- .../services/contract_types.proto | 239 ++- .../services/contract_update.proto | 169 +- .../services/crypto_add_live_hash.proto | 72 +- .../services/crypto_approve_allowance.proto | 312 ++- .../services/crypto_create.proto | 204 +- .../services/crypto_delete.proto | 56 +- .../services/crypto_delete_allowance.proto | 76 +- .../services/crypto_delete_live_hash.proto | 39 +- .../services/crypto_get_account_balance.proto | 94 +- .../services/crypto_get_account_records.proto | 59 +- .../services/crypto_get_info.proto | 210 +- .../services/crypto_get_live_hash.proto | 57 +- .../services/crypto_get_stakers.proto | 51 +- .../services/crypto_service.proto | 98 +- .../services/crypto_transfer.proto | 82 +- .../services/crypto_update.proto | 174 +- .../services/custom_fees.proto | 405 ++-- hapi/hedera-protobufs/services/duration.proto | 26 +- .../services/ethereum_transaction.proto | 124 +- .../services/exchange_rate.proto | 71 +- .../services/file_append.proto | 67 +- .../services/file_create.proto | 127 +- .../services/file_delete.proto | 57 +- .../services/file_get_contents.proto | 67 +- .../services/file_get_info.proto | 98 +- .../services/file_service.proto | 95 +- .../services/file_update.proto | 81 +- hapi/hedera-protobufs/services/freeze.proto | 114 +- .../services/freeze_service.proto | 38 +- .../services/freeze_type.proto | 123 +- .../services/get_account_details.proto | 316 ++- .../services/get_by_key.proto | 70 +- .../services/get_by_solidity_id.proto | 67 +- .../services/network_get_execution_time.proto | 85 +- .../services/network_get_version_info.proto | 49 +- .../services/network_service.proto | 69 +- .../services/node_create.proto | 11 +- .../services/node_stake_update.proto | 375 ++-- .../services/node_update.proto | 3 +- hapi/hedera-protobufs/services/query.proto | 124 +- .../services/query_header.proto | 64 +- hapi/hedera-protobufs/services/response.proto | 125 +- .../services/response_code.proto | 49 +- .../services/response_header.proto | 58 +- .../schedulable_transaction_body.proto | 591 +++--- .../services/schedule_create.proto | 241 ++- .../services/schedule_delete.proto | 45 +- .../services/schedule_get_info.proto | 296 ++- .../services/schedule_service.proto | 140 +- .../services/schedule_sign.proto | 63 +- .../services/smart_contract_service.proto | 149 +- .../services/state/addressbook/node.proto | 18 +- .../state/blockrecords/block_info.proto | 124 +- .../state/blockrecords/running_hashes.proto | 63 +- .../services/state/common.proto | 57 +- .../congestion/congestion_level_starts.proto | 51 +- .../services/state/consensus/topic.proto | 135 +- .../services/state/contract/bytecode.proto | 25 +- .../state/contract/storage_slot.proto | 42 +- .../services/state/file/file.proto | 89 +- .../services/state/primitives.proto | 25 +- .../services/state/roster/roster.proto | 1 - .../services/state/roster/roster_state.proto | 60 +- .../services/state/schedule/schedule.proto | 198 +- .../throttles/throttle_usage_snapshots.proto | 75 +- .../services/state/token/account.proto | 515 ++++- .../state/token/account_pending_airdrop.proto | 66 +- .../state/token/network_staking_rewards.proto | 95 +- .../services/state/token/nft.proto | 63 +- .../state/token/staking_node_info.proto | 147 +- .../services/state/token/token.proto | 312 ++- .../services/state/token/token_relation.proto | 105 +- .../services/system_delete.proto | 79 +- .../services/system_undelete.proto | 70 +- .../services/throttle_definitions.proto | 125 +- .../hedera-protobufs/services/timestamp.proto | 53 +- .../services/token_airdrop.proto | 123 +- .../services/token_associate.proto | 83 +- .../services/token_burn.proto | 86 +- .../services/token_cancel_airdrop.proto | 43 +- .../services/token_claim_airdrop.proto | 25 +- .../services/token_create.proto | 327 ++- .../services/token_delete.proto | 57 +- .../services/token_dissociate.proto | 84 +- .../services/token_fee_schedule_update.proto | 57 +- .../services/token_freeze_account.proto | 65 +- .../token_get_account_nft_infos.proto | 60 +- .../services/token_get_info.proto | 301 ++- .../services/token_get_nft_info.proto | 47 +- .../services/token_get_nft_infos.proto | 73 +- .../services/token_grant_kyc.proto | 67 +- .../services/token_mint.proto | 90 +- .../services/token_pause.proto | 50 +- .../services/token_reject.proto | 94 +- .../services/token_revoke_kyc.proto | 67 +- .../services/token_service.proto | 180 +- .../services/token_unfreeze_account.proto | 66 +- .../services/token_unpause.proto | 49 +- .../services/token_update.proto | 280 ++- .../services/token_update_nfts.proto | 49 +- .../services/token_wipe_account.proto | 123 +- .../services/transaction.proto | 63 +- .../services/transaction_body.proto | 275 ++- .../services/transaction_contents.proto | 42 +- .../transaction_get_fast_record.proto | 64 +- .../services/transaction_get_receipt.proto | 170 +- .../services/transaction_get_record.proto | 164 +- .../services/transaction_receipt.proto | 182 +- .../services/transaction_record.proto | 168 +- .../services/transaction_response.proto | 70 +- .../services/unchecked_submit.proto | 43 +- .../hedera-protobufs/services/util_prng.proto | 44 +- .../services/util_service.proto | 43 +- .../bdd/junit/hedera/utils/GrpcUtils.java | 8 - .../services/bdd/spec/utilops/UtilVerbs.java | 20 - .../VerifyGetAccountNftInfosNotSupported.java | 40 - .../VerifyGetFastRecordNotSupported.java | 39 - .../checks/VerifyGetStakersNotSupported.java | 39 - .../VerifyGetTokenNftInfosNotSupported.java | 38 - .../bdd/suites/crypto/MiscCryptoSuite.java | 13 +- 139 files changed, 11305 insertions(+), 5201 deletions(-) delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/checks/VerifyGetAccountNftInfosNotSupported.java delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/checks/VerifyGetFastRecordNotSupported.java delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/checks/VerifyGetStakersNotSupported.java delete mode 100644 hedera-node/test-clients/src/main/java/com/hedera/services/bdd/spec/utilops/checks/VerifyGetTokenNftInfosNotSupported.java diff --git a/hapi/hedera-protobufs/block/stream/output/smart_contract_service.proto b/hapi/hedera-protobufs/block/stream/output/smart_contract_service.proto index 4967147916e2..193fc3202cc3 100644 --- a/hapi/hedera-protobufs/block/stream/output/smart_contract_service.proto +++ b/hapi/hedera-protobufs/block/stream/output/smart_contract_service.proto @@ -40,7 +40,7 @@ option java_package = "com.hedera.hapi.block.stream.output.protoc"; // <<>> This comment is special code for setting PBJ Compiler java package option java_multiple_files = true; -import "contract_call_local.proto"; +import "contract_types.proto"; import "sidecar_file.proto"; /** diff --git a/hapi/hedera-protobufs/block/stream/output/transaction_output.proto b/hapi/hedera-protobufs/block/stream/output/transaction_output.proto index 7730c60f86c2..308bf005cfbb 100644 --- a/hapi/hedera-protobufs/block/stream/output/transaction_output.proto +++ b/hapi/hedera-protobufs/block/stream/output/transaction_output.proto @@ -51,7 +51,7 @@ import "stream/output/smart_contract_service.proto"; * >> Only a few transactions produce output that is not in the transaction * >> and also not reflected in state changes. All other transaction types * >> are _currently_ not included here. We have, however, allocated names - * >> and indexes for those transaction types to preserve consistency if we + * >> for those transaction types to preserve consistency if we * >> add them later. * *